PHP API: новый взгляд на создание веб-сервисов

Раздел: Разработка программного обеспечения -> Разработка API

Создание нового API на PHP с помощью современных инструментов

В этой статье рассматриваются разные подходы к построению API на PHP. Основное внимание уделяется использованию Slim Framework 4 как легковесного и современного решения.

Как инициализировать Slim-приложение и настроить маршруты?

Slim Framework 4 основан на PSR-7 и PSR-15. Установка через Composer:

composer require slim/slim:"4.*"

Api https php (api на php с https)

Создание файла index.php:

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

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

$app = AppFactory::create();

$app->get('/hello/{name}', function (Request $request, Response $response, $args) {
    $name = $args['name'];
    $response->getBody()->write(json_encode(['message' => "Hello, $name"]));
    return $response->withHeader('Content-Type', 'application/json');
});

$app->run();

Api php action (действия api в php)

Пояснение: создаётся экземпляр приложения, определяется маршрут GET с параметром name, ответ преобразуется в JSON и возвращается с правильным заголовком.

Важно: не забыть добавить middleware для обработки ошибок и CORS.

Пример middleware для CORS:

$app->add(function ($request, $handler) {
    $response = $handler->handle($request);
    return $response
        ->withHeader('Access-Control-Allow-Origin', '*')
        ->withHeader('Access-Control-Allow-Headers', '*')
        ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
});

Json api php (php: создание json api)

Типичные ошибки

Ошибка: клиент получает HTML-ответ вместо JSON. Решение: проверить, что заголовок Content-Type установлен в application/json.

Ошибка: не работают CORS-запросы с методами PUT/DELETE. Решение: добавить обработку OPTIONS-запроса и вернуть пустой ответ с правильными заголовками.

Как реализовать API на чистом PHP без фреймворка?

Этот подход полезен для понимания основ. Пример маршрута:

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

if ($method === 'GET' && $uri === '/api/users') {
    header('Content-Type: application/json');
    $users = [['id' => 1, 'name' => 'Alice'], ['id' => 2, 'name' => 'Bob']];
    echo json_encode($users);
    exit;
}

Php open api (открытое api на php)

Недостатки: сложно поддерживать, нет middleware, приходится вручную управлять ошибками.

Ошибка: при использовании header() перед выводом данных может возникнуть ошибка "Cannot modify header information". Решение: убедиться, что нет вывода до вызова header(), или использовать буферизацию.

Как использовать Laravel для создания API с аутентификацией?

Laravel предоставляет встроенные средства для API: ресурсные контроллеры, Sanctum для токенов. Установка:

composer create-project laravel/laravel api-project
php artisan install:api

Php api new (новое api на php)

Пример контроллера:

use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index()
    {
        return response()->json(User::all());
    }
}

Для аутентификации добавляется middleware 'auth:sanctum' в маршрут. Проблема: избыточность для простых API, высокая сложность конфигурации.

Как создать лёгкое API с помощью Flight PHP?

Flight - микрофреймворк, минималистичный. Пример:

<?php
require 'flight/Flight.php';

Flight::route('GET /users', function() {
    $users = [['id' => 1, 'name' => 'Alice']];
    Flight::json($users);
});

Flight::start();

Подходит для небольших проектов, но может не хватать встроенной валидации и ORM.

Расширенные примеры разработки PHP API

Аутентификация JWT в Slim

Использование библиотеки firebase/php-jwt для создания и проверки токена.

Пример
composer require firebase/php-jwt

Пример middleware для проверки токена:

Пример
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class JwtAuthMiddleware implements MiddlewareInterface
{
    public function process(Request $request, RequestHandlerInterface $handler): Response
    {
        $authHeader = $request->getHeader('Authorization');
        if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader[0], $matches)) {
            $response = new \Slim\Psr7\Response(401);
            $response->getBody()->write(json_encode(['error' => 'Token not provided']));
            return $response->withHeader('Content-Type', 'application/json');
        }
        $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(401);
            $response->getBody()->write(json_encode(['error' => 'Invalid token']));
            return $response->withHeader('Content-Type', 'application/json');
        }
        return $handler->handle($request);
    }
}

Результат запроса без токена:

HTTP/1.1 401 Unauthorized
Content-Type: application/json
{"error":"Token not provided"}

Валидация данных с помощью Respect\Validation

Библиотека позволяет задавать правила валидации. Пример проверки email и пароля:

Пример
use Respect\Validation\Validator as v;

$email = 'user@example.com';
$password = 'weak';

$emailValidator = v::email();
$passwordValidator = v::alnum()->noWhitespace()->length(8, 20);

if (!$emailValidator->validate($email)) {
    $errors[] = 'Email is invalid';
}
if (!$passwordValidator->validate($password)) {
    $errors[] = 'Password must be 8-20 alphanumeric characters';
}

Пример ответа с ошибками:

{
  "errors": ["Password must be 8-20 alphanumeric characters"]
}

Пагинация и фильтрация списка пользователей

Добавление параметров page и limit, возврат метаданных.

Пример
$app->get('/users', function (Request $request, Response $response) {
    $params = $request->getQueryParams();
    $page = max(1, (int)($params['page'] ?? 1));
    $limit = min(100, max(1, (int)($params['limit'] ?? 10)));
    $offset = ($page - 1) * $limit;

    // Пример данных из базы
    $total = 50;
    $users = [['id' => 1, 'name' => 'Alice'], ['id' => 2, 'name' => 'Bob']];

    $result = [
        'data' => $users,
        'meta' => [
            'page' => $page,
            'limit' => $limit,
            'total' => $total,
            'pages' => ceil($total / $limit)
        ]
    ];
    $response->getBody()->write(json_encode($result));
    return $response->withHeader('Content-Type', 'application/json');
});

Результат запроса /users?page=2&limit=5:

{
  "data": [ ... ],
  "meta": {
    "page": 2,
    "limit": 5,
    "total": 50,
    "pages": 10
  }
}

Загрузка файла через API

Обработка multipart/form-data, сохранение файла в директорию.

Пример
$app->post('/upload', function (Request $request, Response $response) {
    $uploadedFiles = $request->getUploadedFiles();
    $file = $uploadedFiles['file'] ?? null;
    if (!$file || $file->getError() !== UPLOAD_ERR_OK) {
        $response->getBody()->write(json_encode(['error' => 'File upload failed']));
        return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
    }
    $filename = uniqid() . '_' . $file->getClientFilename();
    $file->moveTo(__DIR__ . '/uploads/' . $filename);
    $response->getBody()->write(json_encode(['filename' => $filename]));
    return $response->withHeader('Content-Type', 'application/json');
});

Результат успешной загрузки:

{
  "filename": "67890abc_photo.jpg"
}

Тестирование маршрутов с PHPUnit

Использование HTTP-клиента для тестирования Slim-приложения.

Пример
use PHPUnit\Framework\TestCase;
use Slim\Psr7\Factory\ServerRequestFactory;
use Slim\App;

class ApiTest extends TestCase
{
    public function testHelloEndpoint()
    {
        $app = (new App())->get('/hello/{name}', function ($request, $response, $args) {
            $response->getBody()->write(json_encode(['message' => "Hello, {$args['name']}"]));
            return $response->withHeader('Content-Type', 'application/json');
        });

        $request = (new ServerRequestFactory())->createServerRequest('GET', '/hello/World');
        $response = $app->handle($request);

        $this->assertEquals(200, $response->getStatusCode());
        $this->assertStringContainsString('Hello, World', (string)$response->getBody());
    }
}

Результат выполнения теста (при успехе):

OK (1 test, 2 assertions)

Новое API на PHP - comments

En
Php api new (php)