はじめに
1API Platformを使っていてカスタムDELETEアクションを作成した所、思わぬ挙動になったので、その時の備忘録です。
やりたかったこと
User
、Book
、FavoriteBook
の3つEntityがあります。FavoriteBook
はユーザーのお気に入りの本を表す中間テーブルです。
リレーションはこんな感じです。
APIとして提供するのはUser
とBook
のCRUD操作だけで、FavoriteBook
のCRUD操作のエンドポイントは提供しません。代わりに、/api/books/{bookId}/favorite
にPOSTとDLETEメソッドを設け、ここでログインしているユーザーに紐づくFavoriteBook
を作成します。
実装
重要な所だけ載せます。
Book
に設定されているアノテーションはこのようになってます。
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Controller\AddFavoriteAction;
use App\Controller\DeleteFavoriteAction;
use App\Repository\BookRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ApiResource(
* itemOperations={
* "get",
* "delete",
* "put",
* "add_favorite"={
* "method"="POST",
* "path"="/books/{id}/favorite",
* "controller"=AddFavoriteAction::class,
* "denormalization_context"={"groups"={"book:favorite"}},
* },
* "delete_favorite"={
* "method"="DELETE",
* "path"="/books/{id}/favorite",
* "controller"=DeleteFavoriteAction::class,
* }
* },
* collectionOperations={
* "get",
* "post",
* }
* )
* @ORM\Entity(repositoryClass=BookRepository::class)
*/
class Book
{
AddFavoriteAction
とDeleteFavoriteAction
はそれぞれこのようになっています。
<?php
namespace App\Controller;
use App\Entity\Book;
use App\Entity\FavoriteBook;
use App\Entity\User;
use App\Repository\FavoriteBookRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class AddFavoriteAction
{
private $entityManager;
private $tokenStorage;
public function __construct(
EntityManagerInterface $entityManager,
TokenStorageInterface $tokenStorage
) {
$this->entityManager = $entityManager;
$this->tokenStorage = $tokenStorage;
}
public function __invoke(Book $data)
{
$user = $this->tokenStorage->getToken()->getUser();
if (!$user || !$user instanceof User) {
throw new AuthenticationException();
}
/** @var FavoriteBookRepository $favoriteBookRepository */
$favoriteBookRepository = $this->entityManager->getRepository(FavoriteBook::class);
$favorite = $favoriteBookRepository->findOrCreateByUserAndBook($user, $data);
$this->entityManager->persist($favorite);
$this->entityManager->flush();
return $data;
}
}
<?php
namespace App\Controller;
use App\Entity\Book;
use App\Entity\FavoriteBook;
use App\Entity\User;
use App\Repository\FavoriteBookRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class DeleteFavoriteAction
{
private $entityManager;
private $tokenStorage;
public function __construct(
EntityManagerInterface $entityManager,
TokenStorageInterface $tokenStorage
) {
$this->entityManager = $entityManager;
$this->tokenStorage = $tokenStorage;
}
public function __invoke(Book $data)
{
$user = $this->tokenStorage->getToken()->getUser();
if (!$user || !$user instanceof User) {
throw new AuthenticationException();
}
/** @var FavoriteBookRepository $favoriteBookRepository */
$favoriteBookRepository = $this->entityManager->getRepository(FavoriteBook::class);
$favorite = $favoriteBookRepository->findOneBy(['user' => $user, 'book' => $data]);
if ($favorite) {
$this->entityManager->remove($favorite);
$this->entityManager->flush();
}
return $data;
}
}
/api/docs/
にアクセスすると、このような表示になっています。
実際にBook
を登録してから、/api/books/{id}.favorite
のPOSTメソッドを叩くと...OKお気に入り登録されている。
DELETEを叩くと...OKお気に入り解除されてr...あれ?Book
も消えてない...??
原因
カスタムアクションで指定したDeleteFavoriteAction
の__invoke()
メソッドでobjectをreturnしてしまうと、どうやらそのobjectがremoveされてしまうようです。
なので、__invoke()
メソッドのreturnを削除してしまえばOKでした。
余談
__invoke()
メソッドの引数ですが、変数名ベースのインジェクション2を行っているらしく、$data
以外の名前をつけるとエラーになりました。
ここらへんドキュメントに書いてあるのかなあ...。