Что такое 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.

Quick recall
Почему Slim и Mezzio называют microframework, хотя на них можно собрать серьёзный API?

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;
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;
Middleware pipeline: request проходит внутрь через общие слои, handler создаёт response, затем response возвращается наружу через те же слои.
Quick recall
В каком случае microframework-подход начинает проигрывать full-stack фреймворку?

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.

Quick recall
Что важно помнить о методах PSR-7 response вроде `withHeader()` и `withStatus()`?

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 и более ясную поверхность поддержки.

См. также

Sources

  1. Slim 4 Documentation
  2. Slim Framework: Middleware
  3. Mezzio: Overview and Features
  4. Mezzio: The Middleware Factory
  5. PHP-FIG PSR-15: HTTP Server Request Handlers
  6. PHP-FIG PSR-7: HTTP message interfaces