Класичний PHP-FPM створює застосунок з нуля на кожен запит: автозавантаження, провайдери, конфігурація, роутинг. Octane завантажує все один раз у воркер на Swoole, FrankenPHP чи RoadRunner і далі обробляє запити тим самим процесом. Звідси виграш: бутстрап Laravel зникає з кожного запиту. Сам PHP-код не стає швидшим.
Ціна — стан, який більше не помирає разом із запитом. Три джерела проблем: singleton у контейнері, статичні властивості класів і глобальні змінні чи memoization у хелперах. Синглтон, який отримав Request у конструкторі, на другому запиті тримає Request першого користувача. Статичний кеш, який раніше жив 50 мс, тепер живе добу й росте.
Octane сам скидає те, про що знає: auth, session, database connections, cache resolvers, translator. Про ваші класи він не знає. Тому перед міграцією потрібен аудит привʼязок: усе, що тримає стан запиту, переводиться на scoped, статичні кеші отримують очищення на RequestTerminated, а Request передається в методи, а не в конструктори довгоживучих сервісів.
Перед переходом варто виміряти, де йде час. Якщо запит триває 500 мс через SQL і зовнішні API, економія 30 мс бутстрапу непомітна, а ризики витоків реальні. Octane виправданий на легких ендпоінтах з великим RPS, де бутстрап є значною частиною часу відповіді.
// НЕБЕЗПЕЧНО під Octane: Request живе в синглтоні між запитами
final class CurrentTenant
{
private ?Tenant $tenant = null;
public function __construct(private Request $request) {}
public function get(): Tenant
{
return $this->tenant ??= Tenant::whereHost($this->request->getHost())->firstOrFail();
}
}
$this->app->singleton(CurrentTenant::class); // другий користувач побачить tenant першого
// БЕЗПЕЧНО: scoped скидається після кожного запиту
$this->app->scoped(CurrentTenant::class);
// Або взагалі не тримати Request у стані: брати його в момент виклику
final class CurrentTenant
{
public function get(Request $request): Tenant { /* ... */ }
}
// Прихований витік: статичний кеш росте з кожним запитом
final class Pricing
{
private static array $cache = []; // ніколи не очищається під Octane
}
// Скидання власного стану між запитами
Octane::tick('flush-pricing', fn () => Pricing::flush())->seconds(60);
Event::listen(RequestTerminated::class, fn () => Pricing::flush());
Обовʼязково згадайте, що синглтони, які приймають Request, треба переписати на scoped, інакше дані одного користувача побачить інший. І назвіть FrankenPHP як актуальний варіант поруч зі Swoole та RoadRunner.