Construye una API JSON rigurosa dominando las cabeceras, la lectura del cuerpo en bruto y la semántica precisa de los códigos de estado.
Abrir esta lección en KodokonUna API JSON es un contrato: cada respuesta anuncia su tipo (Content-Type: application/json), lleva el código de estado correcto y una estructura estable. PHP no envía nada de esto por defecto - declara text/html. Primer bloque de construcción: un único helper de respuesta, tipado como never (PHP 8.1+). Este tipo de retorno garantiza, tanto al motor como a las herramientas de análisis estático, que la función nunca devuelve el control: cualquier código escrito después de una llamada es demostrablemente inalcanzable.
<?php
declare(strict_types=1);
function jsonResponse(
mixed $data,
int $status = 200
): never {
http_response_code($status);
header('Content-Type: application/json');
echo json_encode(
$data,
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE
);
exit;
}
jsonResponse(['status' => 'ok']);Una segunda sutileza, descubierta a menudo demasiado tarde: $_POST permanece vacío ante un cuerpo JSON. PHP solo lo rellena para application/x-www-form-urlencoded y multipart/form-data. El cuerpo en bruto se lee desde el flujo php://input. Decodifícalo con JSON_THROW_ON_ERROR: sin ese flag, json_decode devuelve null silenciosamente en caso de error - indistinguible de un cuerpo que contiene literalmente null. Y gracias al tipo never, el switch de abajo no necesita ningún break.
<?php
declare(strict_types=1);
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
jsonResponse(['items' => []]);
case 'POST':
$raw = (string) file_get_contents(
'php://input'
);
try {
$payload = json_decode(
$raw,
true,
512,
JSON_THROW_ON_ERROR
);
} catch (JsonException) {
jsonResponse(
['error' => 'Malformed JSON'],
400
);
}
header('Location: /items/42');
jsonResponse(['created' => $payload], 201);
default:
header('Allow: GET, POST');
jsonResponse(
['error' => 'Method not allowed'],
405
);
}Elige los códigos con precisión, porque tus clientes programarán contra ellos, no contra tus mensajes: 200 lectura exitosa, 201 creación (acompañada de una cabecera Location que apunta al recurso), 204 éxito sin cuerpo, 400 JSON mal formado, 404 recurso inexistente, 405 método no gestionado (con una cabecera Allow que enumera los permitidos), y 422 para un JSON sintácticamente válido pero semánticamente incorrecto. Compruébalo todo con curl -i, que muestra las cabeceras recibidas.
curl -i -X POST http://localhost:8000/items \
-H 'Content-Type: application/json' \
-d '{"name":"Widget"}'