Небольшой, но полный API
Пора собрать всё вместе: API пользователей с пятью классическими конечными точками —
POST /users создать · GET /users список · GET /users/{id} прочитать одного · PUT /users/{id} заменить · DELETE /users/{id} удалить.
Одно решение, продиктованное природой PHP: каждый запрос запускает скрипт-маршрутизатор заново, поэтому данные не могут жить в переменной между запросами. Настоящие API хранят состояние в базе данных; здесь эту роль играет небольшой JSON-файл — маршрутизатор загружает его, изменяет и записывает обратно. Обработчики сохранили бы свою форму, если бы файл заменили настоящей базой данных.
Создание и список
POST декодирует пришедший JSON, присваивает пользователю id, сохраняет его и отвечает 201 с созданной записью — вместе с id, чтобы клиент его узнал. GET возвращает всех в виде JSON-массива.
POST /users и GET /users
Два создания, затем список: каждый POST отвечает 201 с сохранённым пользователем, а последний GET показывает обоих — состояние API росло с каждым запросом.
Вывод: PHP-скрипт
<?php
// Хранилище живёт в JSON-файле — каждый запрос запускает скрипт заново.
$router = <<<'PHP'
<?php
$storeFile = sys_get_temp_dir() . "/pc-create-and-list-store.json";
$users = json_decode(file_get_contents($storeFile), true);
$method = $_SERVER["REQUEST_METHOD"];
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
header("Content-Type: application/json");
if ($method === "POST" && $path === "/users") {
$input = json_decode(file_get_contents("php://input"), true);
if (!is_array($input) || !isset($input["name"])) {
http_response_code(400);
echo '{"error": "invalid JSON"}' . "\n";
return;
}
$user = ["id" => count($users) + 1, "name" => $input["name"]];
$users[] = $user;
file_put_contents($storeFile, json_encode($users));
http_response_code(201);
echo json_encode($user) . "\n";
} elseif ($method === "GET" && $path === "/users") {
echo json_encode($users) . "\n";
} 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];
}
$storeFile = sys_get_temp_dir() . "/pc-create-and-list-store.json";
file_put_contents($storeFile, "[]"); // начинаем с пустого
$routerFile = sys_get_temp_dir() . "/pc-create-and-list.php";
file_put_contents($routerFile, $router);
// Свободный порт, затем встроенный веб-сервер PHP как дочерний процесс.
$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 (["Ana", "Ben"] as $name) {
[$status, $body] = httpRequest("$base/users", "POST",
["Content-Type: application/json"], json_encode(["name" => $name]));
echo "POST /users -> $status " . trim($body) . "\n";
}
[$status, $body] = httpRequest("$base/users");
echo "GET /users -> $status " . trim($body) . "\n";
proc_terminate($server);
proc_close($server);
unlink($routerFile);
unlink($storeFile);
Прочитать одного, заменить, удалить
Маршруты /users/{id} дополняют набор: чтение отвечает 200 или 404, замена декодирует новые данные в существующий id, удаление отвечает 204. После удаления тот же GET, который работал раньше, честно возвращает 404 — ресурса больше нет.
GET, PUT и DELETE для /users/{id}
Вся жизнь одного ресурса: прочитать его, заменить (в ответе приходит новое имя), удалить и не суметь прочитать — каждый шаг с корректным кодом состояния.
Вывод: PHP-скрипт
<?php
// GET, PUT и DELETE для одного ресурса поверх хранилища в JSON-файле.
$router = <<<'PHP'
<?php
$storeFile = sys_get_temp_dir() . "/pc-one-user-lifecycle-store.json";
$users = json_decode(file_get_contents($storeFile), true);
$method = $_SERVER["REQUEST_METHOD"];
$segments = explode("/", trim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), "/"));
$id = $segments[1] ?? "";
if ($method === "GET") {
header("Content-Type: application/json");
if (!isset($users[$id])) {
http_response_code(404);
echo '{"error": "user not found"}' . "\n";
return;
}
echo json_encode($users[$id]) . "\n";
} elseif ($method === "PUT") {
$input = json_decode(file_get_contents("php://input"), true);
if (!is_array($input) || !isset($input["name"])) {
http_response_code(400);
echo '{"error": "invalid JSON"}' . "\n";
return;
}
$users[$id] = ["id" => (int) $id, "name" => $input["name"]];
file_put_contents($storeFile, json_encode($users));
header("Content-Type: application/json");
echo json_encode($users[$id]) . "\n";
} elseif ($method === "DELETE") {
unset($users[$id]);
file_put_contents($storeFile, json_encode($users));
http_response_code(204);
}
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];
}
$storeFile = sys_get_temp_dir() . "/pc-one-user-lifecycle-store.json";
file_put_contents($storeFile, '{"1": {"id": 1, "name": "Ana"}}');
$routerFile = sys_get_temp_dir() . "/pc-one-user-lifecycle.php";
file_put_contents($routerFile, $router);
// Свободный порт, затем встроенный веб-сервер PHP как дочерний процесс.
$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";
$steps = [
["GET", "/users/1", null],
["PUT", "/users/1", '{"name": "Ana Maria"}'],
["DELETE", "/users/1", null],
["GET", "/users/1", null],
];
foreach ($steps as [$method, $path, $payload]) {
$requestHeaders = $payload === null ? [] : ["Content-Type: application/json"];
[$status, $body] = httpRequest("$base$path", $method, $requestHeaders, $payload);
printf("%-6s %s -> %d %s\n", $method, $path, $status, trim($body));
}
proc_terminate($server);
proc_close($server);
unlink($routerFile);
unlink($storeFile);