Собери строгий JSON API, освоив заголовки, чтение сырого тела запроса и тонкую семантику кодов состояния.
Открыть этот урок в KodokonJSON API - это контракт: каждый ответ объявляет свой тип (Content-Type: application/json), несёт правильный код состояния и стабильную структуру. По умолчанию PHP не отправляет ничего из этого - он объявляет text/html. Первый кирпичик: единственная вспомогательная функция ответа с типом never (PHP 8.1+). Этот возвращаемый тип гарантирует и движку, и инструментам статического анализа, что функция никогда не возвращает управление: любой код, написанный после вызова, заведомо недостижим.
<?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']);Вторая тонкость, о которой узнают слишком поздно: перед телом в формате JSON $_POST остаётся пустым. PHP заполняет его только для application/x-www-form-urlencoded и multipart/form-data. Сырое тело читается из потока php://input. Декодируй его с флагом JSON_THROW_ON_ERROR: без него json_decode при ошибке молча возвращает null - и это не отличить от тела, которое буквально содержит null. А благодаря типу never в switch ниже вообще не нужны 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
);
}Выбирай коды точно, потому что твои клиенты будут программировать под них, а не под твои сообщения: 200 - успешное чтение, 201 - создание (вместе с заголовком Location, указывающим на ресурс), 204 - успех без тела, 400 - некорректный JSON, 404 - ресурс не найден, 405 - метод не поддерживается (с заголовком Allow, перечисляющим разрешённые), и 422 - для JSON, синтаксически валидного, но семантически неверного. Проверь всё через curl -i, который показывает полученные заголовки.
curl -i -X POST http://localhost:8000/items \
-H 'Content-Type: application/json' \
-d '{"name":"Widget"}'