wizzaq/rest-bundle

Wizzaq RestBundle

安装: 3

依赖项: 0

建议者: 0

安全: 0

星标: 0

关注者: 1

分支: 0

开放问题: 0

类型:symfony-bundle

0.1.0 2024-01-20 15:43 UTC

This package is auto-updated.

Last update: 2024-09-20 17:47:17 UTC


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 或者在 throwOnNotValidfalse 时返回找到的对象

协议

在开发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 通过两种方式来自动化这个过程

⚠️ 默认配置启用协议处理

  1. 简单地在 config/routes.yaml 中将路由默认选项添加 _rest: true

    controllers:
      resource: ../src/Controller/Api/
      type: attribute
      prefix: /api
      defaults: { _rest: true }
  2. 使用 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'