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

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

Основные подходы к реализации JSON API на PHP

Основное эффективное решение: микрофреймворк Slim 4

Slim Framework версии 4 является легковесным и современным инструментом для создания REST API. Он предоставляет минимальный набор функций, необходимых для маршрутизации, обработки запросов и формирования ответов. Установка производится через Composer:

composer require slim/slim:^4.0

Далее создается файл index.php, который служит точкой входа. Определяется маршрут для GET-запроса к /api/users, возвращается ответ в формате JSON:


<?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();

$app->get('/api/users', function (Request $request, Response $response) {
    $users = [
        ['id' => 1, 'name' => 'Alice'],
        ['id' => 2, 'name' => 'Bob']
    ];
    $payload = json_encode($users);
    $response->getBody()->write($payload);
    return $response->withHeader('Content-Type', 'application/json');
});

$app->run();

Каждый шаг: подключение автозагрузчика, создание экземпляра приложения, определение маршрута с замыканием, формирование JSON-строки, запись в тело ответа и установка заголовка Content-Type. После запуска сервера (например, php -S localhost:8000) по адресу http://localhost:8000/api/users будет получен список пользователей.

Возможные проблемы и их решения:

  • Отсутствие CORS-заголовков: добавьте middleware slim/cors или вручную установите заголовки в ответе.
  • Необработанные ошибки: реализуйте кастомный обработчик ошибок через $app->addErrorMiddleware(true, true, true).
  • Проблемы с автозагрузкой: убедитесь, что composer autoload подключен и файлы расположены согласно PSR-4.

Цели использования:

  • Быстрое прототипирование API.
  • Микросервисы с минимальными зависимостями.
  • Проекты, не требующие полного MVC-фреймворка.

Как создать JSON API на чистом PHP без сторонних библиотек?

Иногда нет возможности использовать Composer или фреймворки. Тогда можно написать API на нативном PHP. Необходимо самостоятельно разобрать URI, определить маршрут и вернуть JSON.


<?php
// index.php
header('Content-Type: application/json');

$requestUri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];

if ($method === 'GET' && $requestUri === '/api/users') {
    $users = [
        ['id' => 1, 'name' => 'Alice'],
        ['id' => 2, 'name' => 'Bob']
    ];
    echo json_encode($users);
    exit;
}

http_response_code(404);
echo json_encode(['error' => 'Not found']);

Пояснение: устанавливается заголовок Content-Type, анализируется глобальный массив $_SERVER, выполняется простой роутинг, формируется JSON-ответ. При отсутствии маршрута возвращается 404.

Проблемы и решения:

  • Отсутствие гибкости в роутинге: при увеличении числа маршрутов код становится нечитаемым. Решение: написать класс-роутер или использовать библиотеку.
  • Нет поддержки PSR-7: все запросы и ответы приходится обрабатывать вручную, что повышает риск ошибок безопасности. Решение: подключить отдельные PSR-7 библиотеки (например, guzzlehttp/psr7).
  • Ошибки валидации: необходимо вручную проверять параметры запроса. Решение: реализовать функции-валидаторы.

Цели использования:

  • Обучение и понимание внутреннего устройства API.
  • Простые однофайловые скрипты.
  • Среда с ограниченными возможностями (например, shared hosting).

Как создать JSON API с помощью микрофреймворка Lumen?

Lumen - это легковесная версия Laravel, оптимизированная для API. Установка: composer create-project --prefer-dist laravel/lumen api.


// routes/web.php
$router->get('/api/users', function () use ($router) {
    $users = [['id' => 1, 'name' => 'Alice'], ['id' => 2, 'name' => 'Bob']];
    return response()->json($users);
});

Lumen автоматически преобразует возвращаемый массив в JSON и устанавливает соответствующие заголовки. Если нужно более сложное поведение, используются контроллеры и Eloquent ORM.

Проблемы и решения:

  • Необходимость включения фасадов (например, DB) в конфигурации. Решение: установить параметр app.php 'aliases' => [...] или использовать вспомогательные функции.
  • CORS: требуется установка пакета barryvdh/laravel-cors и добавление middleware.
  • Отсутствие некоторых функций Laravel (мощный artisan). Решение: для полноценного API лучше перейти на Laravel.

Цели использования:

  • API-ориентированные проекты, где нужен минимум функций фреймворка.
  • Быстрая разработка с возможностью масштабирования до Laravel.

Как построить JSON API на Laravel с использованием API Resources?

Laravel предоставляет богатый инструментарий для создания API. Создаем модель и контроллер с помощью artisan:


php artisan make:model User -mf
php artisan make:controller Api/UserController --resource

В контроллере можно использовать Resource классы для трансформации данных:


// UserController.php
public function index()
{
    return UserResource::collection(User::all());
}
// UserResource.php
public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'created_at' => $this->created_at
    ];
}

Маршруты определяются в routes/api.php: Route::apiResource('users', 'Api\UserController');

Проблемы и решения:

  • Избыточность для простых API: множество конфигурационных файлов, сервис-провайдеров. Решение: использовать Lumen или Slim.
  • Производительность: Laravel тяжелее микрофреймворков. Решение: кэширование конфигурации (php artisan config:cache), использование PHP OPcache.
  • Обработка ошибок: стандартные ответы ExceptionHandler можно кастомизировать.

Цели использования:

  • Полноценные веб-приложения с API-частью.
  • Проекты с богатой бизнес-логикой, авторизацией, очередями.

Как автоматически сгенерировать JSON API на Symfony с помощью API Platform?

API Platform - это набор инструментов для Symfony, который автоматически создает REST API и GraphQL на основе аннотаций Doctrine. Установка: composer require api-platform/core.


// src/Entity/User.php
#[ApiResource]
class User
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column]
    private ?int $id = null;
    
    #[ORM\Column]
    private ?string $name = null;
}

После настройки маршрутов (/api/users) API Platform генерирует все CRUD-операции, документацию Swagger, фильтры, пагинацию и т.д. Практически не требуется писать код контроллеров.

Проблемы и решения:

  • Тяжеловесность: много зависимостей, долгая установка. Решение: использовать только для крупных проектов.
  • Ограниченный контроль: сложно изменить поведение, отличное от стандартного. Решение: создание кастомных операций через композеры.
  • Требуется знание Symfony: без понимания структуры сложно настраивать. Решение: начать с лёгких фреймворков.

Цели использования:

  • API с большим количеством сущностей и сложными связями.
  • Необходимость автоматической документации и клиентов.

Расширенные примеры кода

Дополнительные примеры демонстрируют более сложные сценарии, такие как обработка запросов с параметрами, аутентификация и пагинация.

Пример 1: Нативный API с роутером, CORS и валидацией входных данных
Пример

<?php
// index.php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type');

$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = rtrim($uri, '/');

// Роутер
$routes = [
    'GET' => [
        '/api/users' => 'getUsers',
        '/api/users/(\d+)' => 'getUser'
    ],
    'POST' => [
        '/api/users' => 'createUser'
    ]
];

$response = ['error' => 'Not Found'];
$status = 404;

foreach ($routes[$method] ?? [] as $pattern => $handler) {
    $regex = '#^' . $pattern . '$#';
    if (preg_match($regex, $uri, $matches)) {
        array_shift($matches); // удаляем полное совпадение
        $response = call_user_func_array($handler, $matches);
        $status = 200;
        break;
    }
}

http_response_code($status);
echo json_encode($response);

function getUsers() {
    return [
        ['id' => 1, 'name' => 'Alice'],
        ['id' => 2, 'name' => 'Bob']
    ];
}

function getUser($id) {
    // Имитация поиска
    return ['id' => (int)$id, 'name' => 'User' . $id];
}

function createUser() {
    $input = json_decode(file_get_contents('php://input'), true);
    if (empty($input['name'])) {
        http_response_code(400);
        return ['error' => 'Name is required'];
    }
    return ['id' => 3, 'name' => $input['name']];
}
$ curl -X POST http://localhost:8000/api/users -d '{"name":"Charlie"}' -H "Content-Type: application/json"
{"id":3,"name":"Charlie"}

Пояснение: реализован простой роутер с использованием регулярных выражений, поддержка CORS через заголовки, валидация входных данных через php://input и json_decode.

Пример 2: Slim 4 с JWT-аутентификацией
Пример

<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Factory\AppFactory;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

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

$app = AppFactory::create();
$app->addErrorMiddleware(true, true, true);

$secretKey = 'mySecretKey123';

// Middleware для проверки токена
$authMiddleware = function (Request $request, $handler) use ($secretKey) {
    $authHeader = $request->getHeaderLine('Authorization');
    if (empty($authHeader) || !str_starts_with($authHeader, 'Bearer ')) {
        $response = new \Slim\Psr7\Response();
        $response->getBody()->write(json_encode(['error' => 'Unauthorized']));
        return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
    }
    $token = substr($authHeader, 7);
    try {
        $decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
        $request = $request->withAttribute('userId', $decoded->sub);
    } catch (\Exception $e) {
        $response = new \Slim\Psr7\Response();
        $response->getBody()->write(json_encode(['error' => 'Invalid token']));
        return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
    }
    return $handler->handle($request);
};

// Маршрут для получения токена (логин)
$app->post('/api/login', function (Request $request, Response $response) use ($secretKey) {
    $body = $request->getParsedBody();
    $username = $body['username'] ?? '';
    $password = $body['password'] ?? '';
    // Проверка учетных данных (имитация)
    if ($username === 'admin' && $password === '123') {
        $payload = [
            'iss' => 'myapp',
            'sub' => 1,
            'iat' => time(),
            'exp' => time() + 3600
        ];
        $token = JWT::encode($payload, $secretKey, 'HS256');
        $response->getBody()->write(json_encode(['token' => $token]));
        return $response->withHeader('Content-Type', 'application/json');
    }
    $response->getBody()->write(json_encode(['error' => 'Invalid credentials']));
    return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
});

// Защищенный маршрут
$app->get('/api/profile', function (Request $request, Response $response) {
    $userId = $request->getAttribute('userId');
    $response->getBody()->write(json_encode(['id' => $userId, 'name' => 'Admin']));
    return $response->withHeader('Content-Type', 'application/json');
})->add($authMiddleware);

$app->run();
$ curl -X POST http://localhost:8000/api/login -d "username=admin&password=123"
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."}
$ curl -H "Authorization: Bearer eyJ0eXAi..." http://localhost:8000/api/profile
{"id":1,"name":"Admin"}

Пояснение: используется библиотека firebase/php-jwt. Создан middleware для проверки Bearer-токена. Маршрут /api/login выдает токен после успешной аутентификации. Защищенный маршрут /api/profile требует валидного токена.

Пример 3: Laravel API с пагинацией и фильтрацией
Пример

// routes/api.php
Route::get('/users', function (Request $request) {
    $query = App\Models\User::query();
    
    if ($request->has('name')) {
        $query->where('name', 'like', '%' . $request->name . '%');
    }
    if ($request->has('email')) {
        $query->where('email', $request->email);
    }
    
    $perPage = $request->get('per_page', 15);
    $users = $query->paginate($perPage);
    
    return UserResource::collection($users);
});

// UserResource.php
public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
    ];
}
$ curl "http://localhost/api/users?name=Ali&per_page=5"
{
  "data": [
    {"id":1,"name":"Alice","email":"alice@example.com"},
    {"id":21,"name":"Alison","email":"alison@example.com"}
  ],
  "links": {
    "first": "http://localhost/api/users?name=Ali&page=1",
    "last": "http://localhost/api/users?name=Ali&page=1",
    "prev": null,
    "next": null
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 1,
    "path": "http://localhost/api/users",
    "per_page": 5,
    "to": 1,
    "total": 2
  }
}

Пояснение: в Laravel используется Query Builder с условными фильтрами. Метод paginate() автоматически добавляет пагинацию. UserResource трансформирует модели. Результат содержит метаданные пагинации.

PHP: создание JSON API - comments

En
Json api php (php)