Что такое Slim и Mezzio
Slim и Mezzio — PHP-фреймворки для случаев, где приложение лучше собрать из небольшого HTTP-ядра, middleware и выбранных библиотек, а не брать full-stack набор с ORM, шаблонизатором, консолью, очередями и собственной архитектурной «дорогой». Такой подход часто называют microframework, хотя слово немного обманчивое: маленьким остаётся ядро, а итоговый проект может быть вполне серьёзным API, backend-for-frontend, webhook-сервисом или административным шлюзом.
Если Laravel, Symfony и Yii дают готовый framework-way, Slim и Mezzio ближе к конструктору вокруг HTTP. Центральные понятия здесь — route, ServerRequestInterface, ResponseInterface, request handler, middleware pipeline и DI container. Поэтому эта статья напрямую продолжает PSR-7, middleware и HTTP-клиенты, а также опирается на Composer, Packagist и composer.json, Autoloading и PSR-4 и Неймспейсы и use.
Microframework-подход
В full-stack фреймворке вы обычно начинаете с готовой структуры проекта: controllers, models, migrations, templates, queues, console commands, config conventions. В microframework-подходе стартовая точка другая: есть HTTP request, есть набор middleware, есть route, который вызывает handler и возвращает HTTP response. Всё остальное добавляется явно: контейнер, шаблонизатор, ORM, логгер, валидатор, auth, OpenAPI, миграции.
Это удобно, когда приложение само по себе узкое: JSON API для мобильного клиента, webhook receiver для платежей, health-check сервис, internal tool, reverse proxy-like gateway, небольшой public endpoint рядом с большим монолитом. Но это же становится риском, если команда пытается построить полноценный продукт и постепенно вручную изобретает свой Laravel: auth, roles, admin UI, background jobs, migrations, mail, forms, templates, conventions. В такой точке стоит вернуться к Выбор PHP-фреймворка и честно сравнить стоимость свободы со стоимостью готовых соглашений.
flowchart LR
A[HTTP request] --> B[Error / logging middleware]
B --> C[Body parsing / CORS / auth]
C --> D[Routing]
D --> E[Handler]
E --> F[Domain service / database / API client]
F --> E
E --> G[PSR-7 response]
G --> C
C --> B
B --> H[HTTP response]
classDef edge fill:#f7f7f7,stroke:#777,color:#111;
class A,H edge;PSR-7 и PSR-15 как общий язык
PSR-7 описывает HTTP messages: request, response, headers, body streams, URI, uploaded files. В коде это обычно Psr\Http\Message\ServerRequestInterface и Psr\Http\Message\ResponseInterface. Объекты PSR-7 считаются immutable-style: методы вроде withHeader() или withStatus() возвращают новый response, а не меняют текущий объект на месте.
PSR-15 добавляет две роли:
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class AddRequestIdHeader implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$response = $handler->handle($request);
return $response->withHeader('X-Request-Id', 'req-123');
}
}Middleware может выполнить код до handler, делегировать дальше через $handler->handle($request), затем изменить response. Или может не делегировать вообще: например, вернуть 401 Unauthorized, если нет токена. Это та же область, что GET, POST и фильтрация ввода, HTTP-заголовки, ответы и редиректы, CSRF и state-changing запросы и XSS, экранирование вывода и шаблоны, только выраженная через pipeline, а не через глобальный controller lifecycle.
Slim: минимальный dispatcher
Slim особенно прямолинеен: создаёте app, добавляете middleware, объявляете routes, запускаете app. Route callback получает PSR-7 request и response, пишет тело и возвращает response.
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$app = AppFactory::create();
$app->get('/api/ping', function (Request $request, Response $response): Response {
$payload = json_encode(['ok' => true], JSON_THROW_ON_ERROR);
$response->getBody()->write($payload);
return $response->withHeader('Content-Type', 'application/json');
});
$app->run();В Slim 4 контейнер не встроен как обязательная часть. Если нужен DI, обычно подключают PSR-11 container, например PHP-DI, и передают его в AppFactory. Это хорошо сочетается с проектами, где зависимости хочется держать явно: PDO, logger, сервисы домена, clients для внешних API. Но за явность приходится платить дисциплиной: route callback быстро превращается в свалку, если туда положить SQL, validation, auth и форматирование ответа одновременно.
Для неигрушечного Slim-кода лучше быстро перейти от anonymous callbacks к классам:
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class PingHandler implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
// На практике response factory лучше получить через constructor DI.
$response = new \Slim\Psr7\Response();
$response->getBody()->write('{"ok":true}');
return $response->withHeader('Content-Type', 'application/json');
}
}Важная деталь Slim: middleware добавляются слоями, и последний добавленный middleware выполняется первым. Поэтому порядок add() влияет на security и error handling. Routing middleware обычно добавляют раньше error middleware в коде, а error middleware — последним, чтобы он оказался внешним слоем и ловил исключения из следующих компонентов.
Mezzio: PSR-first pipeline
Mezzio сильнее подчёркивает PSR-15-модель: application — это очередь middleware, routing и dispatch — отдельные middleware, handlers — внутренний слой, который должен вернуть response. В отличие от Slim, Mezzio pipeline исполняется в порядке добавления: middleware, подключённый раньше, получает request раньше.
Типичный route в Mezzio связывает HTTP method и path с handler-классом:
// config/routes.php
use App\Handler\PingHandler;
use Mezzio\Application;
return static function (Application $app): void {
$app->get('/api/ping', PingHandler::class, 'api.ping');
};Handler выглядит как обычный PSR-15 request handler:
namespace App\Handler;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class PingHandler implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
return new JsonResponse(['ok' => true]);
}
}Mezzio часто выбирают, когда команда хочет не просто «маленький фреймворк», а аккуратную middleware-архитектуру поверх стандартов: PSR-7, PSR-15, PSR-11, laminas components, route-specific pipelines, explicit factories. Он ближе к компонентному миру Symfony, чем к «быстро набросать endpoint в одном файле». В обмен на это старт может казаться более церемониальным: container config, factories, route config, middleware order.
Routing, middleware и контейнер
В обоих фреймворках routing отвечает на вопрос «какой handler должен обработать этот request?». Middleware отвечает на другой вопрос: «что должно происходить вокруг обработки?». Auth, CORS, body parsing, request id, logging, exception-to-response conversion, rate limiting, content negotiation — обычно middleware. Бизнес-действие вроде CreateOrderHandler или ExportReportHandler — handler.
DI container не должен становиться service locator, из которого всё достают где попало. Лучше, когда container создаёт handler и middleware, а зависимости приходят через constructor:
final class CreateOrderHandler implements RequestHandlerInterface
{
public function __construct(
private OrderService $orders,
private ResponseFactoryInterface $responses,
) {}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = $request->getParsedBody();
$order = $this->orders->createFromPayload($data);
$response = $this->responses->createResponse(201);
$response->getBody()->write(json_encode($order, JSON_THROW_ON_ERROR));
return $response->withHeader('Content-Type', 'application/json');
}
}Такой стиль хорошо тестируется через PHPUnit, моки и стабы и легче анализируется инструментами из PHPDoc, generics и статический анализ. Главное — не смешивать транспортный слой и доменную логику: PSR request не должен протекать во все сервисы приложения без необходимости.
Когда лёгкий стек лучше full-stack
Slim или Mezzio обычно уместны, если границы приложения узкие, команда понимает HTTP, а нужные инфраструктурные решения уже выбраны отдельно. Например: сервис принимает webhook, проверяет подпись, кладёт задачу в очередь из Очереди, фоновые задачи и воркеры и отвечает 202 Accepted. Или backend endpoint читает данные через PDO и подключение к базе, отдаёт JSON и не нуждается в Blade, Eloquent, FormRequest, policies и admin panel.
Full-stack фреймворк чаще выигрывает, когда нужны авторизация, роли, формы, шаблоны, миграции, ORM, очереди, notifications, CLI, ecosystem packages и единые conventions для большой команды. Microframework не запрещает всё это добавить. Он просто не даёт готового ответа, как именно это должно быть устроено.
Практическое правило: если в Slim/Mezzio-проекте появляется много самописной «инфраструктуры фреймворка», проверьте, не дешевле ли перейти на Laravel, Symfony или Yii. Если же приложение остаётся HTTP-пайплайном с понятными handler-классами, лёгкий стек часто даёт меньше магии, меньше boot overhead и более ясную поверхность поддержки.
См. также
- PSR-7, middleware и HTTP-клиенты — базовая модель HTTP messages, middleware и clients.
- Composer, Packagist и composer.json — установка Slim, Mezzio, PSR-7 implementations и DI containers.
- Autoloading и PSR-4 и Неймспейсы и use — организация handler, middleware и service-классов.
- HTTP-заголовки, ответы и редиректы, GET, POST и фильтрация ввода, XSS, экранирование вывода и шаблоны — HTTP- и security-правила, которые microframework не отменяет.
- Laravel, Symfony, Yii, Выбор PHP-фреймворка — соседние варианты, если нужен более полный framework layer.