someblackmagic / tactician-bundle
Tactician 与 Symfony 项目集成的工具包
Requires
- php: >=7.0
- league/tactician: ^1.0
- league/tactician-container: ^2.0
- league/tactician-logger: ^0.10.0
- symfony/config: ^2.8|^3.3|^4.0|^5.0
- symfony/dependency-injection: ^2.8|^3.3|^4.0|^5.0
- symfony/http-kernel: ^2.8|^3.3|^4.0|^5.0
- symfony/yaml: ^2.8|^3.3|^4.0|^5.0
Requires (Dev)
- league/tactician-doctrine: ^1.1.1
- matthiasnoback/symfony-config-test: ^3.0
- matthiasnoback/symfony-dependency-injection-test: ^2.1
- mockery/mockery: ~1.0
- phpunit/phpunit: ~6.1
- symfony/console: ^2.8|^3.3|^4.0|^5.0
- symfony/framework-bundle: ^2.8.15|^3.3|^4.0|^5.0
- symfony/security: ^2.8|^3.3|^4.0|^5.0
- symfony/security-bundle: ^2.8|^3.3|^4.0|^5.0
- symfony/validator: ^2.8|^3.3|^4.0|^5.0
Suggests
- league/tactician-doctrine: For doctrine transaction middleware
- symfony/console: For debugging command-to-handler routing using the tactician:debug console command
- symfony/security: For command security middleware
- symfony/validator: For command validator middleware
This package is auto-updated.
Last update: 2024-08-28 04:48:27 UTC
README
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. 手动映射
假设我们有两个类,RegisterUserCommand
和 RegisterUserHandler
。我们将 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 }
这通过检查类的方法定义来检测处理器接收哪些命令。匹配规则如下:
- 方法必须是公共的。
- 方法必须只接受一个参数。
- 参数必须具有类名类型提示。
换句话说,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.default
和tactician.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)。有关更多信息,请参阅 许可证文件。