Продвинутое

Рантаймы (FPM и Swoole)

Один и тот же код Winter работает под PHP-FPM и под Swoole без изменений в контроллерах и middleware. Рантайм выбирается тем, какой метод Boot зовёт точка входа. Разница доходит до вашего кода ровно в одном месте — общем состоянии.

FPM Boot::web()Swoole Boot::swoole()Контракты HttpRequest / HttpResponse

Что такое рантайм и зачем

Рантайм — среда, в которой исполняется приложение: классический PHP-FPM (процесс на запрос) или Swoole (долгоживущие корутинные воркеры).

Проблема. FPM прост и совместим, но создаёт всё заново на каждый запрос. Swoole даёт корутины и высокую пропускную способность, но живёт долго — и наивный код с общим состоянием под ним ломается. Хочется писать один код, а рантайм выбирать.

Решение. Winter прячет транспорт за контрактами HttpRequest/HttpResponse: контроллеры одинаковы, а рантайм задаётся точкой входа. Об этом и раздел.

Точки входа

Каждому рантайму — свой метод Boot:

php
final public static function web(): never;                                  // FPM / dev
final public static function swoole(string $host = '0.0.0.0', int $port = 9501): never;
final public static function cli(array $argv = []): never;                   // консоль
final public static function executor(array $argv = []): never;             // потомок thread
php
// public/index.php (FPM)
require __DIR__ . '/../bootstrap.php';
Boot::web();

// server.php (Swoole — нужно ext-swoole)
require __DIR__ . '/bootstrap.php';
Boot::swoole('0.0.0.0', 8080);

Запуск сервера — командой run (run = Swoole, run dev = встроенный сервер PHP).

Различия

FPM (web) Swoole (swoole)
Модель Процесс на запрос, без общего состояния Долгоживущие воркеры, состояние в памяти
Маршруты Router::resolve (кеш при DEBUG=false) Скан один раз при старте, дальше из памяти
Корутины нет SWOOLE_HOOK_ALL — PDO/cURL/файлы становятся корутинными
Пул соединений синглтон на воркер корутинный пул
Контекст логов ProcessContext CoroutineContext
Статика Router::static() Router::static()

Настройка Swoole

Переопределите swooleConfig() в Boot — он public static (не protected):

bootstrap.php
public static function swooleConfig(): array
{
  return [
      'worker_num'       => swoole_cpu_num() * 2,
      'max_request'      => 5000,        // перезапуск воркера — бьётся с утечками
      'enable_coroutine' => true,
  ];
}

CLI-опции run (--workers, --max_request…) переопределяют эти значения.

Главный подводный камень — общее состояние

Не храните состояние запроса в синглтоне

Под Swoole воркер обслуживает много запросов, поэтому #[Singleton] переиспользуется между ними. Никогда не кладите данные текущего запроса (текущего пользователя, request-id, загруженную сущность) в свойства синглтона — они утекут в чужой запрос. Держите синглтоны без состояния, а состояние запроса — в #[Request]-scope (см. Внедрение зависимостей).

Это фактически единственное различие, доходящее до кода приложения. Всё остальное (адаптеры запроса/ответа, пул, контекст логов) фреймворк берёт на себя.

Дальше