在Symfony中使用DTO处理含多个ID的JSON请求体
如何通过DTO关联实体验证ID有效性
问题描述
我需要处理如下JSON请求体:
{ "idBook": "34567890", "idReaders": ["3456789","3456789"] }
其中idReaders是已阅读该书的用户ID列表。我用DTO处理这个JSON,但不知道怎么把这些ID关联到对应的书籍、用户实体来验证ID的有效性。当前DTO代码如下:
<?php class addBookReaderDTO { #[Assert\NotBlank] #[Assert\NotNull] public string $idBook; #[Assert\NotBlank] #[Assert\NotNull] #[Assert\E] // 此处为无效注解 public array $idReaders; }
解决方案
步骤1:修正DTO的验证注解
首先修正DTO中无效的#[Assert\E]注解,针对数组元素的验证使用#[Assert\All]确保每个用户ID符合格式要求,同时调整类名符合PHP大驼峰规范:
<?php use Symfony\Component\Validator\Constraints as Assert; class AddBookReaderDTO { #[Assert\NotBlank(message: "书籍ID不能为空")] #[Assert\NotNull(message: "书籍ID不能为null")] public string $idBook; #[Assert\NotBlank(message: "用户ID列表不能为空")] #[Assert\NotNull(message: "用户ID列表不能为null")] #[Assert\All([ #[Assert\NotBlank(message: "单个用户ID不能为空")], #[Assert\Type(type: "string", message: "用户ID必须为字符串类型")] ])] public array $idReaders; }
步骤2:在业务服务层关联实体并验证有效性
DTO仅负责请求参数的格式校验,实体关联和存在性验证需放到业务服务层处理(以Doctrine ORM为例):
<?php namespace App\Service; use App\DTO\AddBookReaderDTO; use App\Repository\BookRepository; use App\Repository\UserRepository; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class BookReaderService { public function __construct( private BookRepository $bookRepository, private UserRepository $userRepository ) {} public function addReaders(AddBookReaderDTO $dto): void { // 验证书籍存在 $book = $this->bookRepository->find($dto->idBook); if (!$book) { throw new NotFoundHttpException("书籍ID {$dto->idBook} 不存在"); } // 验证所有用户ID对应的用户存在 $uniqueReaderIds = array_unique($dto->idReaders); $users = $this->userRepository->findBy(['id' => $uniqueReaderIds]); if (count($users) !== count($uniqueReaderIds)) { $existingUserIds = array_column($users, 'id'); $invalidIds = array_diff($uniqueReaderIds, $existingUserIds); throw new NotFoundHttpException("以下用户ID不存在:" . implode(', ', $invalidIds)); } // 执行关联逻辑(示例:将用户添加到书籍的阅读列表) foreach ($users as $user) { $book->addReader($user); } // 持久化数据 $this->bookRepository->save($book, true); } }
可选:自定义断言实现DTO层面的实体验证
如果希望在DTO校验阶段直接完成实体存在性检查,可以自定义Symfony验证断言:
1. 自定义书籍存在断言
<?php namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; #[\Attribute] class BookExists extends Constraint { public string $message = "书籍ID {{ value }} 不存在"; public function getTargets(): string { return self::PROPERTY_CONSTRAINT; } }
2. 对应的验证器
<?php namespace App\Validator; use App\Repository\BookRepository; use App\Validator\Constraints\BookExists; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; class BookExistsValidator extends ConstraintValidator { public function __construct(private BookRepository $bookRepository) {} public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof BookExists) return; if (empty($value)) return; if (!$this->bookRepository->find($value)) { $this->context->buildViolation($constraint->message) ->setParameter('{{ value }}', $value) ->addViolation(); } } }
3. 同理实现UserExists断言后,修改DTO
<?php use Symfony\Component\Validator\Constraints as Assert; use App\Validator\Constraints as CustomAssert; class AddBookReaderDTO { #[Assert\NotBlank] #[Assert\NotNull] #[CustomAssert\BookExists] // 直接验证书籍存在 public string $idBook; #[Assert\NotBlank] #[Assert\NotNull] #[Assert\All([ #[Assert\NotBlank], #[Assert\Type("string")], #[CustomAssert\UserExists] // 验证每个用户ID存在 ])] public array $idReaders; }
这样在控制器执行$this->validate($dto)时,会自动完成实体存在性校验。
内容的提问来源于stack exchange,提问作者speeeeeed
相关产品推荐
相关产品推荐

