Сервер запускает ваш скрипт
В PHP обслуживание веб-запросов устроено наоборот по сравнению с большинством языков: вы не пишете сервер, который работает бесконечно, — веб-сервер сам запускает ваш скрипт, по одному разу на каждый запрос. Скрипт и есть обработчик: суперглобальные переменные вроде $_SERVER и $_GET хранят запрос, а header() вместе с echo формируют ответ.
В состав PHP входит собственный веб-сервер для разработки: php -S host:port router.php отвечает на каждый запрос, запуская этот единственный скрипт. Примеры здесь действительно его запускают: каждый из них записывает свой скрипт-обработчик во временный файл, стартует php -S на свободном порту как дочерний процесс, отправляет ему настоящие HTTP-запросы, печатает ответы и останавливает сервер — так что каждый пример выполняется и завершается сам.
Первый обработчик
Самый маленький возможный скрипт-обработчик выводит одну строку текста. Всё, что делает API, — JSON, коды состояния, маршрутизация — происходит внутри той же схемы: запрос на входе, echo на выходе.
Приветствие от обработчика
Скрипт-обработчик выводит одну строку; клиент читает именно её как тело ответа, а статус 200 OK подставляется автоматически.
Вывод: PHP-скрипт
<?php
// Скрипт-обработчик: веб-сервер запускает этот файл один раз на каждый запрос.
$router = <<<'PHP'
<?php
header("Content-Type: text/plain");
echo "привет от сервера\n";
PHP;
$routerFile = sys_get_temp_dir() . "/pc-first-handler.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);
$context = stream_context_create(["http" => ["ignore_errors" => true]]);
$body = file_get_contents("http://127.0.0.1:$port/", false, $context);
[, $status] = explode(" ", $http_response_header[0], 2);
echo "статус: $status\n";
echo "тело: $body";
proc_terminate($server);
proc_close($server);
unlink($routerFile);
Ответ в формате JSON
Обработчик API делает три вещи: устанавливает заголовок Content-Type, выбирает код состояния и кодирует данные — сначала header(), затем echo json_encode($value).
Порядок важен: заголовки должны быть отправлены до первого байта тела.
JSON-эндпоинт
Обработчик кодирует массив прямо в ответ. Клиент получает корректный JSON и правильный тип содержимого — это скелет каждого эндпоинта в остальной части этой группы.
Вывод: PHP-скрипт
<?php
// Обработчик API: сначала заголовок, затем закодированное тело.
$router = <<<'PHP'
<?php
header("Content-Type: application/json");
echo json_encode(["id" => 1, "name" => "Ana"]) . "\n";
PHP;
$routerFile = sys_get_temp_dir() . "/pc-json-handler.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);
$context = stream_context_create(["http" => ["ignore_errors" => true]]);
$body = file_get_contents("http://127.0.0.1:$port/user", false, $context);
[, $status] = explode(" ", $http_response_header[0], 2);
$type = "";
foreach ($http_response_header as $headerLine) {
if (stripos($headerLine, "Content-Type:") === 0) {
$type = trim(substr($headerLine, 13));
}
}
echo "статус: $status\n";
echo "тип: $type\n";
echo "тело: $body";
proc_terminate($server);
proc_close($server);
unlink($routerFile);