someblackmagic/tactician-bundle

Tactician 与 Symfony 项目集成的工具包

安装: 2

依赖者: 0

建议者: 0

安全: 0

星标: 0

关注者: 1

分支: 43

类型:symfony-bundle

v1.2.1 2020-08-27 19:39 UTC

README

Build Status Scrutinizer Code Quality

Symfony2 Bundle 用于 Tactician 库 https://github.com/thephpleague/tactician/

安装

步骤 1:下载 Bundle

打开命令行控制台,进入您的项目目录,然后执行以下命令以下载此 Bundle 的最新稳定版本

$ composer require league/tactician-bundle

此命令要求您全局安装了 Composer,具体请参阅 Composer 文档中的安装章节

步骤 2:启用 Bundle

然后,通过将其添加到项目 app/AppKernel.php 文件中注册的 Bundle 列表中来启用该 Bundle

<?php
// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...

            new League\Tactician\Bundle\TacticianBundle(),
        );

        // ...
    }

    // ...
}

使用 Command Bus

创建一个服务并注入 command bus

services:
    app.your_controller:
        class: AppBundle\Controller\YourNameController
        arguments:
            - '@tactician.commandbus'

然后将命令传递给 command bus 以执行

<?php namespace AppBundle\Controller;

use League\Tactician\CommandBus;
use AppBundle\Commands\DoSomethingCommand;

class YourNameController
{
    private $commandBus;

    public function __construct(CommandBus $commandBus)
    {
        $this->commandBus = $commandBus;
    }

    public function doSomething()
    {
        $command = new DoSomethingCommand();
        $this->commandBus->handle($command);
    }
}

自动注入

如果启用了 Symfony 的自动注入功能(从 Symfony 2.8 版本开始提供),您可以使用以下方式注入和使用默认 commandbus,而不是为每个控制器创建一个服务

<?php namespace AppBundle\Controller;

use League\Tactician\CommandBus;
use AppBundle\Commands\DoSomethingCommand;

class YourNameController
{
    public function doSomething(CommandBus $commandBus)
    {
        $command = new DoSomethingCommand();
        $commandBus->handle($command);
    }
}

请注意,此方法仅适用于默认 commandbus,如果您想注入除默认以外的其他 bus,可以通过使用别名覆盖配置

services:
    League\Tactician\CommandBus: '@tactician.commandbus.your_commandbus'

如果您有多个 bus,您可以使用命名参数别名,通过使用 bus 名称作为参数名称的一部分来实现。例如,如果您想注入名为 default 的 command bus,则参数名称应为 defaultBus。语法始终遵循 {bus_name}Bus

此功能从 Symfony 4.2 版本开始提供

<?php namespace AppBundle\Controller;

use League\Tactician\CommandBus;

class YourNameController
{
    public function doSomething(CommandBus $defaultBus)
    {
        //
    }
}

配置 Command 处理器

当您将命令传递给 Tactician 时,最终目的是将其映射到 Handler。

由于处理器通常有额外的依赖项,并且最好按需加载,因此您需要在服务容器中注册它们。

有多种方法可以将您的命令映射到处理器,所有这些方法都可以组合使用。我们将在下面介绍它们

1. 手动映射

假设我们有两个类,RegisterUserCommandRegisterUserHandler。我们将 Handler 注册到服务容器中,以及它需要的存储库。

foo.user.register_user_handler:
    class: Foo\User\RegisterUserHandler
    arguments:
        - '@foo.user.user_repository'

但是,我们仍然需要将 Command 映射到 Handler。我们可以通过在 Handler 的 DI 定义中添加一个标记来实现这一点。

该标记应有两个属性:标记名称,它始终是 tactician.handler,以及命令,它是 Command 的完全限定名称(FQCN)。

<?php
foo.user.register_user_handler:
    class: Foo\User\RegisterUserHandler
    arguments:
        - '@foo.user.user_repository'
    tags:
        - { name: tactician.handler, command: Foo\User\RegisterUserCommand }

2. 基于类型提示映射

而不是重复命令的完整类名,我们还可以通过反射处理器的方法类型提示来实现。

foo.user.register_user_handler:
    class: Foo\User\RegisterUserHandler
    arguments:
        - '@foo.user.user_repository'
    tags:
        - { name: tactician.handler, typehints: true }

这通过检查类的方法定义来检测处理器接收哪些命令。匹配规则如下:

  1. 方法必须是公共的。
  2. 方法必须只接受一个参数。
  3. 参数必须具有类名类型提示。

换句话说,RegisterUserHandler 类应如下所示

<?php
class RegisterUserHandler
{
    public function handle(RegisterUser $command)
    {
       // do stuff
    }
}

如果多个命令进入单个处理器,只要它们遵循上述规则,它们都会被检测到。实际方法名称并不重要。

如果您正在使用类型提示和 FQCN 映射,则 FQCN 映射始终优先。

如果使用最新版本的 Symfony 中的自动注入功能,则注册基于类型提示非常有用。

3. 自定义映射规则

如果您想为容器中自动映射命令到处理器定义自己的规则,您同样可以做到。

首先,实现HandlerMapping接口。在编译时,您将接收到一个ContainerBuilder和一个Tactician 路由对象,您可以使用它将您的命令映射到您的处理器服务。

您的策略很可能涉及使用某种类型的容器标签。如果是这种情况,请考虑扩展TagBasedMapping抽象类。这将为您处理多个总线节省一些样板代码。

一旦您的对象准备就绪,在设置AppKernel.php时将其传递给TacticianBundle实例。

<?php
// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new League\Tactician\Bundle\TacticianBundle(
                new My\Custom\HandlerMapping()
            ),
        );
    }
}

4. 组合映射策略

如果您想将多个策略链式调用,可以使用CompositeMapping对象将它们链接起来。

<?php
// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new League\Tactician\Bundle\TacticianBundle(
                new League\Tactician\Bundle\DependencyInjection\HandlerMapping\CompositeMapping(
                    new League\Tactician\Bundle\DependencyInjection\HandlerMapping\ClassNameMapping(), // standard command: "FQCN" mapping
                    new League\Tactician\Bundle\DependencyInjection\HandlerMapping\TypeHintMapping(), // standard typehints: true mapping
                    new My\Custom\HandlerMapping() // your custom routing
                )
            ),
        );
    }
}

如果有多个HandlerMapping策略检测到相同的Command,但不同的Handlers,那么最后提到的映射策略获胜。因此,通常最好将您的自定义策略放在最后,或者将ClassNameMapping放在最后,这样在必要时您可以进行完全覆盖。

5. 编写您自己的中间件

记住,Tactician完全基于中间件。如果您不想处理所有这些,并且您有基于简单约定映射命令到处理器的简单方法,只需编写自己的中间件来执行处理器

检查您的连接

您可以使用debug:tactician命令来获取哪些命令被映射到哪些服务。

配置中间件

在Tactician中的所有内容都是一个中间件插件。如果没有配置任何中间件,当您将命令传递给$commandBus->handle()时,什么都不会发生。

默认情况下,启用的唯一中间件是命令处理器支持。您可以在app/config.yml中覆盖此设置并添加自己的中间件。

tactician:
    commandbus:
        default:
            middleware:
                # service ids for all your middlewares, top down. First in, last out.
                - tactician.middleware.locking
                - my.custom.middleware.plugin
                - tactician.middleware.command_handler

重要:添加您自己的中间件绝对是被鼓励的,只是请确保始终添加tactician.middleware.command_handler作为最后的中间件。否则,您的命令实际上不会被执行。

查看Tactician文档获取更多信息以及中间件的完整列表。

配置多个命令总线

此包预先配置了一个名为“default”的命令总线,服务ID为tactician.commandbus。尽管如此,一些用户希望配置多个命令总线。

假设您正在将远程会计系统集成到您的应用程序中,并且您想为这些命令使用单独的命令总线。您可以像这样连接两个命令总线

您可以通过配置来完成此操作,如下所示

tactician:
    commandbus:
        default:    # the "regular" command bus in your application
            middleware:
                - tactician.middleware.validator
                - tactician.middleware.command_handler
        accounting: # the command bus for accounting specific commands
            middleware:
                - tactician.middleware.locking
                - some.middleware.service.to.call.the.remote.accounting.app
                - tactician.commandbus.accounting.middleware.command_handler # Because "tactician.middleware.command_handler" refers to the default bus

配置定义了两个总线:“default”和“accounting”。这些总线将分别注册为服务tactician.commandbus.defaulttactician.commandbus.accounting

请注意,每个总线都为其配置了各自的命令处理器中间件:默认情况下为tactician.middleware.command_handler,会计为tactician.commandbus.accounting.middleware.command_handler

如果您想,您还可以更改注册到tactician.commandbus的命令处理器。您可以通过设置配置中的default_bus值来完成此操作

tactician:
    default_bus: accounting
    commandbus:
        default:
            middleware:
                # ...
        accounting:
            middleware:
                # ...

默认情况下,每个总线中都有所有命令。如果您想使命令仅对特定总线可用,您需要指定其ID

foo.user.register_user_handler:
    class: Foo\User\RegisterUserHandler
    arguments:
        - '@foo.user.user_repository'
    tags:
        - { name: tactician.handler, command: Foo\User\RegisterUserCommand, bus: accounting }

然后您将能够仅在会计总线上处理此命令

$bus = $container->get('tactician.commandbus.accounting');
$bus->handle(new Foo\User\RegisterUserCommand('my', 'arguments'));

额外捆绑的中间件

这个包包含了一些预配置的中件件。要启用它们,请将它们添加到您的总线配置中的中件件列表(参见配置中件件)。

验证器中件件(tactician.middleware.validator)

此中件件使用Symfony的验证器来检查在传递之前命令对象。在实践中,这意味着您可以在命令上添加任何Symfony验证器注解,以确保在执行之前完全正确。这并不是完全替代编写内部一致风格的对象的方法,但它非常有帮助。

可以通过配置或类似于默认的Symfony实践中的注解添加约束,请参阅他们的文档

如果命令失败,它将抛出League\Tactician\Bundle\Middleware\InvalidCommandException。此异常还包含由验证器产生的ConstraintViolationList,因此您可以检查或记录错误。

锁定中件件(tactician.middleware.locking)

此中件件包含在Tactician中,请参阅官方文档以获取详细信息。

记录器中件件(tactician.middleware.logger)

此中件件包含在Tactician中,请参阅官方文档以获取详细信息。

安全中件件(tactician.middleware.security)

安全中件件将对处理所有命令执行授权。默认情况下,如果用户未经授权,将抛出AccessDenied异常。

tactician:
    security:
        My\User\Command:
            - 'ROLE_USER'
        My\Admin\Command:
            - 'ROLE_ADMIN'
        My\UserAndAdmin\Command:
            - 'ROLE_USER'
            - 'ROLE_ADMIN'

此中件件基于Symfony的AccessDecisionManager和投票系统。在使用此中件件之前,我们建议您熟悉它。如果您想配置更复杂的场景,考虑实现自定义的Symfony投票者。

作为预防措施,中件件需要您在它被Symfony的AccessDecisionManager评估之前,在安全配置中注册命令。

此外,尽管安全中件件基于可信组件,但我们始终建议采取多层次防御策略。仅将您的命令总线连接到公共Web端点并完全依赖此中件件可能不足以满足您应用程序的需求。

安全中件件默认禁用。

命令处理器中件件(tactician.middleware.command_handler)

始终确保这是列表中最后的中件件

这是将命令与处理器匹配并执行它的插件。如果您有复杂的匹配逻辑,可以自由实现自己的变体并省略此中件件。

然而,对于99%的用户,这应该被启用并设置为列表中的最后一个中件件。

自定义tactician.middleware.command_handler中件件使用的MethodNameInflector

默认情况下,该包使用来自Tactician核心的HandleInflector。也就是说,它期望您的命令处理器具有接收要执行的命令的handle()方法。

但是,如果您更喜欢不同的inflector,您可以在config.yml中传递服务名称。

tactician:
    method_inflector: my_inflector.service.id

Tactician核心提供一系列自定义Inflectors,所有这些都在本包中得到支持。假设有一个名为My\App\RegisterUserCommand()的类,处理器上调用的方法将是

  • tactician.handler.method_name_inflector.handle - handle()
  • tactician.handler.method_name_inflector.handle_class_name - handleRegisterUserCommand()
  • tactician.handler.method_name_inflector.handle_class_name_without_suffix - handleRegisterUser()
  • tactician.handler.method_name_inflector.invoke - __invoke()

虽然 handle() 是一个合理的默认选项,但使用类名方法之一可以让你在单个类上处理多个命令(如果它们共享共同的依赖项或有某种形式的匹配,这可能很有用)。同样,如果要将映射到闭包列表,那么 __invoke 可能很有用。

当使用多个总线时,您还可以指定特定总线的 method_inflector

tactician:
    commandbus:
        command:
            middleware:
                - tactician.middleware.command_handler
        query:
            middleware:
                - tactician.middleware.command_handler
            method_inflector: tactician.handler.method_name_inflector.handle_class_name_without_suffix

测试

$ ./vendor/bin/phpunit

安全

披露信息可以在 Tactician 主仓库 中找到。

许可证

MIT 许可证 (MIT)。有关更多信息,请参阅 许可证文件