Middlewares no Slim Framework: pipeline, responsabilidades e reuso

Capítulo 5

Tempo estimado de leitura: 11 minutos

+ Exercício

O que é um middleware no Slim (PSR-15)

No Slim, um middleware é uma camada que envolve o processamento da requisição HTTP. Ele pode ler/modificar o ServerRequestInterface, interromper o fluxo retornando uma resposta imediatamente, ou delegar para o próximo componente do pipeline e então pós-processar a resposta antes de devolvê-la ao cliente.

O Slim segue o padrão PSR-15, que define dois papéis principais:

  • Middleware: implementa MiddlewareInterface e possui process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface.
  • Request Handler: implementa RequestHandlerInterface e possui handle(ServerRequestInterface $request): ResponseInterface. No final do pipeline, o handler normalmente é a rota (seu action/controller).

Uma forma útil de pensar: middlewares são como filtros em série; cada um decide se chama o próximo e pode ajustar request/response.

Pipeline e ordem de execução

Como o Slim executa os middlewares

O Slim compõe um pipeline com middlewares aplicados em diferentes níveis:

  • Globais (aplicados na aplicação inteira)
  • De grupo (aplicados a um conjunto de rotas)
  • De rota (aplicados a uma rota específica)

Em termos práticos, a execução tem um comportamento de “camadas”:

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

  • Antes (pré-processamento): executa do mais externo para o mais interno (tipicamente: globais → grupo → rota → handler).
  • Depois (pós-processamento): ao retornar a resposta, a ordem se inverte (handler → rota → grupo → globais).

Isso permite que um middleware faça validações antes e, após chamar o próximo, normalize headers, logue o resultado, etc.

Exemplo mental de fluxo

Global A (before) -> Global B (before) -> Group C (before) -> Route D (before) -> Handler (rota) -> Route D (after) -> Group C (after) -> Global B (after) -> Global A (after)

Observação: a forma exata de empilhamento pode variar conforme você adiciona middlewares e como organiza grupos, mas a regra de ouro é: quem envolve por fora executa antes e finaliza por último.

Criando um middleware PSR-15 (passo a passo)

1) Estrutura mínima

Crie uma classe que implemente Psr\Http\Server\MiddlewareInterface:

<?php declare(strict_types=1); namespace App\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; final class ExampleMiddleware implements MiddlewareInterface { public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { // 1) antes do próximo handler // ... // 2) delega para o próximo $response = $handler->handle($request); // 3) depois do próximo handler // ... return $response; } }

2) Evite estado mutável

Middlewares devem ser preferencialmente stateless (sem armazenar dados por requisição em propriedades). Para passar dados adiante, use:

  • $request = $request->withAttribute('chave', $valor)
  • Headers (quando fizer sentido)

3) Como interromper o pipeline

Se uma condição falhar (ex.: autenticação), você pode retornar uma resposta imediatamente, sem chamar $handler->handle().

Aplicando middlewares: global, grupo e rota

Global (toda a aplicação)

Use $app->add() para adicionar um middleware global. A ordem de adição importa para o empilhamento.

// Exemplo: $app->add(new SomeMiddleware());

Por grupo

Ao agrupar rotas, aplique middlewares ao grupo para compartilhar responsabilidades (ex.: autenticação em /api, CORS em /api, etc.).

// Exemplo conceitual: $group = $app->group('/api', function(...) { ... }); // $group->add(new AuthMiddleware(...));

Por rota

Para casos específicos (ex.: rate limit apenas em uma rota sensível), aplique diretamente na rota.

// Exemplo conceitual: $app->get('/login', LoginAction::class)->add(new RateLimitMiddleware(...));

Middlewares comuns (implementações práticas)

1) Logging (request/response)

Objetivo: registrar método, path, status e tempo. Use um logger (ex.: PSR-3) injetado no middleware.

<?php declare(strict_types=1); namespace App\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Psr\Log\LoggerInterface; final class LoggingMiddleware implements MiddlewareInterface { public function __construct(private LoggerInterface $logger) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $start = microtime(true); $method = $request->getMethod(); $path = $request->getUri()->getPath(); $response = $handler->handle($request); $ms = (microtime(true) - $start) * 1000; $status = $response->getStatusCode(); $this->logger->info('http_request', [ 'method' => $method, 'path' => $path, 'status' => $status, 'duration_ms' => (int) $ms, ]); return $response; } }

Dica: se você também implementar correlação (request-id), inclua o ID no contexto do log.

2) CORS (Cross-Origin Resource Sharing)

Objetivo: permitir que navegadores chamem sua API a partir de outros domínios. Um middleware de CORS normalmente:

  • Responde OPTIONS (preflight) imediatamente com headers adequados
  • Adiciona headers CORS na resposta de requisições normais
<?php declare(strict_types=1); namespace App\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Slim\Psr7\Response; final class CorsMiddleware implements MiddlewareInterface { public function __construct( private string $allowOrigin = '*', private string $allowMethods = 'GET,POST,PUT,PATCH,DELETE,OPTIONS', private string $allowHeaders = 'Content-Type, Authorization, X-Request-Id', private string $exposeHeaders = 'X-Request-Id' ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { if (strtoupper($request->getMethod()) === 'OPTIONS') { $response = new Response(204); return $this->withCorsHeaders($response); } $response = $handler->handle($request); return $this->withCorsHeaders($response); } private function withCorsHeaders(ResponseInterface $response): ResponseInterface { return $response ->withHeader('Access-Control-Allow-Origin', $this->allowOrigin) ->withHeader('Access-Control-Allow-Methods', $this->allowMethods) ->withHeader('Access-Control-Allow-Headers', $this->allowHeaders) ->withHeader('Access-Control-Expose-Headers', $this->exposeHeaders); } }

Boas práticas: em produção, evite * se você usa cookies/credenciais; prefira lista de origens confiáveis.

3) Autenticação (Bearer token simples)

Objetivo: validar credenciais e anexar o “usuário autenticado” na requisição via attributes. O middleware deve:

  • Ler Authorization: Bearer <token>
  • Validar o token (via serviço injetado)
  • Se inválido, retornar 401
  • Se válido, adicionar user (ou auth) em $request->withAttribute()
<?php declare(strict_types=1); namespace App\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Slim\Psr7\Response; interface TokenValidatorInterface { /** @return array{id:string,roles:string[]} */ public function validate(string $token): array; } final class AuthMiddleware implements MiddlewareInterface { public function __construct(private TokenValidatorInterface $validator) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $auth = $request->getHeaderLine('Authorization'); if (!preg_match('/^Bearer\s+(.*)$/i', $auth, $m)) { return $this->unauthorized('missing_token'); } $token = trim($m[1]); try { $user = $this->validator->validate($token); } catch (\Throwable $e) { return $this->unauthorized('invalid_token'); } $request = $request->withAttribute('user', $user); return $handler->handle($request); } private function unauthorized(string $code): ResponseInterface { $response = new Response(401); $response->getBody()->write(json_encode([ 'error' => 'unauthorized', 'code' => $code ], JSON_UNESCAPED_SLASHES)); return $response->withHeader('Content-Type', 'application/json'); } }

Aplicação típica: adicionar esse middleware em um grupo /api ou em rotas privadas, mantendo rotas públicas fora do grupo.

4) Rate limiting simples (por IP + rota)

Objetivo: limitar chamadas por janela de tempo. Uma versão simples pode usar armazenamento em memória (útil para desenvolvimento) ou cache compartilhado (Redis) em produção. Abaixo um exemplo didático com um storage abstrato.

<?php declare(strict_types=1); namespace App\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Slim\Psr7\Response; interface RateLimitStoreInterface { public function increment(string $key, int $ttlSeconds): int; } final class RateLimitMiddleware implements MiddlewareInterface { public function __construct( private RateLimitStoreInterface $store, private int $limit = 60, private int $windowSeconds = 60 ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $ip = $request->getServerParams()['REMOTE_ADDR'] ?? 'unknown'; $routeKey = $request->getMethod() . ':' . $request->getUri()->getPath(); $key = 'rl:' . sha1($ip . '|' . $routeKey); $count = $this->store->increment($key, $this->windowSeconds); if ($count > $this->limit) { $response = new Response(429); $response->getBody()->write(json_encode([ 'error' => 'too_many_requests' ], JSON_UNESCAPED_SLASHES)); return $response ->withHeader('Content-Type', 'application/json') ->withHeader('Retry-After', (string) $this->windowSeconds); } $response = $handler->handle($request); return $response ->withHeader('X-RateLimit-Limit', (string) $this->limit) ->withHeader('X-RateLimit-Window', (string) $this->windowSeconds); } }

Passo a passo para usar com segurança:

  • Comece aplicando em rotas críticas (login, recuperação de senha).
  • Depois mova para um grupo (ex.: /api) com limites diferentes por subgrupo.
  • Em produção, implemente RateLimitStoreInterface com Redis/Memcached para consistência entre instâncias.

5) Normalização de resposta (JSON padrão + headers)

Objetivo: padronizar respostas, adicionando headers e garantindo Content-Type consistente. Um middleware pode:

  • Adicionar headers comuns (ex.: X-Content-Type-Options)
  • Garantir que respostas JSON tenham Content-Type: application/json
  • Opcionalmente, envelopar payloads (ex.: {"data": ...}) quando aplicável

Como o Slim trabalha com handlers que podem escrever no body, uma normalização agressiva (re-serializar body) pode ser intrusiva. Um caminho seguro é focar em headers e em um formato de erro consistente.

<?php declare(strict_types=1); namespace App\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; final class ResponseNormalizationMiddleware implements MiddlewareInterface { public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $response = $handler->handle($request); $response = $response ->withHeader('X-Content-Type-Options', 'nosniff') ->withHeader('X-Frame-Options', 'DENY'); // Se já houver Content-Type, respeite. Caso contrário, defina JSON por padrão. if (!$response->hasHeader('Content-Type')) { $response = $response->withHeader('Content-Type', 'application/json'); } return $response; } }

Dica: para padronizar erros (400/401/403/404/500), combine este middleware com um error handler central (sem duplicar lógica em cada action).

6) Correlação de request (Request-Id)

Objetivo: gerar/propagar um identificador único por requisição para rastrear logs e chamadas entre serviços. O middleware deve:

  • Ler X-Request-Id se o cliente já enviou
  • Se não houver, gerar um ID
  • Adicionar o ID em $request->withAttribute() para uso interno
  • Adicionar o header X-Request-Id na resposta
<?php declare(strict_types=1); namespace App\Middleware; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; final class RequestIdMiddleware implements MiddlewareInterface { public function __construct(private string $headerName = 'X-Request-Id') {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $incoming = trim($request->getHeaderLine($this->headerName)); $requestId = $incoming !== '' ? $incoming : $this->generateRequestId(); $request = $request->withAttribute('request_id', $requestId); $response = $handler->handle($request); return $response->withHeader($this->headerName, $requestId); } private function generateRequestId(): string { // 16 bytes => 32 hex chars return bin2hex(random_bytes(16)); } }

Integração com logging: no LoggingMiddleware, leia $request->getAttribute('request_id') e inclua no contexto do log.

Compondo middlewares com responsabilidades bem definidas

Separação por responsabilidade

Evite criar um “middleware monolítico” que faz CORS + auth + log + rate limit. Prefira middlewares pequenos e composáveis:

  • RequestIdMiddleware: apenas correlação
  • LoggingMiddleware: apenas log (mas pode usar request-id se existir)
  • CorsMiddleware: apenas CORS
  • AuthMiddleware: apenas autenticação
  • RateLimitMiddleware: apenas limitação
  • ResponseNormalizationMiddleware: apenas headers/padrões

Ordem sugerida (exemplo prático)

Uma ordem comum para maximizar rastreabilidade e consistência:

NívelMiddlewarePor quê
Global (primeiros)RequestIdGarante ID para todo o resto (logs, erros)
GlobalLoggingRegistra tudo, incluindo falhas e tempos
Global ou /apiCORSResponde preflight cedo e padroniza headers
Grupo privadoAuthBloqueia acesso antes de executar actions
Rota sensívelRateLimitLimita pontos críticos sem penalizar tudo
Global (últimos)ResponseNormalizationAplica headers finais em qualquer resposta

Observação: “primeiros” e “últimos” aqui se referem ao empilhamento desejado. Como a pilha é em camadas, teste e ajuste para garantir que headers finais sejam aplicados mesmo em respostas curtas (ex.: 401/429).

Evitando dependências circulares ao reutilizar middlewares

Problema comum

Dependência circular aparece quando:

  • Middleware A depende de Serviço B
  • Serviço B depende de algo que, direta ou indiretamente, depende do Middleware A

Isso costuma acontecer quando middlewares passam a “conhecer” detalhes de controllers, rotas ou do container de DI, ou quando serviços de domínio tentam acessar o pipeline HTTP.

Estratégias práticas para evitar

  • Dependa de interfaces pequenas: por exemplo, TokenValidatorInterface, RateLimitStoreInterface, LoggerInterface. O middleware não deve depender de controllers nem de serviços que dependam do middleware.
  • Não injete o App/Router no middleware: se precisar de dados de rota, use atributos do request (quando disponíveis) ou aplique o middleware no local correto (grupo/rota).
  • Use request attributes como contrato: ex.: request_id, user. Outros componentes leem esses atributos sem precisar conhecer o middleware.
  • Evite “middleware chamando middleware”: composição deve ser feita pelo pipeline (global/grupo/rota), não por instanciar um middleware dentro de outro.
  • Factories para construção: se a criação do middleware exige várias dependências, use uma factory dedicada (ou configuração do container) para montar o middleware sem acoplar camadas.

Padrão de composição recomendado

Em vez de um middleware “AuthAndRateLimitMiddleware”, componha assim:

// Grupo /api privado: AuthMiddleware // Rota /login: RateLimitMiddleware // Global: RequestIdMiddleware, LoggingMiddleware, ResponseNormalizationMiddleware

Assim, cada peça é reutilizável e você evita que um middleware precise conhecer regras de outro.

Checklist rápido de implementação

  • Todo middleware PSR-15 implementa process() e decide se chama $handler->handle().
  • Use withAttribute() para dados internos (user, request-id).
  • Use headers para comunicação com cliente (CORS, X-Request-Id, rate limit).
  • Prefira interfaces para dependências (logger, validator, store).
  • Componha por nível (global/grupo/rota) em vez de misturar responsabilidades.

Agora responda o exercício sobre o conteúdo:

Em um middleware PSR-15 no Slim, qual abordagem está mais alinhada com o funcionamento do pipeline e com boas práticas de reuso?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

No Slim (PSR-15), o middleware envolve o processamento: pode ajustar a requisição, interromper retornando uma resposta ou delegar ao prximo handler e depois pf3s-processar a resposta. Isso favorece responsabilidades pequenas e reutilize1veis.

Próximo capitúlo

Validação e sanitização de entrada no Back-end com Slim Framework

Arrow Right Icon
Capa do Ebook gratuito Back-end com Slim Framework (PHP): Rotas, Middlewares e Arquitetura Limpa
31%

Back-end com Slim Framework (PHP): Rotas, Middlewares e Arquitetura Limpa

Novo curso

16 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.