league/tactician-bundle

用于将 Tactician 集成到 Symfony 项目的软件包

安装次数: 8,077,007

依赖项: 26

建议者: 1

安全: 0

星标: 245

关注者: 11

分支: 43

开放问题: 5

类型:symfony-bundle

v1.5.1 2024-01-30 18:43 UTC

README

Build Status Scrutinizer Code Quality

Symfony2 Bundle for the Tactician library https://github.com/thephpleague/tactician/

安装

步骤 1: 下载软件包

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

$ composer require league/tactician-bundle

此命令需要您全局安装 Composer,如 Composer 文档中的安装章节中所述。

步骤 2: 启用软件包

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

<?php
// app/AppKernel.php

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

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

        // ...
    }

    // ...
}

使用命令总线

创建一个服务并注入命令总线

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

然后传递一个命令给命令总线以执行

<?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 版本开始提供),则无需为每个控制器创建服务使用默认命令总线,而是可以按如下方式注入和使用它

<?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);
    }
}

请注意,这仅适用于默认命令总线,如果您想注入非默认的总线,您可以通过别名覆盖配置

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

如果您有多个总线,您可以使用命名参数别名,使用总线名称作为参数名称的一部分。例如,如果您想注入名为 default 的命令总线,请将参数命名为 defaultBus。语法始终遵循 {bus_name}Bus

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

<?php namespace AppBundle\Controller;

use League\Tactician\CommandBus;

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

配置命令处理器

当您将命令传递给 Tactician 时,最终目标是将它映射到处理器。

由于处理器通常具有额外的依赖关系并且最好是懒加载的,因此您希望将它们注册在服务容器中。

有几种不同的方法可以将您的命令映射到处理器,所有这些方法都可以组合。以下我们将一一介绍

1. 手动映射

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

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

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

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

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. 自定义映射规则

如果您想为容器中自动映射命令到处理程序定义自己的规则,您也可以做到这一点。

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

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

一旦您的对象准备就绪,在设置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
                )
            ),
        );
    }
}

如果有多个处理程序映射策略检测到相同的命令,但不同的处理程序,那么最后提到的映射策略获胜。因此,通常最好将自定义策略放在最后或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)。有关更多信息,请参阅 许可证文件