ヘッダー、生のボディの読み取り、そしてステータスコードのきめ細かな意味論を使いこなして、厳密な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']);二つ目の微妙な点は、しばしば遅すぎるタイミングで気づかれます。$_POSTはJSONボディに対しては空のままです。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"}'