wizzaq / rest-bundle
Wizzaq RestBundle
Requires
- php: ^8.0
- ext-json: *
- symfony/cache: ^6.0
- symfony/config: ^6.0
- symfony/dependency-injection: ^6.0
- symfony/framework-bundle: ^6.0
- symfony/polyfill-php81: 1.x-dev
Requires (Dev)
- doctrine/coding-standard: ^9.0
- doctrine/orm: ^2.10 || ^3.0
- friendsofphp/proxy-manager-lts: ^1.0
- phpunit/phpunit: ^7.5 || ^8.0 || ^9.3 || ^10.0
- psalm/plugin-phpunit: ^0.16.1
- psalm/plugin-symfony: ^3
- psr/log: ^1.1.4|^2.0|^3.0
- symfony/deprecation-contracts: ^2.1|^3
- symfony/doctrine-bridge: ^6.0
- symfony/form: ^6.0
- symfony/phpunit-bridge: ^6.0
- symfony/property-info: ^6.0
- symfony/proxy-manager-bridge: ^6.0
- symfony/security-bundle: ^6.0
- symfony/twig-bridge: ^6.0
- symfony/validator: ^6.0
- symfony/web-profiler-bundle: ^6.0
- symfony/yaml: ^6.0
- twig/twig: ^1.34|^2.12|^3.0
- vimeo/psalm: ^4.7
Suggests
- symfony/doctrine-bridge: To use ProcessForm argument resolver.
- symfony/form: To use ProcessForm argument resolver.
README
这是您一直在寻找的REST bundle。
使用方法
ProcessForm 参数
将典型表单处理包装成更干净的行动代码。
配置
# config/packages/wizzaq_rest.yaml wizzaq_rest: use_resolvers: true # set to false to completely disable resolvers
简单示例
典型控制器的简单示例。
<?php namespace App\Controller; use App\Form\Filter\MyEntityFilterType; use App\Form\Dto\Filter\MyEntityFilter; use App\Repository\MyEntityRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Wizzaq\RestBundle\Attribute\ProcessForm; class MyEntityController extends AbstractController { #[Route('/my-entity', methods: 'GET')] public function list( #[ProcessForm(MyEntityFilterType::class, mapEntity: false, throwOnNotValid: false)] ?MyEntityFilter $filter = null, MyEntityRepository $repository ): Response { return $this->json(['list' => $repository->list($filter ?? new MyEntityFilter())]); } #[Route('/my-entity', methods: 'POST')] #[Route('/my-entity/{id}', methods: 'PUT')] public function edit( #[ProcessForm(MyEntityType::class)] MyEntity $entity, EntityManagerInterface $em ): Response { // $entity is processed by form and validated // Wizzaq\RestBundle\Exception\FormValidationException will be thrown if validation failed $em->persist($entity); $em->flush(); return new JsonResponse(['id' => $entity->getId()]); } }
所有选项
在 ProcessForm 属性上有许多选项可供控制行为
form:string(必填)要处理的表单类型类名
#[Route('/my-entity/{id}', methods: 'PUT')] public function edit( #[ProcessForm(form: MyEntityType::class)] MyEntity $entity, EntityManagerInterface $em ): Response { // $entity is processed by form and validated // Wizzaq\RestBundle\Exception\FormValidationException will be thrown if validation failed $em->persist($entity); $em->flush(); return new JsonResponse(['id' => $entity->getId()]); }
mapEntity:null|bool(默认:null)如果不为空,则严格通知
ProcessFormResolver是否使用EntityValueResolver在表单处理之前解析值。
如果为false,则将null用作创建表单的初始值。
如果为null,则ProcessFormResolver将尝试猜测。如果动作同时支持创建和编辑,则可能很有用。⚠️ 我们建议您始终明确指定此选项。
#[Route('/my-entity/{id}', methods: 'PUT')] public function edit( #[ProcessForm(form: MyEntityType::class, mapEntity: true)] MyEntity $entity, EntityManagerInterface $em ): Response { // $entity is processed by form and validated // Wizzaq\RestBundle\Exception\FormValidationException will be thrown if validation failed $em->persist($entity); $em->flush(); return new JsonResponse(['id' => $entity->getId()]); }
submit:bool(默认false)如果为真,则使用
$form->submit($data, false)(是的,带有$clearMissing=false) 而不是默认的$form->handleRequest($request)#[Route('/my-entity', methods: 'GET')] public function list( #[ProcessForm(MyEntityFilterType::class, submit: true)] ?MyEntityFilter $filter = null, MyEntityRepository $repository ): Response { return $this->json(['list' => $repository->list($filter ?? new MyEntityFilter())]); }
throwOnNotValid:bool(默认true)默认情况下,如果验证失败,则
ProcessFormResolver将引发FormValidationException。将
throwOnNotValid设置为false,如果您想在动作内部处理无效表单。处理后的表单可用
\Wizzaq\RestBundle\Config\RestConfig::processedForm($request)... use Wizzaq\RestBundle\Config\RestConfig; ... #[Route('/my-entity/{id}', methods: ['GET', 'POST'])] public function edit( #[ProcessForm(form: MyEntityType::class, throwOnNotValid: false)] ?MyEntity $entity = null, Request $request, RestConfig $restConfig, EntityManagerInterface $em ): Response { if (null === $entity) { $this->addFlash( 'error', 'Entity not found!' ); return new RedirectResponse('/my-entity'); } $form = $restConfig->processedForm($request); if ($form->isSubmitted() && $form->isValid()) { // do some stuff here $em->persist($entity); $em->flush(); return new RedirectResponse('/my-entity'); } return $this->render('my_entity/edit.html.twig', [ 'form' => $form, ]); }继承自 MapEntity
id如果配置了
id选项并且与路由参数匹配,则解析器将通过主键找到它
映射配置用于
findOneBy()方法的属性和值:键是路由占位符名称,值是Doctrine属性名称
排除通过 排除 一个或多个属性来配置应用于
findOneBy()方法的属性,以便不使用 所有 属性
stripNull如果为真,则当使用
findOneBy()时,任何值为null的值都将不会用于查询。
objectManager默认情况下,
EntityValueResolver使用默认对象管理器,但您可以配置此设置
evictCache如果为真,将强制Doctrine始终从数据库而不是缓存中获取实体。
disabled如果为真,则
ProcessFormResolver将不会尝试替换参数。
resolver默认情况下,将使用
ProcessFormResolver来解析参数,但您可以配置此设置
它如何工作。
ProcessForm 参数扩展 MapEntity 并使用 EntityValueResolver 查找现有实体,正如它们的文档中所描述的那样。
在 ProcessFormResolver 使用从 EntityValueResolver 返回的对象(或 null)创建表单后
- 作为
$data - 如果方法不是
GET,则表单的当前method将替换为$request中的实际方法
并根据定义的选项进行处理。
如果表单有效,则返回表单中的处理数据。
如果没有,则抛出 FormValidationException 或者在 throwOnNotValid 为 false 时返回找到的对象
协议
在开发API时,我们都有一个主要监听器来从JSON解码负载并将其放回请求以进行处理,例如
public function onRequest(RequestEvent $event): void { $request = $event->getRequest(); if ('' === $request->getContent()) { return; } if ('json' !== $request->getContentTypeFormat()) { return; } try { $request->request->replace($request->toArray()); } catch (JsonException $e) { throw new BadRequestHttpException('Unable to parse request.', $e); } }
或者在操作中每次都解码它 🤔
现在您可以使用 WizzaqRestBundle 通过两种方式来自动化这个过程
⚠️ 默认配置启用协议处理
-
简单地在
config/routes.yaml中将路由默认选项添加_rest: truecontrollers: resource: ../src/Controller/Api/ type: attribute prefix: /api defaults: { _rest: true }
-
使用
Rest参数。可以应用于类或方法<?php namespace App\Controller; use App\Form\Filter\MyEntityFilterType; use App\Form\Dto\Filter\MyEntityFilter; use App\Repository\MyEntityRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bridge\Twig\Attribute\Template; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Attribute\Route; use Wizzaq\RestBundle\Attribute\ProcessForm; use Wizzaq\RestBundle\Attribute\Rest; use Wizzaq\RestBundle\Exception\FormValidationException; class MyEntityController extends AbstractController { #[Route('/my-entity', methods: 'GET')] #[Rest(responseSerializationGroups: 'default')] public function list( #[ProcessForm(form: MyEntityFilterType::class, throwOnNotValid: false)] ?MyEntityFilter $filter = null, MyEntityRepository $repository ): array { return ['list' => $repository->list($filter ?? new MyEntityFilter())]; } #[Route('/my-entity/{id}', name: 'app.my_entity.get_or_update', methods: ['GET', 'POST'])] #[Route('/api/my-entity/{id}', name: 'api.my_entity.get_or_update', methods: ['GET', 'POST'])] #[Rest(routes: 'api.my_entity.get_or_update', responseSection: 'entity', responseSerializationGroups: ['with_related', 'default'])] #[Template('my_entity/edit.html.twig')] public function edit( #[ProcessForm(MyEntityType::class, throwOnNotValid: false)] MyEntity $entity, Request $request, RestConfig $restConfig, EntityManagerInterface $em ): array { $form = $restConfig->processedForm($request); if ($form->isSubmitted() && $form->isValid()) { // do some stuff here $em->persist($entity); $em->flush(); return $restConfig->isRest($request) ? ['entity' => $entity] : new RedirectResponse('/my-entity'); } elseif ($form->isSubmitted() && !$form->isValid() && $restConfig->isRest($request)) { throw new FormValidationException($form); } return [ 'form' => $form, 'entity' => $entity, ] } }
配置选项
完整的配置选项
# config/packages/wizzaq_rest.yaml wizzaq_rest: use_protocols: true # set to false to completely disable `ProtocolListener` default_protocol: null # uses first defined protocol by default protocols: # available protocols rest: true # only one simple protocol defined by bundle right now ^_^ default_response_section: null # if not null, then only defined section of returned result will be used as response for rest route serializer: null # serializer service id (autoselect by default from jms_serializer/serializer)
Rest参数选项
routes:null|string|array仅应用于定义的路由
protocol:?string要使用的协议名称
responseSection:?string仅使用返回结果的定义部分作为响应
responseSerializationGroups:null|string|array在通过协议序列化响应时传递定义的序列化组到序列化器
创建自己的协议
如果您想创建自己的协议,只需实现 Wizzaq\RestBundle\Protocol\NamedProtocolInterface 并通过自动配置将其标记为协议
namespace App\Protocol; use Wizzaq\RestBundle\Config\RestConfig; use Wizzaq\RestBundle\Protocol\NamedProtocolInterface; use Wizzaq\RestBundle\Protocol\RestProtocol; class MyProtocol extends RestProtocol implements NamedProtocolInterface { public function __construct(RestConfig $restConfig, bool $debug = false, $serializer = null) { parent::__construct($restConfig, $debug, $serializer); } public function getProtocolName(): string { return 'my_rest'; } public function processResponse($response, Request $request): Response { return parent::processResponse(['success' => true, 'data' => $response], $request); } }
或者实现 Wizzaq\RestBundle\Protocol\ProtocolInterface 并使用 alias 属性手动标记服务
# config/services.yaml ... services: ... App\Protocol\MyProtocol: tags: - { name: 'wizzaq_rest.protocol', alias: 'my_rest' }
然后您可以禁用不必要的默认协议
# config/packages/wizzaq_rest.yaml wizzaq_rest: default_protocol: 'my_rest' # not nessesary if you have only one protocol protocols: rest: false
附加功能
循环引用处理器
为 Symfony 序列化器 提供丢失的 CircularReferenceHandler。
# config/packages/framework.yaml framework: ... serializer: circular_reference_handler: 'wizzaq_rest.serializer.circular_reference_handler'