使用Symfony Serialialser序列化API输出
#php #doctrine

我已经看到API的位置,从学说查询获得结果后,结果实体被序列化并返回为HTTP响应。

遵循这种方式可能会产生一些问题,因为您将数据库架构耦合到视图,并且您将返回信息客户端不应看到。让我们看看一个示例

让我们想象我们有这样的实体:

  #[ORM\Entity(repositoryClass: UserRepository::class)]
  class User {

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private string $name;

    #[ORM\Column(length: 255)]
    private string $lastName;

    #[ORM\Column]
    private \DateTimeImmutable $birthDate;

    // getters & setters
}

现在,让我们想象我们查询 userrepository 并将其返回到客户端

   class UserController extends AbstractController {

      #[Route('/users', name: 'get_users', methods: ['GET'])]
      public function getUsersAction(EntityManagerInterface $em, SerializerInterface $serializer): JsonResponse
      {
          $users = $em->getRepository(UserRepository::class)->findAll();
          return new JsonResponse($serializer->normalize($users), 200);
      }

   }

按照这种方式,我们将公开所有用户属性,即使是我们不想展示的用户属性。

要解决此问题,我们可以通过两种方法来缓解:

1.-将串行器组添加到实体属性
2.-使用构建输出的服务

为例,让我们考虑一下我们不想公开ID属性。

将串行器组添加到实体属性

在这种情况下,我们只需要在要公开的实体属性中添加序列化器组。然后,当序列化结果时,我们必须指示串行器组才能仅序列化匹配该组的

   #[ORM\Entity(repositoryClass: UserRepository::class)]
   class User {

      #[ORM\Id]
      #[ORM\GeneratedValue]
      #[ORM\Column]
      private ?int $id = null;

      #[ORM\Column(length: 255)]
      #[Groups(['api_output'])]
      private string $name;

      #[ORM\Column(length: 255)]
      #[Groups(['api_output'])]
      private string $lastName;

      #[ORM\Column]
      #[Groups(['api_output'])]
      private \DateTimeImmutable $birthDate;

      // getters & setters
  }

我们已将 api_output 组添加到 name lastName 出生日期属性。现在让我们序列化告诉序列化器要使用哪个组。

   class UserController extends AbstractController {

      #[Route('/users', name: 'get_users', methods: ['GET'])]
      public function getUsersAction(EntityManagerInterface $em, SerializerInterface $serializer): JsonResponse
      {
          $users = $em->getRepository(UserRepository::class)->findAll();

          $context = (new ObjectNormalizerContextBuilder())->withGroups('api_output')->toArray();
          return new JsonResponse($serializer->normalize($users, null, $context), 200);
      }

   }

我不太喜欢这种方法,因为您仍将数据库架构与输出耦合。如果您的模式开始增长并建立关联,则管理组可能会变得复杂,并且很容易获得循环参考错误。

所以,让我们看看第二种方法

使用构建输出的服务

最后一种方法包括创建一个单独的服务,该服务将负责构建输出。为此,我们需要:

  • 单独的模型( userOutput )将充当输出模型
  • 将接收一系列用户并将返回数组或 userOutput 的服务

让我们看看:

   class UserOutput {

       public function __construct(
          public readonly string $name,
          public readonly string $lastName,
          public readonly string $birthDate
       ){}
   }
   class UserOutputBuilder
   {
       /**
        * @param User[] $users
        * @return UserOutput[]
        */
       public function buildOutput(array $users): array
       {
           $targetUsers = [];
           foreach ($users as $user){
              $targetUsers[] = new UserOutput(
                 $user->getName(),
                 $user->getLastname(),
                 $user->getBirthDate()->format('d/m/Y')
              );
           }

           return $targetUsers;
       }
   }

现在,我们只需要委派在 useroutputbuilder
上构建输出的责任。

   class UserController extends AbstractController {

       #[Route('/users', name: 'get_users', methods: ['GET'])]
       public function getUsersAction(EntityManagerInterface $em, UserOutputBuilder $userOutputBuilder, SerializerInterface $serializer): JsonResponse
       {
           $users = $em->getRepository(UserRepository::class)->findAll();
           return new JsonResponse($serializer->normalize($userOutputBuilder->buildOutput($users)), 200);
       }

  }

使用这种方法,我们的模式的任何更改都不会影响我们的输出,因为有一个构建器从学说结果构建输出,并且使用单独的模型。如果我们的架构更改并且我们希望公开更多属性,我们将不得不将其添加到输出模型中,并在构建器上进行更改。