Рантаймы (FPM и Swoole)
Один и тот же код Winter работает под PHP-FPM и под Swoole без изменений в
контроллерах и middleware. Рантайм выбирается тем, какой метод Boot зовёт точка
входа. Разница доходит до вашего кода ровно в одном месте — общем состоянии.
Что такое рантайм и зачем
Рантайм — среда, в которой исполняется приложение: классический PHP-FPM (процесс на запрос) или Swoole (долгоживущие корутинные воркеры).
Проблема. FPM прост и совместим, но создаёт всё заново на каждый запрос. Swoole даёт корутины и высокую пропускную способность, но живёт долго — и наивный код с общим состоянием под ним ломается. Хочется писать один код, а рантайм выбирать.
Решение. Winter прячет транспорт за контрактами HttpRequest/HttpResponse:
контроллеры одинаковы, а рантайм задаётся точкой входа. Об этом и раздел.
Точки входа
Каждому рантайму — свой метод Boot:
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// 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):
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 (см. Внедрение зависимостей).
Это фактически единственное различие, доходящее до кода приложения. Всё остальное (адаптеры запроса/ответа, пул, контекст логов) фреймворк берёт на себя.
Дальше
- CLI → run — запуск сервера и опции
- Внедрение зависимостей — scope’ы и общее состояние
- Логирование — контекст под FPM и Swoole