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