Symfony Advent Calendar 2022 12日目の記事です✨
普段、フォームなどのPOST処理をするときはFormコンポーネントを使うと思います。
今回はSerializerとValidaotrを使って、POST処理をします。
APIで以下のようなJSONが送られてきた想定で、書籍の登録をします。
{
title: "ちょうぜつソフトウェア設計入門"
price: 3080
authorId: 1
}
なお、authorId:1で紐づく著者は田中ひさてるさんです。
Entity側
いつも通りつくればOKです。
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use App\Repository\AuthorRepository;
#[ORM\Entity(repositoryClass: AuthorRepository::class)]
#[ORM\Table(name: '`author`')]
class Author
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id;
#[ORM\Column(length: 180, unique: true)]
#[Assert\NotBlank]
#[Assert\Email]
private ?string $email = null;
#[ORM\Column]
#[Assert\NotBlank]
private ?string $name;
// 以下Getter, Setter
}
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use App\Repository\BookRepository;
#[ORM\Entity(repositoryClass: BookRepository::class)]
#[ORM\Table(name: '`book`')]
class Author
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id;
#[ORM\Column]
#[Assert\NotBlank]
#[Assert\Length(max:200)]
private ?string $title;
#[ORM\Column]
#[Assert\NotBlank]
private ?int $price;
#[ORM\ManyToOne(targetEntity: Author::class)]
private Author $author;
// 以下Getter, Setter
}
Controller側
Serializer
とValidator
をDIして利用します。Serializerでリクエストをオブジェクトに置換し、Validatorを使ってデータ検証します。問題なければ登録です。
namespace App\Controller;
use App\Entity\Book;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class BookController extends AbstractController
{
#[Route('/book', name: 'app_book_create', methods: ['POST'])]
public function create(
SerializerInterface $serializer,
ValidatorInterface $validator,
EntityManagerInterface $entityManager,
Request $request
): JsonResponse
{
// リクエストをBookエンティティに置換
$book = $serializer->deserialize($request->getContent(), Book::class, 'json');
// データ検証
$errors = $validator->validate($book);
if ($errors->count()) {
// エラーだったらBadRequest
throw new BadRequestHttpException((string) $errors);
}
// DB保存
$entityManager->persist($book);
$entityManager->flush();
// IDを出力
return $this->json([
'id' => $book->getid(),
]);
}
}
Serializer::deserialize()
で置換するクラスとフォーマットを指定して、置換します。
Validator::validate()
でエンティティを渡すとクラスのアトリビュートを解析して、データ検証を行います。
EntityManager::persist()
, EntityManager::flush()
は永続化対象としてマーキング&DBと同期です。
ただ、このままだとauthor_idからAuthorへ置換しないので、著者が誰か保存されません。
Serializer
にはIDからオブジェクトを取得する機能はないからです。
そう考えるとForm
コンポーネントはよくできています。この部分意識しなくて良いですからね。
Denormalizerをつくる
Serializer::deserialize()
でAuthorもオブジェクトにできるようにDenormalizer
を作ります。
Denormalizer
について、くわしくはこちら。
この独自Denormalizerで、DBからデータを取得してオブジェクトにセットすることができます。
namespace App\Serializer\Normalizer;
use App\Entity\Book;
use App\Repository\AuthorRepository;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
class BookDenormalizer implements DenormalizerInterface
{
public function __construct(
private readonly AuthorRepository $authorRepository,
private readonly ObjectNormalizer $objectNormalizer,
)
{
}
// Denormalize
public function denormalize(mixed $data, string $type, string $format = null, array $context = [])
{
$book = $this->objectNormalizer->denormalize($data, $type, $format, $context);
assert($book instanceof Book);
// 'authorId'がある場合は、DBからデータを取得してBookにセット
if (array_key_exists('authorId', $data)) {
$author = $this->authorRepository->find($data['author_id']);
$book->setAuthor($author);
}
return $book;
}
// 対象のクラスを指定する
public function supportsDenormalization(mixed $data, string $type, string $format = null): bool
{
return $type === Book::class;
}
}
これにより、ただしく田中ひさてるさんの著書として登録できます。