LoginSignup
1
1

More than 1 year has passed since last update.

SerializerとValidatorでPOSTを処理する

Last updated at Posted at 2022-12-11

Symfony Advent Calendar 2022 12日目の記事です✨

普段、フォームなどのPOST処理をするときはFormコンポーネントを使うと思います。
今回はSerializerとValidaotrを使って、POST処理をします。

APIで以下のようなJSONが送られてきた想定で、書籍の登録をします。

{
    title: "ちょうぜつソフトウェア設計入門"
    price: 3080
    authorId: 1
}

なお、authorId:1で紐づく著者は田中ひさてるさんです。

Entity側

いつも通りつくればOKです。

Author.php

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
}
Book.php
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側

SerializerValidatorをDIして利用します。Serializerでリクエストをオブジェクトに置換し、Validatorを使ってデータ検証します。問題なければ登録です。

BookController.php

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からデータを取得してオブジェクトにセットすることができます。

BookDenormalizer.php

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;
    }
}

これにより、ただしく田中ひさてるさんの著書として登録できます。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1