通过掌握响应头、读取原始请求体以及状态码的细致语义,构建一个严谨的 JSON API。
在 Kodokon 中打开本课一个 JSON 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"}'