Kodokon kodokon.com

Eine JSON-API in PHP bauen

Baue eine saubere JSON-API, indem du die Header, das Lesen des rohen Bodys und die feine Semantik der Statuscodes beherrschst.

9 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Eine JSON-API ist ein Vertrag: Jede Antwort kündigt ihren Typ an (Content-Type: application/json), trägt den richtigen Statuscode und eine stabile Struktur. PHP verschickt davon standardmäßig nichts - es deklariert text/html. Erster Baustein: ein einziger Antwort-Helper, typisiert mit never (PHP 8.1+). Dieser Rückgabetyp garantiert der Engine wie auch den Werkzeugen der statischen Analyse, dass die Funktion die Kontrolle niemals zurückgibt: Jeder Code, der nach einem Aufruf steht, ist nachweislich unerreichbar.

PHP
<?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']);
Ein einziger Ausgang: Status, Header und Body immer stimmig.

Eine zweite Feinheit, die oft zu spät entdeckt wird: $_POST bleibt bei einem JSON-Body leer. PHP füllt es nur für application/x-www-form-urlencoded und multipart/form-data. Der rohe Body wird aus dem Stream php://input gelesen. Dekodiere ihn mit JSON_THROW_ON_ERROR: Ohne dieses Flag gibt json_decode bei einem Fehler stillschweigend null zurück - nicht unterscheidbar von einem Body, der buchstäblich null enthält. Und dank des Typs never braucht das folgende switch überhaupt kein break.

PHP
<?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
        );
}
Setzt das oben definierte jsonResponse voraus; das catch ohne Variable ist PHP 8.

Wähle die Codes genau, denn deine Clients programmieren gegen sie und nicht gegen deine Meldungen: 200 erfolgreiches Lesen, 201 Erstellung (begleitet von einem Location-Header, der auf die Ressource zeigt), 204 Erfolg ohne Body, 400 fehlerhaftes JSON, 404 fehlende Ressource, 405 nicht behandelte Methode (mit einem Allow-Header, der die erlaubten auflistet), und 422 für JSON, das syntaktisch gültig, aber semantisch falsch ist. Prüfe das alles mit curl -i, das die empfangenen Header anzeigt.

BASH
curl -i -X POST http://localhost:8000/items \
  -H 'Content-Type: application/json' \
  -d '{"name":"Widget"}'
Die Option -i zeigt Status und Header: der einzige echte Test des Vertrags.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Deine API empfängt ein POST mit einem JSON-Body: Warum ist $_POST leer?
    • PHP füllt $_POST nur bei form-urlencoded- und multipart-Bodys; ein JSON-Body wird aus php://input gelesen
    • Das JSON überschreitet das Standardlimit post_max_size
    • Du musst zuerst json_decode auf $_POST aufrufen, um es zu füllen
  2. Welchen Statuscode gibst du nach einer erfolgreichen Erstellung per POST zurück?
    • 200
    • 201
    • 204
    • 302
  3. Was garantiert der Rückgabetyp never?
    • Die Funktion gibt immer null zurück
    • Die Funktion gibt die Kontrolle nie zurück: Sie endet mit exit oder einer Exception, und die Engine überprüft das
    • Die Funktion kann von einer Kindklasse nicht überschrieben werden