Encodera

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

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

Оглавление

API Platform

Писать вручную контроллеры для каждой сущности — список, одна запись, создание, изменение, удаление — однообразная работа. API Platform создаёт такой API по сущностям Doctrine вместе с документацией, проверкой данных и постраничным выводом.

Установка

composer require api

Ресурс

Достаточно пометить сущность атрибутом:

use ApiPlatform\Metadata\ApiResource;
 
#[ORM\Entity]
#[ApiResource]
class Post
{
    // свойства и методы
}

Появятся пять адресов:

Метод и адресДействие
GET /api/postsСписок
POST /api/postsСоздание
GET /api/posts/{id}Одна запись
PATCH /api/posts/{id}Изменение
DELETE /api/posts/{id}Удаление

По адресу /api откроется документация, где каждый запрос можно выполнить прямо в браузере.

Только нужные операции

use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post as PostOperation;
 
#[ApiResource(operations: [
    new GetCollection(),
    new Get(),
    new PostOperation(),
])]
class Post
{
    // …
}

Изменение и удаление через API стали недоступны.

Какие поля отдавать и принимать

Группы сериализации разделяют чтение и запись:

use Symfony\Component\Serializer\Attribute\Groups;
 
#[ApiResource(
    normalizationContext: ['groups' => ['post:read']],
    denormalizationContext: ['groups' => ['post:write']],
)]
class Post
{
    #[Groups(['post:read'])]
    private ?int $id = null;
 
    #[Groups(['post:read', 'post:write'])]
    #[Assert\NotBlank]
    private ?string $title = null;
 
    #[Groups(['post:read'])]
    private ?\DateTimeImmutable $createdAt = null;
}

Идентификатор и дату клиент видит, но изменить не может. Ограничения Assert проверяются автоматически: неверные данные дают ответ с кодом 422.

Постраничный вывод

#[ApiResource(paginationItemsPerPage: 20)]

Клиент запрашивает страницы параметром ?page=2.

Фильтры

use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
 
#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: ['title' => 'partial', 'author' => 'exact'])]
#[ApiFilter(OrderFilter::class, properties: ['createdAt'])]
class Post
{
    // …
}

Теперь работают запросы /api/posts?title=php и /api/posts?order[createdAt]=desc.

Права доступа

use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Patch;
 
#[ApiResource(operations: [
    new GetCollection(),
    new Get(),
    new PostOperation(security: "is_granted('ROLE_USER')"),
    new Patch(security: "object.getAuthor() == user"),
    new Delete(security: "is_granted('ROLE_ADMIN')"),
])]

В выражении object — запрошенная запись, user — текущий пользователь.

Своя логика

Действия при сохранении — назначить автора, отправить письмо — пишут в классе-обработчике (state processor), который подключают к операции параметром processor. Данные не из базы отдаёт поставщик (state provider).

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

Атрибут #[ApiResource] без настроек открывает все операции и все свойства сущности любому посетителю — включая удаление и поля вроде пароля. Для каждого ресурса задайте список операций, группы сериализации и правила security, а затем проверьте в документации /api, что видно гостю.

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

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