Ciclo HTTP no Slim Framework: Request, Response e contratos PSR

Capítulo 2

Tempo estimado de leitura: 9 minutos

+ Exercício

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.

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

$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 como application/json; charset=utf-8).
  • Leia o stream com (string)$request->getBody().
  • Faça json_decode com true para 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 moveTo para 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:

CampoDescrição
successboolean indicando sucesso
datapayload em caso de sucesso
errorobjeto com detalhes do erro (quando success=false)
metametadados (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_UNICODE para 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 /payments com header Idempotency-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, withHeader e write no 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).

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

Ao construir uma resposta JSON no Slim seguindo PSR-7, qual prática está mais alinhada com a imutabilidade do Response e com um retorno consistente para a API?

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

Você errou! Tente novamente.

Em PSR-7, o Response é imutável: withHeader e withStatus retornam uma nova instância. Para JSON consistente, escreva o JSON no body e retorne a resposta com Content-Type correto e status adequado.

Próximo capitúlo

Rotas no Slim Framework: definição, agrupamento e organização por domínio

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

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.