Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

REST API

API — интерфейс для программ: вместо страниц HTML приложение отдаёт данные в формате JSON, а читает их мобильное приложение или JavaScript на другом сайте. REST — распространённое соглашение о таком API: у каждого вида данных (ресурса) свой адрес, а действие над ним задаёт метод HTTP.

Маршруты ресурса

API статей живёт рядом с сайтом, в отдельной группе /api/posts. Добавьте её в config/routes.php вне группы сайта — проверка CSRF здесь не нужна:

// config/routes.php
use App\Action\Api;
 
$app->group('/api/posts', function (RouteCollectorProxy $group) {
    $group->get('', Api\ListPostsAction::class);
    $group->get('/{id:[0-9]+}', Api\ShowPostAction::class);
    $group->post('', Api\CreatePostAction::class);
    $group->delete('/{id:[0-9]+}', Api\DeletePostAction::class);
});
ЗапросЧто делает
GET /api/postsСписок статей
GET /api/posts/5Одна статья
POST /api/postsСоздать статью
DELETE /api/posts/5Удалить статью

Действия API кладут в отдельную папку src/Action/Api/, чтобы не путать их с действиями страниц.

Помощник для JSON

Функция json() из первой главы превращается в класс:

// src/Support/Json.php
namespace App\Support;
 
use Psr\Http\Message\ResponseInterface as Response;
 
final class Json
{
    public static function write(Response $response, mixed $data, int $status = 200): Response
    {
        $response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE));
 
        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus($status);
    }
}

Список

// src/Action/Api/ListPostsAction.php
namespace App\Action\Api;
 
final class ListPostsAction
{
    public function __construct(private PostRepository $posts) {}
 
    public function __invoke(Request $request, Response $response): Response
    {
        return Json::write($response, ['data' => $this->posts->all()]);
    }
}

Откройте http://localhost:8080/api/posts — браузер покажет статьи в формате JSON. Репозиторий тот же, что у страниц сайта: изменился только вид ответа. Действие ShowPostAction устроено так же, но вызывает find() и бросает HttpNotFoundException, если статьи нет.

Создание

// src/Action/Api/CreatePostAction.php
public function __invoke(Request $request, Response $response): Response
{
    $data = (array) $request->getParsedBody();
    $errors = $this->validator->validate($data);
 
    if ($errors !== []) {
        return Json::write($response, ['errors' => $errors], 422);
    }
 
    $id = $this->posts->create(trim($data['title']), trim($data['body']));
 
    return Json::write($response, $this->posts->find($id), 201);
}

Проверяет данные тот же PostValidator, что и форму. Запрос из терминала:

curl -X POST http://localhost:8080/api/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "Статья из API", "body": "Текст"}'

В ответ придёт созданная статья с кодом 201. Тело JSON разбирает посредник addBodyParsingMiddleware().

Удаление

Добавьте в репозиторий метод delete(), который выполняет DELETE FROM posts WHERE id = ? и возвращает true, если строка была удалена. Действие:

// src/Action/Api/DeletePostAction.php
public function __invoke(Request $request, Response $response, array $args): Response
{
    if (!$this->posts->delete((int) $args['id'])) {
        throw new HttpNotFoundException($request);
    }
 
    return $response->withStatus(204);
}

Код 204 означает «выполнено, тела ответа нет».

Ошибки в JSON

Свой обработчик ошибок из прошлых уроков выводит страницу HTML — программе-клиенту она бесполезна. Добавьте в его функцию проверку адреса:

// config/middleware.php
if (str_starts_with($request->getUri()->getPath(), '/api/')) {
    $response = $app->getResponseFactory()->createResponse();
 
    return Json::write($response, ['error' => 'Request failed'], $status);
}

Проверку ставят сразу после вычисления $status. Теперь /api/posts/999 вне режима отладки вернёт JSON с кодом 404.

Коды ответов

ДействиеУспехОшибки
Список, просмотр200404
Создание201422
Удаление204404

Частая ошибка

Ответ с кодом 200 и телом {"error": "..."} — частая небрежность: программа-клиент считает запрос успешным и пытается разобрать данные, которых нет. Код состояния — часть ответа: ошибки проверки отдавайте с 422, отсутствие записи — с 404.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.