veeenex / command-center
Laravel 框架的命令和领域事件
Requires
- php: >=5.6.0
- laravel/framework: 5.3.*
Requires (Dev)
- orchestra/testbench: ^3.1.4
- phpunit/phpunit: 4.*
- scrutinizer/ocular: ~1.1
This package is auto-updated.
Last update: 2024-09-14 00:01:55 UTC
README
该包是从 airbornfoxx/commandcenter 分支出来的
此包提供了一种框架无关的架构,用于在您的应用程序中利用命令和领域事件。任何组件都可以轻松扩展以适应您的特定用途。
灵感来源于并在 Jeffrey Way 的 Laracasts 上得到扩展
安装
通过 Composer 安装 CommandCenter。
添加到 composer.json
"require": { "veeenex/command-center": "~1.0" }
安装
composer require "veeenex/command-center"
Laravel
更新应用程序配置以包含包的服务提供者。
'VeeeneX\CommandCenter\Provider\CommandCenterServiceProvider'
查看此文档末尾的 Laravel 部分,了解更多功能!
用法
在开始之前,不建议在架构不那么重要的较小项目中使用这种方法。此包有助于构建您的业务逻辑,坚持单一职责原则,并保持您的控制器精简。
CommandApplication
为了开始,您必须通过实现包的 CommandApplication
接口并注册任何绑定来将您的特定应用程序与 CommandCenter 进行注册。请使用包含的 Laravel 实现和服务提供者作为其他框架的参考。参考类如下
- Flyingfoxx\CommandCenter\Laravel\Application
- Flyingfoxx\CommandCenter\Laravel\CommandCenterServiceProvider
命令
一切从命令开始。命令是对领域执行的特定操作的“指令”。它表示为一个简单的 DTO(数据传输对象),它携带执行该特定命令所需的数据。
例如,假设您需要注册新用户。您将创建一个 RegisterUserCommand
,它看起来像这样
<?php namespace Foxx\Users; class RegisterUserCommand { public $username; public $password; public function __construct($username, $password) { $this->username = $username; $this->password = $password; } }
以
Foxx
作为示例应用程序
因此,您现在可以将所有逻辑放入控制器中,而不是现在创建一个命令并将数据传递到包含逻辑的处理程序。但是,现在您需要一个传递命令到相应处理程序的方法。那么,公交系统如何呢?
命令总线
首先,您需要将包的 CommandBus
注入到您的控制器中。这将是通过将命令传递到相应处理程序的方式。
<?php use Flyingfoxx\CommandCenter\CommandBus; class RegistrationController { protected $commandBus; public function __construct(CommandBus $commandBus) { $this->commandBus = $commandBus; } }
接下来,您创建并传递命令到命令总线。
<?php use Flyingfoxx\CommandCenter\CommandBus; use Foxx\Users\RegisterUserCommand; class RegistrationController { protected $commandBus; public function __construct(CommandBus $commandBus) { $this->commandBus = $commandBus; } public function store() { // Grab the input (using Laravel in this example) $input = Input::only('username', 'password'); // Create command $command = new RegisterUserCommand($input['username'], $input['password']); // Pass command to command bus $this->commandBus->execute($command); } }
通过这样做,命令总线将命令传递到相应的处理程序,在那里将执行命令的逻辑。
它是通过以下方式将命令类映射到相应的处理程序类的
- RegisterUserCommand => RegisterUserHandler
- PostBlogEntryCommand => PostBlogEntryHandler
请注意,您可以通过实现包的
CommandTranslator
类轻松地更改此映射。不要忘记更新任何应用程序绑定。
命令处理器
现在您需要一个处理命令的处理程序类。这将是命令总线交付命令的地方。如果命令类是 RegisterUserCommand
,则处理程序类必须是 RegisterUserHandler
。
处理程序类必须实现包的 CommandHandler
接口,需要 handle()
方法。
<?php namespace Foxx\Users; use Flyingfoxx\CommandCenter\CommandHandler; class RegisterUserHandler implements CommandHandler { protected $user; public function __construct(User $user) { $this->user = $user; } public function handle($command) { $user = $this->user->register($command->username, $command->password); return $user; } }
它应该可以工作。现在您可以在您的应用程序中利用命令。但是,现在您需要一种方法来挂钩这些命令以执行其他任务。您可以使用领域事件和监听器,只有在事件发生时才会执行这些任务。
事件
领域事件是指领域内发生了重要的事件。从上一个例子继续,一旦执行了 RegisterUserCommand
,就发生了一个事件,即用户已被注册。
因此,您可以调用事件 UserWasRegistered
,它将作为一个简单的 DTO(数据传输对象)来传递事件监听器所需的数据。
<?php namespace Foxx\Events; use Foxx\Users\User; class UserWasRegistered { public $user; public function __construct(User $user) { $this->user = $user; } }
现在,您已经有一个事件了,必须在您的模型类中引发事件(在应用程序中创建事件的实例)。为此,您可以使用包中的 EventGenerator
trait,如下所示
<?php namespace Foxx\Users\User; use Flyingfoxx\CommandCenter\Eventing\EventGenerator; use Foxx\Events\UserWasRegistered; class User { use EventGenerator; protected $username; protected $password; public function register($username, $password) { $this->username = $username; $this->password = $password; $this->raise(new UserWasRegistered($this)); return $this; } }
好的,现在 UserWasRegistered
事件已经引发,并且现在准备好分发(让您的应用程序了解其发生)。您可以在命令处理器类中通过注入包的 EventDispatcher
类并调用 dispatch($events)
方法来完成此操作。
<?php namespace Foxx\Users; use Flyingfoxx\CommandCenter\CommandHandler; use Flyingfoxx\CommandCenter\Eventing\EventDispatcher; class RegisterUserHandler implements CommandHandler { protected $user; protected $dispatcher; public function __construct(User $user, EventDispatcher $dispatcher) { $this->user = $user; $this->dispatcher = $dispatcher; } public function handle($command) { $user = $this->user->register($command->username, $command->password); $this->dispatcher->dispatch($user->releaseEvents()); return $user; } }
别忘了在实体对象上调用
releaseEvents()
(因为它使用了EventGenerator
trait)。这样,所有分发的事件都将从引发的事件中删除。
事件监听器
现在事件已经引发和分发,您需要为该事件注册监听器。
遵循约定,如果我们引发事件 Foxx\Events\UserWasRegistered
,那么要监听的事件名称将是 Foxx.Events.UserWasRegistered
。
下一步是在您的应用程序中注册一个事件监听器类。您可能需要在用户注册后发送一封电子邮件给他们。例如,在 Laravel 中,您可能会这样做
Event::listen('Foxx.Events.UserWasRegistered', 'Foxx\Listeners\EmailNotifier');
或者要注册此监听器以使用任何应用程序事件,您可以尝试这样做
Event::listen('Foxx.Events.*', 'Foxx\Listeners\EmailNotifier');
因此,现在,当在此命名空间下分发任何事件时,此监听器类将触发其 handle()
方法。当然,您可能只想使用此监听器类响应某些事件。您可以使用包的 EventListener
类来完成此操作。
通过简单地扩展此 EventListener
类,您可以创建遵循约定来处理每个特定事件的函数。约定是如果分发的事件是 UserWasRegistered
,则监听器类中触发的方法将是 whenUserWasRegistered
。如果没有找到此方法,它将简单地继续。
有了这些,您的 EmailNotifier
类可能看起来像这样
<?php namespace Foxx\Listeners; use Flyingfoxx\CommandCenter\Eventing\EventListener; use Flyingfoxx\Events\UserWasRegistered; class EmailNotifier extends EventListener { public function whenUserWasRegistered(UserWasRegistered $event) { // send an email to the user } }
装饰命令总线
有时您可能想要装饰命令总线以在处理命令之前执行额外的操作。此包已经包含了一个验证装饰器。
验证
包含的验证命令总线可以作为装饰器与主命令总线一起使用。这必须在您的特定应用程序中实现。一旦命令已传递给命令总线,它将检查关联的验证器类并调用其 validate($command)
方法。否则,它将继续。这样,您就可以在执行命令和引发任何领域事件之前执行任何验证。
创建验证器类的约定如下
- RegisterUserCommand > RegisterUserValidator
只需包含一个 validate($command)
方法,并按您通常的方式执行验证。
<?php namespace Foxx\Users; use Flyingfoxx\CommandCenter\CommandValidator; class RegisterUserValidator implements CommandValidator { public function validate($command) { var_dump('validating register user command'); } }
您可以通过创建一个实现包的
CommandBus
接口的类并遵循装饰器设计模式来创建自己的装饰器。您的最佳选择是复制包含的ValidationCommandBus
类并根据您的需求进行修改。
别忘了在您的特定应用程序中正确加载您的新装饰器。并且别忘了为您的新的装饰器包含一个新的翻译方法,无论是通过扩展
MainCommandTranslator
类还是创建一个实现包的CommandTranslator
类的新类。
Laravel
如果您是 Laravel 用户,那么在这个包中为您提供了现成的解决方案。它包括包的 CommandApplication
接口的 Laravel 实现,以及一个可以加载到配置中的服务提供者。默认的命令总线使用验证装饰器,已经为您设置好了。再次强调,如果您需要提供额外的装饰器,请按照验证命令总线的模式操作,应该可以正常工作。
此包包含了一些为 Laravel 设计的特质。这些特质可以帮助您清理类,并提高代码的可读性。
Commander
这个特质本质上是对命令总线进行了封装,可以在任何控制器中使用。您不必注入命令总线,可以按照以下方式注入包的 Commander
特质:
<?php use Flyingfoxx\CommandCenter\Laravel\Commander; use Foxx\Users\RegisterUserCommand; class RegistrationController { use Commander; public function store() { // Grab the input (using Laravel in this example) $input = Input::only('username', 'password'); // Create command $command = new RegisterUserCommand($input['username'], $input['password']); // Pass command to command bus $this->execute($command); } }
现在您可以直接在控制器本身上调用 execute 方法。另一种选择是将此方法放入基控制器中,一次编写即可。
Dispatcher
这个特质本质上是对事件分发器进行了封装,可以在您的处理类中使用。您不必注入事件分发器,可以按照以下方式注入包的 Dispatcher
特质:
<?php namespace Foxx\Users; use Flyingfoxx\CommandCenter\CommandHandler; use Flyingfoxx\CommandCenter\Laravel\Dispatcher; class RegisterUserHandler implements CommandHandler { use Dispatcher; protected $user; public function __construct(User $user) { $this->user = $user; } public function handle($command) { $user = $this->user->register($command->username, $command->password); $this->dispatchEventsFor($user); return $user; } }
因此,您不需要调用 dispatch($events)
,而是应该在处理类上调用 dispatchEventsFor($entity)
,传入实体。这个特质还会自动释放传入实体的事件。
Laravel 5 更新
现在在 Laravel 5 中,您可以使用方法注入以及使用表单请求对象作为命令对象。映射如下:
- RegisterUserRequest > RegisterUserHandler
- PostBlogEntryRequest > PostBlogEntryHandler
其他所有内容的工作方式应保持不变。以下是一个示例控制器使用情况:
<?php namespace Foxx\Http\Controllers; use Flyingfoxx\CommandCenter\Laravel\Commander; use Foxx\Users\RegisterUserRequest; class RegistrationController { use Commander; public function store(RegisterUserRequest $request) { // Pass command to command bus $this->execute($request); } }
结论
以上就是所有内容。请随意扩展,提出问题或发表评论。此外,请务必查看 Jeffrey Way 在 Laracasts 上的 Laracasts 系列,了解更多关于这方面的知识。