webmunkeez/adr-bundle

为Symfony设计的Action-Domain-Responder模式。

安装: 113

依赖项: 0

建议者: 0

安全: 0

星标: 1

关注者: 1

分支: 0

开放问题: 0

类型:symfony-bundle


README

此包在Symfony应用程序中释放了Action-Domain-Responder模式。

安装

使用Composer安装此包

$ composer require webmunkeez/adr-bundle

将包添加到您的应用程序内核中

// config/bundles.php

return [
    // ...
    Webmunkeez\ADRBundle\WebmunkeezADRBundle::class => ['all' => true],
    // ...
];

使用

动作

动作是一个可调用的类,必须实现\Webmunkeez\ADRBundle\Action\ActionInterface

final class StoryDetailAction implements \Webmunkeez\ADRBundle\Action\ActionInterface
{
    public function __invoke(): Response
    {
        return $this->render($data);
    }
    
    public function render(?\Webmunkeez\ADRBundle\Response\ResponseDataInterface $data = null): Response
    {
        return new Response(...);
    }
}

但,它可以是一个更经典的控制器,实现相同的接口

final class StoryController implements \Webmunkeez\ADRBundle\Action\ActionInterface
{
    public function detail(): Response
    {
        return $this->render($data);
    }
    
    public function render(?\Webmunkeez\ADRBundle\Response\ResponseDataInterface $data = null): Response
    {
        return new Response(...);
    }
}

(每个实现ActionInterface的服务都自动标记为controller.service_arguments)

响应者

响应者是服务,它接收数据(一个实现\Webmunkeez\ADRBundle\Response\ResponseDataInterface的对象)并将其返回为Symfony\Component\HttpFoundation\Response实例。
它可以是一个包含HTML或JsonResponse或任何你想要的内容的响应,只要它是Symfony\Component\HttpFoundation\Response实例。

在此包中,有一个响应者管理器\Webmunkeez\ADRBundle\Response\Responder,您可以将其注入到您的动作(或控制器)中。

响应者管理器获取您的应用程序中的所有响应者(它使用编译器传递来获取所有标记为webmunkeez_adr.responder的服务并按优先级排序)并找到正确的响应者以渲染响应。

final class StoryDetailAction implements \Webmunkeez\ADRBundle\Action\ActionInterface
{
    private \Webmunkeez\ADRBundle\Response\Responder $responder;
    
    public function __construct(\Webmunkeez\ADRBundle\Response\Responder $responder)
    {
        $this->responder = $responder
    }
    
    public function __invoke(): Response
    {
        return $this->render($data);
    }
    
    public function render(?\Webmunkeez\ADRBundle\Response\ResponseDataInterface $data = null): Response
    {
        return $this->responder->render($data);
    }
}

您可以使用\Webmunkeez\ADRBundle\Response\ResponderAwareInterface\Webmunkeez\ADRBundle\Response\ResponderAwareTrait来自动注入响应者

final class StoryDetailAction implements \Webmunkeez\ADRBundle\Action\ActionInterface, \Webmunkeez\ADRBundle\Response\ResponderAwareInterface
{
    use \Webmunkeez\ADRBundle\Response\ResponderAwareTrait;
    
    public function __invoke(): Response
    {
        return $this->render($data);
    }
    
    public function render(?\Webmunkeez\ADRBundle\Response\ResponseDataInterface $data = null): Response
    {
        return $this->responder->render($data);
    }
}

您还可以使用\Webmunkeez\ADRBundle\Action\ActionTrait来简化代码

final class StoryDetailAction implements \Webmunkeez\ADRBundle\Action\ActionInterface, \Webmunkeez\ADRBundle\Response\ResponderAwareInterface
{
    use \Webmunkeez\ADRBundle\Response\ResponderAwareTrait;
    use \Webmunkeez\ADRBundle\Action\ActionTrait;
    
    public function __invoke(): Response
    {
        return $this->render($data);
    }
}

或者直接扩展\Webmunkeez\ADRBundle\Action\AbstractAction

final class StoryDetailAction extends \Webmunkeez\ADRBundle\Action\AbstractAction
{
    public function __invoke(): Response
    {
        return $this->render($data);
    }
}

响应者是实现\Webmunkeez\ADRBundle\Response\ResponderInterface的类(因此,它们自动标记为webmunkeez_adr.responder

final class XmlResponder implements \Webmunkeez\ADRBundle\Response\ResponderInterface
{
    private RequestStack $requestStack;
    private SerializerInterface $serializer;

    public function __construct(RequestStack $requestStack, SerializerInterface $serializer)
    {
        $this->requestStack = $requestStack;
        $this->serializer = $serializer;
    }

    public function supports(): bool
    {
        return 'xml' === $this->requestStack->getCurrentRequest()->getPreferredFormat();
    }

    public function render(?\Webmunkeez\ADRBundle\Response\ResponseDataInterface $data = null): Response
    {
        $xml = $this->serializer->serialize($data, 'xml');

        $response = new Response($xml);
        $response->headers->set('Content-Type', 'text/xml');

        return $response;
    }
}

如您所见,有两个方法:supports定义激活响应者的条件,以及render来创建响应。

核心响应者

提供了两个核心响应者

HtmlResponder

\Webmunkeez\ADRBundle\Response\HtmlResponder使用Twig渲染HTML模板。要指定模板,您必须使用\Webmunkeez\ADRBundle\Attribute\Template

use Webmunkeez\ADRBundle\Attribute\Template;

#[Template('story/detail.html.twig')]
final class StoryDetailAction implements \Webmunkeez\ADRBundle\Action\ActionInterface
{
    ...
}

如果请求包含HTTP_ACCEPT text/html头,则此响应者处于活动状态(警告:此响应者需要一个twig模板,否则将抛出\Webmunkeez\ADRBundle\Exception\RenderingException异常)。
其优先级为priority: -20

JsonResponder

\Webmunkeez\ADRBundle\Response\JsonResponder使用Serializer渲染JSON(您可以使用\Webmunkeez\ADRBundle\Attribute\SerializationContext指定序列化上下文)

use Webmunkeez\ADRBundle\Attribute\SerializationContext;

#[SerializationContext(['groups' => 'group_one'])]
final class StoryDetailAction implements \Webmunkeez\ADRBundle\Action\ActionInterface
{
    ...
}

如果请求包含HTTP_ACCEPT application/json头,则此响应者处于活动状态。
其优先级为priority: -10

自定义响应者

您可以根据我之前的XmlResponder示例编写自己的响应者,通过实现\Webmunkeez\ADRBundle\Response\ResponderInterface

实现此接口的服务将自动标记为webmunkeez_adr.responder,优先级为0,您可以更改它(在您的service.yaml中或通过静态的getDefaultPriority方法;参见https://symfony.com.cn/doc/current/service_container/tags.html#tagged-services-with-priority)。

您可以定义“通用”响应者,如html、json、xml等。但您也可以定义更具体的,通过检查$request->attributes->get('_controller')来仅使响应者为特定动作

final class CustomResponder implements \Webmunkeez\ADRBundle\Response\ResponderInterface
{
    private RequestStack $requestStack;
    private Environment $twig;

    public function __construct(RequestStack $requestStack, Environment $twig)
    {
        $this->requestStack = $requestStack;
        $this->twig = $twig;
    }

    public function supports(): bool
    {
        $controller = $this->requestStack->getCurrentRequest()->attributes->get('_controller');
        $actionClass = false !== strpos($controller, '::') ? substr($controller, 0, strpos($controller, '::')) : $controller;

        return CustomResponderAction::class === $actionClass;
    }

    public function render(?\Webmunkeez\ADRBundle\Response\ResponseDataInterface $data = null): Response
    {
        $data = array_merge($data, ['customResponder' => true]);

        $html = $this->twig->render($this->requestStack->getCurrentRequest()->attributes->get('_template_path'), $data);

        return new Response($html);
    }
}

渲染异常监听器

如果有未捕获的\Webmunkeez\ADRBundle\Exception\RenderingException,它将被此监听器捕获,并抛出一个包含原始异常的\Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException

异常监听器

如果存在未被捕获的 \Throwable,它将被此监听器捕获,该监听器将抛出包含原始异常的 \Symfony\Component\HttpKernel\Exception\BadRequestHttpException

HTTP 异常监听器

如果您请求一个带有 HTTP_ACCEPT application/json 头的 Action,并且如果该 Action 抛出一个实现了 \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface 的异常,其内容将自动以 JSON 读取格式序列化到 Response 响应体的内容中。