Symfony 4 REST API POST请求数据验证:如何实现瘦控制器设计?
Symfony 瘦控制器模式下的POST请求验证实现方案
作为Laravel开发者,你可以找到和Laravel FormRequest几乎完全对应的实现方式,以下是几种常用的符合瘦控制器理念的实现方案:
方案1:DTO + 参数自动解析验证(最推荐,和Laravel FormRequest体验一致)
这种方案完全不需要在控制器内写任何验证逻辑,验证规则全部收敛在DTO类中,请求进入控制器前会自动完成反序列化和验证,验证失败直接返回错误。
步骤1:创建DTO类定义验证规则
<?php namespace App\Dto; use Symfony\Component\Validator\Constraints as Assert; class CreatePostDto { #[Assert\NotBlank(message: '标题不能为空')] #[Assert\Length(max: 255, maxMessage: '标题长度不能超过255字符')] #[Assert\Unique(entity: 'App\Entity\Post', field: 'title', message: '标题已存在')] public string $title; #[Assert\NotBlank(message: '内容不能为空')] public string $body; }
步骤2:配置DTO参数解析器
实现Symfony的ValueResolver接口,让框架自动处理请求反序列化和验证:
<?php namespace App\ValueResolver; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\ValidatorInterface; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class DtoValueResolver implements ValueResolverInterface { public function __construct( private SerializerInterface $serializer, private ValidatorInterface $validator ) {} public function resolve(Request $request, ArgumentMetadata $argument): iterable { $dtoClass = $argument->getType(); // 仅处理App\Dto目录下的DTO类 if (!str_starts_with($dtoClass, 'App\Dto\\') || !class_exists($dtoClass)) { return []; } // 反序列化请求数据到DTO对象 $dto = $this->serializer->deserialize( $request->getContent(), $dtoClass, 'json' ); // 执行验证 $errors = $this->validator->validate($dto); if (count($errors) > 0) { // 可自定义错误格式,比如返回结构化的JSON错误 throw new BadRequestHttpException((string) $errors); } yield $dto; } }
步骤3:控制器直接类型提示使用
<?php namespace App\Controller; use App\Dto\CreatePostDto; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; class PostController extends AbstractController { #[Route('/api/posts', methods: ['POST'])] public function store(CreatePostDto $postDto): JsonResponse { // 到此处请求已经验证通过,直接使用DTO内的合法数据即可 $validatedData = [ 'title' => $postDto->title, 'body' => $postDto->body ]; // 后续业务逻辑,比如数据持久化 return new JsonResponse(['status' => 'success']); } }
方案2:表单类验证
适合同时做前后端混合开发的场景,验证规则收敛在表单类型类中,控制器代码同样简洁:
步骤1:创建表单类型类
<?php namespace App\Form; use App\Entity\Post; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints as Assert; class PostType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('title', options: [ 'constraints' => [ new Assert\NotBlank(), new Assert\Length(max: 255), new Assert\Unique(entity: Post::class, field: 'title') ] ]) ->add('body', options: [ 'constraints' => [ new Assert\NotBlank() ] ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Post::class, 'csrf_protection' => false, // API场景关闭CSRF校验 ]); } }
步骤2:控制器使用
#[Route('/api/posts', methods: ['POST'])] public function store(Request $request): JsonResponse { $post = new Post(); $form = $this->createForm(PostType::class, $post); $form->submit(json_decode($request->getContent(), true)); if (!$form->isValid()) { return new JsonResponse(['errors' => (string) $form->getErrors(true, false)], 400); } // 验证通过,直接持久化$post对象即可 $this->getDoctrine()->getManager()->persist($post); $this->getDoctrine()->getManager()->flush(); return new JsonResponse(['status' => 'success']); }
方案3:控制器内手动验证(不推荐,不符合瘦控制器理念)
和Laravel中$request->validate()写法接近,适合临时小范围使用:
#[Route('/api/posts', methods: ['POST'])] public function store(Request $request, ValidatorInterface $validator): JsonResponse { $data = json_decode($request->getContent(), true); $constraints = new Assert\Collection([ 'title' => [ new Assert\NotBlank(), new Assert\Length(max: 255), new Assert\Unique(entity: Post::class, field: 'title') ], 'body' => [new Assert\NotBlank()] ]); $errors = $validator->validate($data, $constraints); if (count($errors) > 0) { return new JsonResponse(['errors' => (string) $errors], 400); } // 验证通过,使用$data执行业务逻辑 return new JsonResponse(['status' => 'success']); }
内容的提问来源于stack exchange,提问作者Станислав Строянецкий
相关产品推荐
相关产品推荐

