PHP 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:apiPhp 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)