O que o Slim recebe e devolve no ciclo HTTP
No Slim, cada requisição HTTP percorre um ciclo bem definido: o servidor entrega uma requisição ao aplicativo, o Slim a representa como um objeto ServerRequestInterface (PSR-7), passa por uma cadeia de middlewares (PSR-15) e, ao final, uma rota (handler) produz um objeto ResponseInterface (PSR-7). O Slim não “imprime” diretamente; ele sempre trabalha com objetos imutáveis (principalmente em PSR-7), então alterações em request/response retornam novas instâncias.
Contratos PSR envolvidos
- PSR-7: define mensagens HTTP (Request/Response), headers, URI, stream do body, cookies e arquivos enviados.
- PSR-15: define middlewares e request handlers, padronizando como interceptar/encadear a execução.
Na prática, uma rota Slim costuma ter a assinatura:
use Psr\Http\Message\ResponseInterface as Response;use Psr\Http\Message\ServerRequestInterface as Request;use Slim\App;$app->get('/exemplo', function (Request $request, Response $response): Response { // ler request e construir response return $response;});PSR-7 na prática: lendo dados do Request
1) Lendo headers
Headers podem ter múltiplos valores. Use getHeaderLine para obter uma string única (valores concatenados) ou getHeader para array.
$userAgent = $request->getHeaderLine('User-Agent');$accept = $request->getHeaderLine('Accept');$authHeader = $request->getHeaderLine('Authorization');Boas práticas: normalize a lógica para ausência de header e evite assumir que ele sempre existe.
$traceId = $request->getHeaderLine('X-Trace-Id');if ($traceId === '') { $traceId = bin2hex(random_bytes(16));}2) Query params (URL)
Query params vêm da URL, como /users?page=2&active=1.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
$query = $request->getQueryParams();$page = isset($query['page']) ? (int)$query['page'] : 1;$active = isset($query['active']) ? filter_var($query['active'], FILTER_VALIDATE_BOOL) : null;Passo a passo recomendado:
- Leia todos com
getQueryParams(). - Converta tipos (int/bool) explicitamente.
- Defina defaults.
- Valide limites (ex.: page >= 1).
3) Route params (parâmetros de rota)
Parâmetros definidos na rota, como /users/{id}, ficam em atributos do request.
$app->get('/users/{id}', function (Request $request, Response $response, array $args): Response { $id = (int)$args['id']; // ... return $response;});Em middlewares/handlers genéricos, também é comum acessar via atributos:
$id = $request->getAttribute('id');4) Cookies
Cookies chegam no request e podem ser lidos com:
$cookies = $request->getCookieParams();$sessionId = $cookies['session_id'] ?? null;Observação: cookies são dados do cliente, portanto trate como não confiáveis (validação/assinatura quando aplicável).
5) Body: JSON
Para APIs, o mais comum é Content-Type: application/json. Em PSR-7, o body é um stream. Você pode ler o conteúdo e decodificar.
$rawBody = (string)$request->getBody();$data = json_decode($rawBody, true);if (!is_array($data)) { $data = [];}Passo a passo seguro para JSON:
- Verifique
Content-Type(ou aceite variações comoapplication/json; charset=utf-8). - Leia o stream com
(string)$request->getBody(). - Faça
json_decodecomtruepara array. - Valide erro de parsing quando necessário.
$contentType = $request->getHeaderLine('Content-Type');$isJson = str_contains(strtolower($contentType), 'application/json');if ($isJson) { $rawBody = (string)$request->getBody(); $data = json_decode($rawBody, true); if (json_last_error() !== JSON_ERROR_NONE) { // trate como 400 Bad Request em uma resposta padronizada (ver adiante) }}6) Body: form-urlencoded e multipart
Quando o cliente envia formulário (application/x-www-form-urlencoded) ou multipart, o Slim/PSR-7 expõe os dados via getParsedBody().
$parsed = $request->getParsedBody();$email = is_array($parsed) ? ($parsed['email'] ?? null) : null;Importante: getParsedBody() pode retornar null, array ou objeto, dependendo do parser e do conteúdo.
7) Upload de arquivos
Arquivos enviados em multipart ficam em getUploadedFiles(), retornando objetos UploadedFileInterface.
$files = $request->getUploadedFiles();$avatar = $files['avatar'] ?? null;if ($avatar) { $originalName = $avatar->getClientFilename(); $mediaType = $avatar->getClientMediaType(); $size = $avatar->getSize(); // mover para destino final (exemplo) $avatar->moveTo(__DIR__ . '/uploads/' . basename($originalName));}Passo a passo recomendado para upload:
- Verifique se o índice existe.
- Valide tamanho e tipo (não confie apenas no
getClientMediaType()). - Gere nome seguro (evite usar nome original diretamente).
- Use
moveTopara persistir.
Construindo Responses PSR-7 corretamente
Responses também são imutáveis: métodos como withHeader, withStatus retornam uma nova instância. Para escrever no body, use o stream retornado por getBody().
1) Resposta texto simples
$response->getBody()->write('OK');return $response ->withHeader('Content-Type', 'text/plain; charset=utf-8') ->withStatus(200);2) Resposta JSON com status e headers
Um padrão comum é sempre retornar JSON com Content-Type correto e encoding UTF-8.
$payload = ['status' => 'success', 'data' => ['id' => 10]];$json = json_encode($payload, JSON_UNESCAPED_UNICODE);$response->getBody()->write($json);return $response ->withHeader('Content-Type', 'application/json; charset=utf-8') ->withStatus(201);3) Headers adicionais (ex.: Location, Cache-Control)
$response->getBody()->write($json);return $response ->withHeader('Content-Type', 'application/json; charset=utf-8') ->withHeader('Location', '/users/10') ->withHeader('Cache-Control', 'no-store') ->withStatus(201);4) Cookies na resposta
Em PSR-7, cookies de resposta são enviados via header Set-Cookie. Você pode adicionar um ou mais headers Set-Cookie.
$cookie = 'session_id=abc123; Path=/; HttpOnly; SameSite=Lax';return $response ->withAddedHeader('Set-Cookie', $cookie) ->withHeader('Content-Type', 'text/plain; charset=utf-8') ->withStatus(200);PSR-15: middlewares e a cadeia de execução
Middlewares PSR-15 recebem um ServerRequestInterface e um RequestHandlerInterface. Eles podem:
- Interromper e retornar uma resposta imediatamente (ex.: autenticação falhou).
- Delegar para o próximo com
$handler->handle($request)e então modificar a resposta. - Enriquecer o request com atributos (ex.: usuário autenticado, trace id).
Exemplo: middleware para adicionar Trace ID e expor no response
use Psr\Http\Message\ServerRequestInterface;use Psr\Http\Server\MiddlewareInterface;use Psr\Http\Server\RequestHandlerInterface;use Psr\Http\Message\ResponseInterface;final class TraceIdMiddleware implements MiddlewareInterface{ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $traceId = $request->getHeaderLine('X-Trace-Id'); if ($traceId === '') { $traceId = bin2hex(random_bytes(16)); } $request = $request->withAttribute('trace_id', $traceId); $response = $handler->handle($request); return $response->withHeader('X-Trace-Id', $traceId); }}Uso: registre o middleware no app ou em um grupo de rotas (a forma exata de registro depende da estrutura do seu projeto).
Padrão de resposta JSON consistente
Para evitar respostas “soltas” e inconsistentes, defina um envelope padrão. Um formato prático:
| Campo | Descrição |
|---|---|
success | boolean indicando sucesso |
data | payload em caso de sucesso |
error | objeto com detalhes do erro (quando success=false) |
meta | metadados (paginação, trace id, etc.) |
Helper para responder JSON
Você pode criar uma função/helper reutilizável (ou um serviço) para garantir Content-Type, encoding e status code.
use Psr\Http\Message\ResponseInterface as Response;function jsonResponse(Response $response, array $payload, int $status = 200, array $headers = []): Response{ $json = json_encode($payload, JSON_UNESCAPED_UNICODE); $response->getBody()->write($json); $response = $response->withHeader('Content-Type', 'application/json; charset=utf-8') ->withStatus($status); foreach ($headers as $name => $value) { $response = $response->withHeader($name, $value); } return $response;}Exemplo de sucesso
$payload = [ 'success' => true, 'data' => ['id' => 10, 'name' => 'Ana'], 'meta' => ['trace_id' => $request->getAttribute('trace_id')],];return jsonResponse($response, $payload, 200);Exemplo de erro padronizado (400)
$payload = [ 'success' => false, 'data' => null, 'error' => [ 'code' => 'invalid_json', 'message' => 'Corpo JSON inválido.', ], 'meta' => ['trace_id' => $request->getAttribute('trace_id')],];return jsonResponse($response, $payload, 400);Cuidados com encoding e JSON
- Use
application/json; charset=utf-8. - Considere
JSON_UNESCAPED_UNICODEpara legibilidade. - Em erros de serialização, retorne um erro controlado (ex.: 500) e registre detalhes internamente.
Métodos HTTP, idempotência e uso correto no Slim
Semântica essencial dos métodos
- GET: leitura. Deve ser seguro (não alterar estado) e idempotente.
- HEAD: como GET, mas sem body (útil para checar existência/tamanho).
- POST: criação/ação não idempotente (em geral). Pode criar recursos ou disparar processos.
- PUT: substituição completa de um recurso em uma URI conhecida. Idempotente.
- PATCH: atualização parcial. Pode ser idempotente se definido assim pelo servidor, mas não é garantido por padrão.
- DELETE: remoção. Idempotente (repetir tende a manter o mesmo estado final).
Como isso afeta rotas no Slim
Defina rotas com o método correto para que caches, proxies e clientes se comportem bem.
$app->get('/users/{id}', ...);$app->post('/users', ...);$app->put('/users/{id}', ...);$app->patch('/users/{id}', ...);$app->delete('/users/{id}', ...);Idempotência na prática: evitando duplicidade em POST
Quando um cliente pode reenviar um POST por timeout/retry, você pode suportar idempotência via um header como Idempotency-Key. A ideia: se a mesma chave for reutilizada, o servidor retorna a mesma resposta (ou o mesmo resultado) sem criar duplicatas.
Passo a passo (visão prática):
- Cliente envia
POST /paymentscom headerIdempotency-Key. - Servidor calcula um “fingerprint” do request (opcional) e salva o resultado associado à chave.
- Se a chave já existir, retorne a resposta previamente armazenada (status, headers e body) em vez de processar novamente.
$app->post('/payments', function (Request $request, Response $response): Response { $key = $request->getHeaderLine('Idempotency-Key'); if ($key === '') { return jsonResponse($response, [ 'success' => false, 'data' => null, 'error' => ['code' => 'missing_idempotency_key', 'message' => 'Header Idempotency-Key é obrigatório.'], ], 400); } // Pseudocódigo: consultar armazenamento de idempotência // $stored = $idemStore->find($key); // if ($stored) return replay($response, $stored); $body = (string)$request->getBody(); $data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { return jsonResponse($response, [ 'success' => false, 'data' => null, 'error' => ['code' => 'invalid_json', 'message' => 'Corpo JSON inválido.'], ], 400); } // Pseudocódigo: processar pagamento e gerar resultado $result = ['payment_id' => 'pay_123', 'status' => 'authorized']; $payload = ['success' => true, 'data' => $result]; $resp = jsonResponse($response, $payload, 201); // Pseudocódigo: salvar resposta para replay // $idemStore->save($key, $resp); return $resp;});Observações importantes:
- Idempotência não é “mágica”: requer armazenamento (banco/redis) e política de expiração.
- Para PUT, a idempotência já é esperada pela semântica do método, desde que a URI identifique o recurso.
Tratando Content Negotiation e respostas coerentes
Mesmo que sua API sempre responda JSON, é útil validar Accept e responder de forma previsível.
$accept = $request->getHeaderLine('Accept');if ($accept !== '' && !str_contains($accept, 'application/json') && !str_contains($accept, '*/*')) { return jsonResponse($response, [ 'success' => false, 'data' => null, 'error' => ['code' => 'not_acceptable', 'message' => 'Este endpoint responde apenas application/json.'], ], 406);}Checklist prático para handlers Slim (Request in, Response out)
- Leia dados do request usando PSR-7: headers, query, atributos, cookies, parsed body e uploaded files.
- Converta tipos e valide entradas; não confie em dados do cliente.
- Construa responses imutáveis:
withStatus,withHeaderewriteno body. - Padronize JSON:
Content-Type, UTF-8, envelope consistente e mensagens de erro previsíveis. - Use métodos HTTP corretamente e implemente idempotência quando o domínio exigir (especialmente em POST crítico).