Laravel Octane — long-lived processes, what changes
Concept
Laravel Octane fundamentally changes PHP's execution model. Traditional PHP: each HTTP request boots the framework (loads all service providers, registers all bindings, sets up all singletons) — then processes the request — then discards everything. With Octane, the framework boots ONCE, and each subsequent request reuses the same bootstrapped application.
Workers: Octane runs your application in a long-lived PHP process using Swoole or RoadRunner. The process boots Laravel once, then loops: accept request → reset state → handle request → send response → repeat. No bootstrap overhead per request. No OPcache revalidation. Benchmarks show 2-10× throughput improvement.
What changes in your code:
- No per-request globals:
$_GET,$_POST,$_SERVERare only valid for the current request cycle. Octane resets them but any code that caches them statically between requests will serve stale data to the next request. - No static state between requests: Static class properties persist across requests. If your code sets
SomeService::$state = 'something'and never resets it, the second request sees the first request's state. - Singletons are true singletons: In traditional PHP, a singleton only lives for one request. In Octane, it lives for the process lifetime. Services that hold per-request state (authentication, localization) must be properly reset between requests or must NOT be singletons.
- Memory leaks are real: Object cycles, accumulated static state, and un-freed resources accumulate over thousands of requests. Monitor memory and restart workers periodically.
Octane service providers: Laravel Octane introduces flush bindings — services that should be re-created per request even in the long-lived process. Use $this->app->scoped() instead of $this->app->singleton() for per-request services.
Code Example
<?php
// Installation: composer require laravel/octane
// php artisan octane:install --server=swoole
// php artisan octane:start --workers=4
// ===== PROBLEM: static state persists across requests =====
class StatefulService
{
private static array $cache = []; // persists across requests in Octane!
public static function process(string $key): string
{
if (!isset(self::$cache[$key])) {
self::$cache[$key] = expensiveComputation($key);
}
return self::$cache[$key];
}
}
// FIX: use instance state or per-request container binding
// ===== PROBLEM: singleton holds per-request data =====
class UserContext
{
private ?User $currentUser = null; // in Octane: persists across requests!
public function setUser(User $u): void { $this->currentUser = $u; }
public function getUser(): ?User { return $this->currentUser; }
}
// FIX: use scoped binding (resets per request)
// In ServiceProvider:
// $this->app->scoped(UserContext::class); // re-instantiated per Octane request
// ===== Octane-aware service provider =====
use Laravel\Octane\Facades\Octane;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// scoped() = singleton within a request, new instance per request in Octane
$this->app->scoped(UserContext::class);
$this->app->scoped(RequestContext::class);
}
}
// ===== Memory monitoring in long-lived processes =====
// Octane worker restart after N requests:
// OCTANE_MAX_REQUESTS=500 in .env
// php artisan octane:start --max-requests=500
// Monitoring memory leak
Octane::tick('memory-check', function() {
$mb = memory_get_usage(true) / 1024 / 1024;
if ($mb > 256) {
logger()->warning("High memory: {$mb}MB — consider restarting worker");
}
})->seconds(60);