Адаптери рантаймів
Lift постачає три необов’язкові адаптери для довгоживучих рантаймів PHP. Усі вони тримають екземпляр $app живим між запитами, тож вартість завантаження оплачується лише один раз — з’єднання з БД залишаються прогрітими, кеш рефлексії гарячий, а синглтони повторно використовуються.
| Рантайм | Клас | Транспорт |
|---|---|---|
| RoadRunner | Lift\Runtime\RoadRunnerWorker |
Go-процес, PSR-7 через IPC |
| Swoole / OpenSwoole | Lift\Runtime\SwooleServer |
Розширення PHP, асинхронний ввід-вивід |
| FrankenPHP | Lift\Runtime\FrankenPhpWorker |
Вбудований Caddy, заповнює суперглобалі |
Персистентний стан — застосовно до всіх рантаймів
Оскільки той самий процес PHP обробляє багато запитів, об’єкти-синглтони, зареєстровані через $app->singleton(), живуть увесь термін життя воркера — це зроблено навмисно:
- З’єднання з базою даних зберігаються → поведінка пулу з’єднань, без накладних витрат на перепідключення.
- Логери, кеші, HTTP-клієнти → прогріті й повторно використовуються.
Стан рівня запиту (наприклад, автентифікований користувач) ніколи не має зберігатися в синглтоні. Кладіть його в атрибути запиту:
// middleware
$user = Auth::check($request);
$request = $request->withAttribute('user', $user);
// обробник
$user = $request->getAttribute('user');
RoadRunner
RoadRunner — це сервер PHP-застосунків на основі Go. Воркери — це довгоживучі процеси PHP, які спілкуються з батьківським Go-процесом через IPC.
Вимоги
composer require spiral/roadrunner-http nyholm/psr7
./vendor/bin/rr get-binary # завантажує бінарник rr
Налаштування
worker.php (корінь проєкту):
<?php
require 'vendor/autoload.php';
$app = require 'bootstrap/app.php';
(new \Lift\Runtime\RoadRunnerWorker($app))->serve();
.rr.yaml:
version: "3"
server:
command: "php worker.php"
http:
address: "0.0.0.0:8080"
pool:
num_workers: 4
max_jobs: 1000 # перезапускати воркер після N запитів (захист від витоків пам’яті)
Запуск:
./rr serve
Фабрика PSR-17
RoadRunnerWorker::serve() автоматично визначає фабрику PSR-17 із встановлених пакетів (Nyholm → Guzzle → Laminas у такому порядку). Передайте свою, щоб перевизначити:
(new RoadRunnerWorker($app))->serve(new \Nyholm\Psr7\Factory\Psr17Factory());
Як це працює
Кожна ітерація циклу:
PSR7Worker::waitRequest()блокується, доки RoadRunner не доставить наступний HTTP-запит як PSR-7ServerRequestInterface.Request::fromPsr7()перетворює його на LiftRequest.$app->handle($request)виконує конвеєр middleware + маршрутизатора.PSR7Worker::respond()надсилає LiftResponse(який уже реалізуєResponseInterface) назад до RoadRunner.- RoadRunner проксує його клієнту.
Swoole / OpenSwoole
Swoole — це розширення PHP, що додає асинхронний, подієво-керований HTTP-сервер прямо в PHP. Зовнішній бінарник не потрібен.
Вимоги
pecl install swoole
# або
pecl install openswoole
Увімкніть у php.ini:
extension=swoole
Налаштування
server.php (корінь проєкту):
<?php
require 'vendor/autoload.php';
$app = require 'bootstrap/app.php';
(new \Lift\Runtime\SwooleServer($app))->start();
Запуск:
php server.php
Конфігурація
Передайте масив налаштувань Swoole другим аргументом:
new \Lift\Runtime\SwooleServer($app, [
'host' => '0.0.0.0',
'port' => 9501,
'worker_num' => swoole_cpu_num() * 2,
'max_request' => 1000, // перезапускати воркер після N запитів
'daemonize' => false,
'log_file' => '/var/log/swoole.log',
]);
Повний перелік налаштувань: документація Swoole.
Корутини
Якщо ви вмикаєте корутини (наприклад, \Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL)), кожен обробник запиту виконується у власній корутині. Стандартний PDO і блокувальний ввід-вивід не є корутинно-обізнаними. Варіанти:
- Використовуйте
Swoole\Database\PDOPoolдля корутинно-безпечного доступу до бази даних. - Або тримайте корутини вимкненими (за замовчуванням) і покладайтеся на кілька воркерів для конкурентності.
Як це працює
Колбек on('request', ...) спрацьовує синхронно для кожного запиту:
SwooleServerперетворює\Swoole\Http\Request→ LiftRequest(метод, URI, заголовки, cookie, тіло).$app->handle($request)виконує конвеєр.- Статус, заголовки й тіло записуються назад у
\Swoole\Http\Response.
FrankenPHP
FrankenPHP — це сервер PHP-застосунків, вбудований у Caddy. У режимі воркера він заповнює суперглобалі ($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES) заново для кожного запиту — рівно як PHP-FPM. Це означає, що Request::fromGlobals() працює без змін.
Вимоги
Завантажте бінарник FrankenPHP (він включає PHP + Caddy — окреме встановлення не потрібне):
curl -L https://github.com/dunglas/frankenphp/releases/latest/download/frankenphp-linux-x86_64 \
-o frankenphp && chmod +x frankenphp
Налаштування
worker.php (корінь проєкту):
<?php
require 'vendor/autoload.php';
$app = require 'bootstrap/app.php';
(new \Lift\Runtime\FrankenPhpWorker($app))->serve();
Caddyfile:
{
frankenphp
admin off
auto_https off
}
:8080 {
root * public
# Маршрутизувати кожен запит через worker.php.
# У режимі воркера Caddy використовує цей шлях для ідентифікації пулу воркерів;
# уже запущений воркер обробляє фактичну логіку запиту.
rewrite * /worker.php
php_server {
worker worker.php 4 # кількість воркерів; опустіть, щоб використати число CPU
}
}
Запуск:
./frankenphp run --config Caddyfile
Як це працює
FrankenPhpWorker::serve() циклиться на frankenphp_handle_request():
- FrankenPHP заповнює суперглобалі й викликає колбек.
Request::fromGlobals()будує свіжий LiftRequest.$app->handle($request)виконує конвеєр.- Відповідь надсилається через
http_response_code(),header()іecho. - FrankenPHP завершує HTTP-цикл, і цикл продовжується.
Міграція з php-fpm
Якщо ваш наявний public/index.php викликає $app->run(), загорніть це в режим воркера FrankenPHP:
// worker.php
require 'vendor/autoload.php';
$app = require 'bootstrap/app.php';
(new \Lift\Runtime\FrankenPhpWorker($app))->serve();
Ваш public/index.php може залишатися незмінним для традиційних розгортань FPM. Для режиму воркера FrankenPHP потрібна лише точка входу worker.php.