REST API стал стандартом де-факто для обмена данными между приложениями. Если вы пишете на PHP и хотите создать свой первый или улучшить существующий REST-сервис, эта статья для вас. Я поделюсь практическим опытом, покажу код и расскажу, как избежать типичных граблей.

Digital-студия WNDER

Что такое REST и с чем его едят

REST (Representational State Transfer) — это архитектурный стиль, а не протокол. Простыми словами, это набор правил, как клиент и сервер общаются через HTTP. Каждый URL — это ресурс. Действия с ресурсом определяются HTTP-методами: GET — получить, POST — создать, PUT — обновить, DELETE — удалить. Всё просто, как в жизни: пришёл в магазин (URL), взял товар (GET), положил в корзину (POST), изменил количество (PUT), выкинул (DELETE).

Главное преимущество REST — единообразие. Клиент точно знает, как запросить данные, а сервер знает, как ответить. Формат данных обычно JSON — легко читается и парсится.

Скелет REST-сервиса на чистом PHP

Многие сразу тянут фреймворки вроде Laravel или Slim. Но чтобы понять суть, лучше начать с чистого PHP. Создадим точку входа — index.php. Она будет принимать все запросы и направлять их в нужный обработчик.

<?php
// index.php

// Разрешаем CORS (если нужно)
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");
header("Access-Control-Allow-Headers: Content-Type, Authorization");

// Получаем метод и путь
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = rtrim($path, '/');

// Простейшая маршрутизация
switch (true) {
    case $method === 'GET' && $path === '/api/users':
        getUsers();
        break;
    case $method === 'GET' && preg_match('#^/api/users/(\d+)$#', $path, $matches):
        getUser($matches[1]);
        break;
    case $method === 'POST' && $path === '/api/users':
        createUser();
        break;
    case $method === 'PUT' && preg_match('#^/api/users/(\d+)$#', $path, $matches):
        updateUser($matches[1]);
        break;
    case $method === 'DELETE' && preg_match('#^/api/users/(\d+)$#', $path, $matches):
        deleteUser($matches[1]);
        break;
    default:
        http_response_code(404);
        echo json_encode(['error' => 'Not Found']);
}

Это минимальный роутинг. В реальном проекте лучше вынести его в отдельный класс, но для старта сойдёт.

Обработка запросов и ответов

Теперь напишем функции-обработчики. Данные будем хранить в массиве (для примера), в реальности — база данных. Важно правильно читать тело запроса для POST и PUT.

function getUsers() {
    // Здесь мог бы быть запрос к БД
    $users = [
        ['id' => 1, 'name' => 'Алиса'],
        ['id' => 2, 'name' => 'Боб']
    ];
    echo json_encode($users);
}

function getUser($id) {
    // Эмуляция поиска
    $users = [
        1 => ['id' => 1, 'name' => 'Алиса'],
        2 => ['id' => 2, 'name' => 'Боб']
    ];
    if (isset($users[$id])) {
        echo json_encode($users[$id]);
    } else {
        http_response_code(404);
        echo json_encode(['error' => 'User not found']);
    }
}

function createUser() {
    $input = json_decode(file_get_contents('php://input'), true);
    if (!isset($input['name'])) {
        http_response_code(400);
        echo json_encode(['error' => 'Name is required']);
        return;
    }
    // Логика сохранения (например, в БД)
    $newUser = ['id' => 3, 'name' => $input['name']];
    http_response_code(201);
    echo json_encode($newUser);
}

function updateUser($id) {
    $input = json_decode(file_get_contents('php://input'), true);
    // Проверяем существование и обновляем
    echo json_encode(['id' => $id, 'name' => $input['name'] ?? 'Обновлено']);
}

function deleteUser($id) {
    // Удаляем
    http_response_code(204); // No Content
}

Обратите внимание на коды ответов: 200 — успех, 201 — создано, 204 — удалено, 400 — неверный запрос, 404 — не найдено, 500 — ошибка сервера. Это важно для клиента.

Правило: Всегда проверяйте входные данные. Даже если кажется, что клиент пришлёт правильный JSON, не ленитесь делать json_decode с проверкой на null. Иначе получите фатальную ошибку и белый экран.

Продвинутая маршрутизация и автозагрузка

Когда маршрутов становится много, switch превращается в кашу. Лучше использовать регулярные выражения и вынести всё в отдельный класс. Также подключим автозагрузку через Composer (если ещё не используете — пора начать).

Пример простого роутера с поддержкой параметров:

class Router {
    private $routes = [];

    public function add($method, $pattern, $callback) {
        $this->routes[] = compact('method', 'pattern', 'callback');
    }

    public function dispatch($method, $uri) {
        foreach ($this->routes as $route) {
            if ($route['method'] !== $method) continue;
            $pattern = '#^' . $route['pattern'] . '$#';
            if (preg_match($pattern, $uri, $matches)) {
                array_shift($matches);
                return call_user_func_array($route['callback'], $matches);
            }
        }
        http_response_code(404);
        echo json_encode(['error' => 'Not Found']);
    }
}

// Использование
$router = new Router();
$router->add('GET', '/api/users', 'getUsers');
$router->add('GET', '/api/users/(\d+)', 'getUser');
$router->add('POST', '/api/users', 'createUser');
$router->dispatch($_SERVER['REQUEST_METHOD'], $path);

Такой подход гораздо гибче. Можно легко добавлять новые маршруты, не трогая логику.

Аутентификация и безопасность

REST API часто требует авторизации. Самый простой способ — токены. Клиент передаёт токен в заголовке Authorization: Bearer <token>. Сервер проверяет его валидность. Вот пример проверки:

function authenticate() {
    $headers = getallheaders();
    if (!isset($headers['Authorization'])) {
        http_response_code(401);
        echo json_encode(['error' => 'Unauthorized']);
        exit;
    }
    $token = str_replace('Bearer ', '', $headers['Authorization']);
    // Проверяем токен в БД или расшифровываем JWT
    if ($token !== 'secret-token') {
        http_response_code(403);
        echo json_encode(['error' => 'Forbidden']);
        exit;
    }
}

// Вызываем перед обработкой защищённых маршрутов
authenticate();

Не забывайте про SQL-инъекции, XSS и другие атаки. Используйте подготовленные запросы (prepared statements) при работе с БД. Никогда не доверяйте данным от клиента.

Лайфхак: Для быстрой отладки API используйте инструменты вроде Postman или curl. Они позволяют отправлять запросы с разными методами и заголовками, а также сохранять коллекции запросов.

Сравнение подходов: чистый PHP vs фреймворки

Когда стоит писать на чистом PHP, а когда брать фреймворк? Вот таблица для сравнения.

Критерий Чистый PHP Фреймворк (Laravel, Slim)
Скорость разработки Низкая Высокая
Гибкость Полная Ограничена правилами
Порог входа Низкий Средний/высокий
Производительность Высокая Средняя
Безопасность Всё на ваших плечах Многое из коробки

Для маленьких проектов или обучения чистый PHP — отличный выбор. Для крупных систем лучше взять фреймворк, чтобы не изобретать велосипед.

Что в итоге

Создать RESTful сервис на PHP — задача не rocket science. Главное — понять принципы: ресурсы, методы, коды ответов. Начните с простого роутинга, постепенно добавляйте функциональность. Не забывайте про безопасность и валидацию. И помните: даже самый навороченный API строится из простых кусочков.

Практикуйтесь, пишите код, экспериментируйте. И всё получится.

Студия WNDER