Spune exact ce vrei
Un API REST vorbește prin două vocabulare. Metoda spune ce vrea clientul: GET citește, POST creează, PUT înlocuiește, PATCH schimbă o parte, DELETE șterge. Codul de stare spune cum a ieșit: 2xx succes, 4xx greșeala clientului, 5xx a serverului.
Un detaliu care merită știut: PUT este idempotent — trimis de două ori, se termină la fel ca o singură dată. POST nu este: două cereri POST creează două resurse.
Metoda potrivită
Routerul citește verbul din $_SERVER["REQUEST_METHOD"] și trimite fiecare pe ramura lui. Crearea răspunde cu 201 Created și indică noua resursă printr-un antet Location; ștergerea răspunde cu 204 No Content — succes fără nimic de spus.
GET, POST și DELETE pe aceeași resursă
Trei verbe, trei răspunsuri: lista vine cu 200, crearea cu 201 și un antet Location, iar ștergerea cu 204 și un corp gol.
Ieșire: script PHP
<?php
// O ramură pentru fiecare verb: metoda face parte din rută.
$router = <<<'PHP'
<?php
$method = $_SERVER["REQUEST_METHOD"];
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$segments = explode("/", trim($path, "/"));
if ($method === "GET" && $path === "/notes") {
echo "notes: shopping, ideas\n";
} elseif ($method === "POST" && $path === "/notes") {
header("Location: /notes/3");
http_response_code(201);
echo "created\n";
} elseif ($method === "DELETE" && count($segments) === 2 && $segments[0] === "notes") {
http_response_code(204);
} else {
http_response_code(404);
}
PHP;
function httpRequest(string $url, string $method = "GET", array $headers = [], ?string $body = null): array {
$options = ["method" => $method, "ignore_errors" => true];
if ($headers !== []) {
$options["header"] = implode("\r\n", $headers);
}
if ($body !== null) {
$options["content"] = $body;
}
$responseBody = file_get_contents($url, false, stream_context_create(["http" => $options]));
return [(int) explode(" ", $http_response_header[0])[1], $responseBody, $http_response_header];
}
$routerFile = sys_get_temp_dir() . "/pc-methods.php";
file_put_contents($routerFile, $router);
// Un port liber, apoi serverul web încorporat al PHP ca proces-copil.
$probe = stream_socket_server("tcp://127.0.0.1:0");
$port = (int) explode(":", stream_socket_get_name($probe, false))[1];
fclose($probe);
$server = proc_open(["php", "-S", "127.0.0.1:$port", $routerFile],
[1 => ["file", "/dev/null", "w"], 2 => ["file", "/dev/null", "w"]], $pipes);
do {
usleep(20000);
$up = @fsockopen("127.0.0.1", $port);
} while ($up === false);
fclose($up);
$base = "http://127.0.0.1:$port";
[$status, $body] = httpRequest("$base/notes");
echo "GET /notes -> $status " . trim($body) . "\n";
[$status, , $headers] = httpRequest("$base/notes", "POST",
["Content-Type: application/json"], '{"text": "call Ben"}');
$location = "";
foreach ($headers as $headerLine) {
if (stripos($headerLine, "Location:") === 0) {
$location = trim(substr($headerLine, 9));
}
}
echo "POST /notes -> $status Location: $location\n";
[$status] = httpRequest("$base/notes/3", "DELETE");
echo "DELETE /notes/3 -> $status (fără conținut)\n";
proc_terminate($server);
proc_close($server);
unlink($routerFile);
Codul de stare potrivit
Și erorile merită precizie: o resursă inexistentă este 404 Not Found (cu un corp de eroare JSON, ca să-l poată citi programele), iar un verb greșit pe o cale cunoscută este 405 Method Not Allowed — cu un antet Allow care numește verbele ce ar funcționa. Scriptul routerului răspunde el însuși în ambele cazuri, alegând starea cu http_response_code().
200, 404 și 405
Notița existentă întoarce 200 cu date, cea lipsă 404 cu o eroare JSON, iar cererea DELETE pe o rută doar-GET este refuzată cu 405 și un antet Allow: GET.
Ieșire: script PHP
<?php
// 200 pentru găsit, 404 pentru lipsă, 405 pentru verbul greșit.
$router = <<<'PHP'
<?php
$notes = ["1" => "shopping list"];
$method = $_SERVER["REQUEST_METHOD"];
$segments = explode("/", trim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), "/"));
$id = $segments[1] ?? "";
if ($method !== "GET") {
// Verb greșit pe o cale cunoscută: refuză cererea și spune ce ar funcționa.
header("Allow: GET");
http_response_code(405);
} elseif (!isset($notes[$id])) {
header("Content-Type: application/json");
http_response_code(404);
echo '{"error": "note not found"}' . "\n";
} else {
header("Content-Type: application/json");
echo json_encode(["id" => $id, "text" => $notes[$id]]) . "\n";
}
PHP;
function httpRequest(string $url, string $method = "GET", array $headers = [], ?string $body = null): array {
$options = ["method" => $method, "ignore_errors" => true];
if ($headers !== []) {
$options["header"] = implode("\r\n", $headers);
}
if ($body !== null) {
$options["content"] = $body;
}
$responseBody = file_get_contents($url, false, stream_context_create(["http" => $options]));
return [(int) explode(" ", $http_response_header[0])[1], $responseBody, $http_response_header];
}
$routerFile = sys_get_temp_dir() . "/pc-status-codes.php";
file_put_contents($routerFile, $router);
// Un port liber, apoi serverul web încorporat al PHP ca proces-copil.
$probe = stream_socket_server("tcp://127.0.0.1:0");
$port = (int) explode(":", stream_socket_get_name($probe, false))[1];
fclose($probe);
$server = proc_open(["php", "-S", "127.0.0.1:$port", $routerFile],
[1 => ["file", "/dev/null", "w"], 2 => ["file", "/dev/null", "w"]], $pipes);
do {
usleep(20000);
$up = @fsockopen("127.0.0.1", $port);
} while ($up === false);
fclose($up);
$base = "http://127.0.0.1:$port";
foreach (["/notes/1", "/notes/99"] as $path) {
[$status, $body] = httpRequest("$base$path");
printf("GET %s -> %d %s\n", $path, $status, trim($body));
}
[, , $headers] = httpRequest("$base/notes/1", "DELETE");
[, $statusText] = explode(" ", $headers[0], 2);
$allow = "";
foreach ($headers as $headerLine) {
if (stripos($headerLine, "Allow:") === 0) {
$allow = trim(substr($headerLine, 6));
}
}
echo "DELETE /notes/1 -> $statusText (Allow: $allow)\n";
proc_terminate($server);
proc_close($server);
unlink($routerFile);