API на PHP: от простого к эффективному серверу

Раздел: Веб-разработка -> API разработка

Основные подходы к созданию API на PHP

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

Эффективное решение: микрофреймворк Slim 4

Как организовать REST API с минимальными затратами на настройку и высокой производительностью?

Slim 4 - лёгкий фреймворк, идеально подходящий для создания API. Он предоставляет маршрутизатор, middleware, контейнер зависимостей и удобную обработку ответов.


// Установка через Composer
composer require slim/slim:4.*

// index.php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

// Маршрут GET /api/users
$app->get('/api/users', function (Request $request, Response $response, $args) {
    $users = [
        ['id' => 1, 'name' => 'Иван'],
        ['id' => 2, 'name' => 'Мария']
    ];
    $payload = json_encode($users);
    $response->getBody()->write($payload);
    return $response->withHeader('Content-Type', 'application/json');
});

// Маршрут POST /api/users
$app->post('/api/users', function (Request $request, Response $response, $args) {
    $data = $request->getParsedBody();
    // Валидация и сохранение
    $newUser = ['id' => 3, 'name' => $data['name'] ?? 'Неизвестно'];
    $payload = json_encode($newUser);
    $response->getBody()->write($payload);
    return $response->withHeader('Content-Type', 'application/json')->withStatus(201);
});

// Middleware для CORS
$app->add(function ($request, $handler) {
    $response = $handler->handle($request);
    return $response
        ->withHeader('Access-Control-Allow-Origin', '*')
        ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
        ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
});

$app->run();
    

Php api server (api сервер php)

Типичные ошибки и их решение:

  • Ошибка 404 при обращении к маршруту - проверьте порядок маршрутов и метод HTTP.
  • Ошибка 500 из-за некорректного JSON - убедитесь, что данные корректно декодируются через json_last_error().
  • CORS ошибка в браузере - добавьте middleware для preflight-запросов (OPTIONS).
  • Пустое тело ответа - проверьте, что контент записывается в поток $response->getBody().

Вариант 1: Чистый PHP без фреймворков

Как создать минимальный API сервер на нативном PHP без внешних зависимостей?

Подходит для простых проектов или обучения. Основная сложность - ручная маршрутизация и обработка ошибок.


// public/index.php
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

if ($method === 'GET' && $uri === '/api/users') {
    $users = [['id'=>1,'name'=>'Анна'], ['id'=>2,'name'=>'Петр']];
    echo json_encode($users);
    exit;
}

if ($method === 'POST' && $uri === '/api/users') {
    $data = json_decode(file_get_contents('php://input'), true);
    if ($data === null) {
        http_response_code(400);
        echo json_encode(['error' => 'Неверный JSON']);
        exit;
    }
    // Сохранение...
    echo json_encode(['id'=>3, 'name'=>$data['name']]);
    exit;
}

http_response_code(404);
echo json_encode(['error' => 'Не найден']);
    

Проблемы:

  • Отсутствие автоматической маршрутизации - каждый новый endpoint добавляется вручную.
  • Сложность с middlewares (аутентификация, логирование) - приходится писать вручную.
  • Нет валидации входящих данных - требуется самому проверять типы и форматы.

Вариант 2: Laravel для полнофункционального API

Как построить крупное масштабируемое API с авторизацией и ORM?

Laravel предлагает готовые решения: маршрутизация через routes/api.php, Eloquent ORM, встроенная аутентификация (Sanctum или Passport), валидация и ресурсы.


// routes/api.php
use App\Http\Controllers\UserController;

Route::get('/users', [UserController::class, 'index']);
Route::post('/users', [UserController::class, 'store']);
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
});

// UserController
public function index() {
    return UserResource::collection(User::paginate(10));
}

public function store(Request $request) {
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|email|unique:users'
    ]);
    $user = User::create($validated);
    return new UserResource($user);
}
    

Возможные сложности:

  • Избыточность для простых проектов - Laravel требует больше ресурсов.
  • Необходимость изучения концепций фреймворка (Eloquent, Service Container).
  • Настройка аутентификации для API (выбор Sanctum или Passport).

Вариант 3: Использование Lumen (микрофреймворк от Laravel)

Как получить Laravel-подобный инструмент с меньшим весом?

Lumen - именно микрофреймворк для API, сохраняет часть компонентов Laravel (Eloquent, валидация), но быстрее.


// composer.json: "laravel/lumen-framework": "^10.0"
// Пример маршрута
$router->get('/api/users', function () use ($router) {
    return response()->json(User::all());
});
    

Вариант 4: FastRoute - библиотека маршрутизации без фреймворка

Как добавить только маршрутизацию в проект с чистым PHP?

FastRoute (nikic/FastRoute) поддерживает динамические маршруты, группы и middleware через обёртки.


use FastRoute\RouteCollector;

$dispatcher = FastRoute\simpleDispatcher(function(RouteCollector $r) {
    $r->addRoute('GET', '/api/users', 'get_users');
    $r->addRoute('POST', '/api/users', 'create_user');
});

$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::FOUND:
        $handler = $routeInfo[1];
        $vars = $routeInfo[2];
        // вызов обработчика
        break;
    case FastRoute\Dispatcher::NOT_FOUND:
        // 404
        break;
}
    

Расширенные примеры и углублённые сценарии

В этом разделе приведены дополнительные примеры кода, которые помогут реализовать аутентификацию, работу с базой данных, кастомную обработку ошибок и другие часто востребованные возможности.

Пример 1: Аутентификация по JWT с использованием Slim и firebase/php-jwt

Пример

// Установка библиотеки
composer require firebase/php-jwt

// Middleware для проверки токена
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$app->add(function ($request, $handler) {
    $authHeader = $request->getHeaderLine('Authorization');
    if (preg_match('/Bearer\s+(\S+)/', $authHeader, $matches)) {
        $token = $matches[1];
        try {
            $decoded = JWT::decode($token, new Key('secret_key', 'HS256'));
            $request = $request->withAttribute('user_id', $decoded->sub);
        } catch (\Exception $e) {
            $response = new \Slim\Psr7\Response();
            $response->getBody()->write(json_encode(['error' => 'Неверный токен']));
            return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
        }
    } else {
        // Отсутствует токен - пропускаем, если не требуется
    }
    return $handler->handle($request);
});

// Генерация токена при логине
$app->post('/api/login', function ($request, $response) {
    $data = $request->getParsedBody();
    // Проверка учётных данных...
    $payload = [
        'iss' => 'example.org',
        'sub' => 123,
        'iat' => time(),
        'exp' => time() + 3600
    ];
    $token = JWT::encode($payload, 'secret_key', 'HS256');
    $response->getBody()->write(json_encode(['token' => $token]));
    return $response->withHeader('Content-Type', 'application/json');
});
  
Результат при успешном запросе POST /api/login:
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."}
  

Пример 2: Кастомный обработчик ошибок в Slim

Пример

use Slim\Exception\HttpNotFoundException;
use Slim\Exception\HttpMethodNotAllowedException;

$errorMiddleware = $app->addErrorMiddleware(true, true, true);

$errorMiddleware->setErrorHandler(HttpNotFoundException::class, function ($request, $exception, $displayErrorDetails, $response) {
    $response->getBody()->write(json_encode(['error' => 'Ресурс не найден']));
    return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
});

$errorMiddleware->setErrorHandler(\Throwable::class, function ($request, $exception, $displayErrorDetails, $response) {
    $response->getBody()->write(json_encode(['error' => 'Внутренняя ошибка сервера']));
    return $response->withStatus(500)->withHeader('Content-Type', 'application/json');
});
  

Пример 3: Подключение к базе данных через PDO с prepared statements

Пример

// config/db.php
$host = 'localhost';
$db   = 'api_db';
$user = 'root';
$pass = '';
$dsn = "mysql:host=$host;dbname=$db;charset=utf8mb4";

$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// В контроллере
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $args['id']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
    $payload = json_encode($user);
} else {
    $response->getBody()->write(json_encode(['error' => 'Пользователь не найден']));
    return $response->withStatus(404);
}
  

Пример 4: Реализация пагинации и фильтрации (на примере Slim)

Пример

$app->get('/api/users', function ($request, $response) use ($pdo) {
    $params = $request->getQueryParams();
    $page = (int)($params['page'] ?? 1);
    $perPage = (int)($params['per_page'] ?? 10);
    $offset = ($page - 1) * $perPage;

    $stmt = $pdo->prepare('SELECT * FROM users LIMIT :limit OFFSET :offset');
    $stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
    $stmt->execute();
    $users = $stmt->fetchAll(PDO::FETCH_ASSOC);

    return $response->withJson([
        'data' => $users,
        'page' => $page,
        'per_page' => $perPage
    ]);
});
  
Пример ответа при запросе GET /api/users?page=2&per_page=5:
{"data":[...],"page":2,"per_page":5}
  

Пример 5: Использование middleware для логирования запросов

Пример

$app->add(function ($request, $handler) {
    $start = microtime(true);
    $response = $handler->handle($request);
    $duration = microtime(true) - $start;
    $log = sprintf(
        "[%s] %s %s %s - %.4f секунд\n",
        date('Y-m-d H:i:s'),
        $request->getMethod(),
        (string)$request->getUri(),
        $response->getStatusCode(),
        $duration
    );
    file_put_contents(__DIR__ . '/../logs/api.log', $log, FILE_APPEND | LOCK_EX);
    return $response;
});
  

API сервер PHP - comments

En
Php api server (php)