Небольшие готовые к копированию рецепты, показывающие, как Lift решает реальные задачи.
API «Hello, World»
Минимально возможное приложение Lift — JSON на входе, JSON на выходе.
<?php
require 'vendor/autoload.php';
use Lift\App;
use Lift\Http\Response;
$app = new App();
$app->get('/', fn() => Response::json(['hello' => 'world']));
$app->run();
REST CRUD с контроллером
Типизированный контроллер, автоматически разрешаемый через контейнер.
<?php
use Lift\App;
use Lift\Http\Request;
use Lift\Http\Response;
class UserController
{
public function __construct(private readonly UserRepository $users) {}
public function index(): array
{
return $this->users->all(); // -> авто JSON
}
public function show(Request $r): Response
{
$user = $this->users->find((int) $r->param('id'));
return $user
? Response::json($user)
: Response::json(['error' => 'Not found'], 404);
}
public function store(Request $r): Response
{
$id = $this->users->create($r->json());
return Response::json(['id' => $id], 201);
}
}
$app = new App();
$app->group('/api/v1', function ($g) {
$g->get('/users', [UserController::class, 'index']);
$g->get('/users/{id:\d+}', [UserController::class, 'show']);
$g->post('/users', [UserController::class, 'store']);
});
$app->run();
Auth-middleware + JWT
Проверка Bearer-токена в middleware, прикрепление пользователя к запросу.
<?php
use Lift\Http\Response;
use Lift\Jwt\Jwt;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class AuthMiddleware implements MiddlewareInterface
{
public function __construct(private readonly Jwt $jwt) {}
public function process(ServerRequestInterface $r, RequestHandlerInterface $h): ResponseInterface
{
[, $token] = explode(' ', $r->getHeaderLine('Authorization') . ' ', 2);
try {
$claims = $this->jwt->decode($token);
} catch (\Throwable) {
return Response::json(['error' => 'Unauthorized'], 401);
}
return $h->handle($r->withAttribute('user', $claims));
}
}
$app->singleton(Jwt::class, fn() => new Jwt(secret: $_ENV['APP_KEY']));
$app->get('/me', fn(Request $r) => $r->getAttribute('user'))
->middleware(AuthMiddleware::class);
Валидация
Текучие правила — при ошибке автоматически возвращается 422 с картой ошибок.
<?php
$app->post('/signup', function (Request $r) {
// validate() объединяет тело + query + параметры маршрута, выбрасывает ValidationException при ошибке
$data = $r->validate([
'email' => 'required|email',
'password' => 'required|min_length:8',
'age' => 'integer|min:13',
]);
return Response::json(['user' => createUser($data)], 201);
});
Фоновая задача через очередь
Помещаем задачу из обработчика; воркер её подхватывает.
<?php
use Lift\Queue\AbstractJob;
use Lift\Queue\RedisQueue;
use Lift\Redis\RedisClient;
// AbstractJob предоставляет failed()/getQueue()/getDelay()/getTries();
// вы реализуете только handle().
class SendWelcomeEmail extends AbstractJob
{
public function __construct(private string $email) {}
public function handle(): void
{
// ... отправить письмо
}
}
$app->setQueue(new RedisQueue($app->make(RedisClient::class)));
$app->post('/signup', function (Request $r) use ($app) {
$email = $r->input('email');
$app->dispatch(new SendWelcomeEmail($email));
return Response::noContent();
});
Эндпоинт JSON-RPC 2.0
Привяжите JsonRpcServer к одному маршруту — полное соответствие спецификации.
<?php
use Lift\JsonRpc\JsonRpcServer;
$rpc = new JsonRpcServer();
// Параметры внедряются по имени (объект params) или по позиции (массив params)
$rpc->register('math.add', fn(int $a, int $b): int => $a + $b);
$rpc->register('math.mul', fn(int $a, int $b): int => $a * $b);
$app->rpc('/rpc', $rpc);
Server-Sent Events
Потоковая отправка живых обновлений клиенту.
<?php
use Lift\Http\SseEmitter;
use Lift\Http\SseEvent;
use Lift\Http\SseResponse;
$app->get('/stream/clock', function () {
return new SseResponse(function (SseEmitter $emit) {
while (true) {
$emit(SseEvent::json(['ts' => date('c')]));
sleep(1);
}
});
});
Готовые к подключению воркеры для RoadRunner, Swoole и FrankenPHP — держат PHP в памяти между запросами без единого изменения кода приложения.
RoadRunner
spiral/roadrunner-http
nyholm/psr7
PHP остаётся в памяти между запросами — без затрат на bootstrap на каждый запрос. Lift автоматически определяет установленную фабрику PSR-17 (Nyholm → Guzzle → Laminas).
<?php
// public/worker.php
require 'vendor/autoload.php';
use Lift\App;
use Lift\Http\Response;
use Lift\Runtime\RoadRunnerWorker;
$app = new App();
$app->get('/', fn() => Response::json(['status' => 'ok']));
$app->get('/users/{id:\d+}', fn(\Lift\Http\Request $r) => ['id' => $r->param('id')]);
(new RoadRunnerWorker($app))->serve();
.rr.yaml → server: {command: "php public/worker.php"}
Swoole / OpenSwoole
ext-swoole
Lift преобразует \Swoole\Http\Request → Lift Request и записывает обратно Lift Response — ваши обработчики маршрутов остаются идентичными.
<?php
require 'vendor/autoload.php';
use Lift\App;
use Lift\Http\Response;
use Lift\Runtime\SwooleServer;
$app = new App();
$app->get('/', fn() => Response::json(['status' => 'ok']));
$app->get('/users/{id:\d+}', fn(\Lift\Http\Request $r) => ['id' => $r->param('id')]);
(new SwooleServer($app, [
'host' => '0.0.0.0',
'port' => 9501,
'worker_num' => swoole_cpu_num(),
]))->start();
FrankenPHP
режим воркера
Тот же public/index.php работает и в режиме воркера FrankenPHP, и в классическом php-fpm — отдельная точка входа не нужна.
<?php
require 'vendor/autoload.php';
use Lift\App;
use Lift\Http\Response;
use Lift\Runtime\FrankenPhpWorker;
$app = new App();
$app->get('/', fn() => Response::json(['status' => 'ok']));
// Режим воркера FrankenPHP: PHP крутится в цикле на frankenphp_handle_request().
// Откатывается к одному циклу запроса для php-fpm / php -S.
if (function_exists('frankenphp_handle_request')) {
(new FrankenPhpWorker($app))->serve();
} else {
$app->run();
}