laracasts / commander
Laravel中的命令和领域事件
Requires
- php: >=5.4.0
- illuminate/filesystem: ~5.0
- illuminate/support: ~5.0
- mustache/mustache: ~2.6
Requires (Dev)
- codeception/codeception: ~2.0.0
- phpspec/phpspec: ~2.0.0
README
此包为您提供了一个简单的方式来利用命令和领域事件在您的Laravel项目中。
Laravel 5用户:此包不再需要,因为框架自带灵活的命令总线。
安装
像往常一样,通过Composer安装Commander。
"require": { "laracasts/commander": "~1.0" }
接下来,更新 app/config/app.php
,在提供者数组中包含对该包服务提供者的引用。
'providers' => [ 'Laracasts\Commander\CommanderServiceProvider' ]
用法
最关键的忠告是,请记住这种方法并不适用于所有情况。如果您正在构建一个简单的CRUD应用,没有太多业务逻辑,那么您可能不需要这个包。仍想继续?好吧 - 继续吧!
目标
想象您正在构建一个广告职位列表的应用程序。现在,当雇主发布新的职位列表时,需要发生一系列事情,对吧?好吧,不要把所有这些事情都放入控制器中!相反,让我们利用命令、处理程序和领域事件来简化代码。
控制器
首先,我们可以将此包的CommanderTrait
注入到您的控制器中(或者如果您愿意,可以注入到BaseController中)。这将为您提供一些辅助方法来管理将命令传递给命令总线的流程。
<?php use Laracasts\Commander\CommanderTrait; class JobsController extends \BaseController { use CommanderTrait; /** * Publish the new job listing. * * @return Response */ public function store() { } }
好的?接下来,我们将这个“指令”(发布职位列表)表示为一个命令。这只是一个简单的DTO。
<?php use Laracasts\Commander\CommanderTrait; use Acme\Jobs\PostJobListingCommand; class JobsController extends \BaseController { use CommanderTrait; /** * Post the new job listing. * * @return Response */ public function store() { $this->execute(PostJobListingCommand::class); return Redirect::home(); }
注意我们如何将用户的指令(或命令)表示为可读的类:PostJobListingCommand
。execute
方法将期望命令的类路径,作为字符串。上面,我们使用有用的PostJobListingCommand::class
来获取这个路径。或者,您也可以手动将路径作为字符串写出。
命令DTO
很简单,对吧?我们创建一个命令来表示指令,然后将该命令扔入命令总线。下面是这个命令可能的样子
<?php namespace Acme\Jobs; class PostJobListingCommand { public $title; public $description; public function __construct($title, $description) { $this->title = $title; $this->description = $description; } }
当您在
CommanderTrait
上调用execute
方法时,它将自动将来自Input::all()
的数据映射到您的命令。您无需手动进行此操作。
那么命令总线到底做了什么?将其视为一个简单的实用工具,它将此命令转换为相关的处理程序类,该类将处理命令!在这种情况下,这意味着根据需要委托发布新的职位列表。
默认情况下,命令总线将快速搜索并替换命令类的名称,以确定要从IoC容器中解析哪个处理程序类。因此
- PostJobListingCommand => PostJobListingCommandHandler
- ArchiveJobCommand => ArchiveJobCommandHandler
明白了吗?好的。但请注意,如果您更喜欢不同的命名约定,可以覆盖默认设置。请参见下面。
装饰命令总线
可能有时您想装饰命令总线,首先执行某种操作……也许您需要先清理一些数据。这很简单。首先,创建一个实现Laracasts\Commander\CommandBus
契约的类……
<?php namespace Acme\Jobs; use Laracasts\Commander\CommandBus; class JobSanitizer implements CommandBus { public function execute($command) { // sanitize the job data } }
……现在在控制器中执行命令时,引用此类。
$this->execute(PostJobListingCommand::class, null, [ 'JobSanitizer' ]);
就这样!现在,您有一个钩子,可以在将命令和数据传递给处理程序类之前对其进行清理。在这方面……
处理程序类
让我们现在创建第一个处理程序类
<?php namespace Acme\Jobs; use Laracasts\Commander\CommandHandler; use Laracasts\Commander\Events\DispatchableTrait; class PostJobListingCommandHandler implements CommandHandler { use DispatchableTrait; public function handle($command) { $job = Job::post($command->title, $command->description); $this->dispatchEventsFor($job); return $job; } }
在这个演示中,我们的处理程序相当简单。在现实生活中,这里会有更多的动作发生。注意那个 dispatchEventsFor
方法吗?这个方法将处理为实体触发所有队列中的事件的流程。这样,应用的其他部分可以监听作业发布时的通知,并相应地做出反应。
引发事件
以下是一个快速示例,展示了 Job
模型可能的样子
<?php namespace Acme\Jobs; use Laracasts\Commander\Events\EventGenerator; use Acme\Jobs\Events\JobWasPublished; class Job extends \Eloquent { use EventGenerator; protected $fillable = ['title', 'description']; public static function post($title, $description) { // We're ignoring persistence for this demo $job = new static(compact('title', 'description')); $job->raise(new JobWasPublished($job)); return $job; } }
请特别注意我们引发事件的位置。
$job->raise(new JobWasPublished($job));
再次强调,这个 JobWasPublished
对象不过是一个简单的传输对象。
<?php namespace Acme\Jobs\Events; use Acme\Jobs\Job; class JobWasPublished { public $job; public function __construct(Job $job) /* or pass in just the relevant fields */ { $this->job = $job; } }
此外,raise
方法可以通过那个 EventGenerator
特性来访问。它只是将事件存储在一个数组中。
事件监听器
现在,由于处理程序类派发了所有事件,这意味着你可以注册任意数量的监听器。要监听的事件名称遵循以下简单的约定
- 路径\到\引发的事件 => 路径.到.引发的事件
所以,本质上,我们用点替换斜杠,使其看起来更面向对象一点。所以,如果我们引发
- Acme\Jobs\Events\JobWasPublished
那么要监听的事件将是
- Acme.Jobs.Events.JobWasPublished
让我们注册一个基本的事件监听器来发送电子邮件。
Event::listen('Acme.Jobs.Events.JobWasPublished', function($event) { var_dump('Send a notification email to the job creator.'); });
记住:你可以为同一事件注册多个监听器。也许你还需要在作业发布时执行一些与报告相关的操作。那么,添加一个新的事件监听器吧!
上面的例子使用了简单的闭包。如果你想,可以采取更“通配符”的方法,这个包可以帮到你。
首先,让我们设置一个 EmailNotifier
类,它将有机会处理应用中所有引发的事件。
Event::listen('Acme.*', 'Acme\Listeners\EmailNotifier');
所以,现在,每次你在 Acme
命名空间中引发一个事件,一旦派发,EmailNotifier
类的 handle
方法就会触发。当然,我们不需要对 每个 事件做出响应!只是几个。再次强调,我们可以遵循一个简单的方法命名约定,只对感兴趣的特定事件做出响应。
JobWasPublished
事件类将在你的事件监听器上查找 whenJobWasPublished
方法。如果存在,它将调用它。否则,它将继续执行。这意味着我们的 EmailNotifier
类可能看起来像这样
<?php namespace Acme\Listeners; use Laracasts\Commander\Events\EventListener; use Acme\Jobs\Events\JobWasPublished; class EmailNotifier extends EventListener { public function whenJobWasPublished(JobWasPublished $event) { var_dump('send an email'); } }
因为这个类扩展了 EventListener
,父类将管理所有确定是否调用 whenJobWasPublished
的细节。
文件生成
你可能会发现自己手动创建大量命令和处理程序类。相反,使用这个包中包含的 Artisan 命令!只需运行
php artisan commander:generate Acme/Bar/SubscribeUserCommand
这将生成 SubscribeUserCommand
和 SubscribeUserCommandHandler
类。默认情况下,它将在 "app/" 目录中的 "Acme" 目录中查找。如果你的基本域名目录在其他地方,请传递 --base="src"
。
命令
<?php namespace Acme\Bar; class SubscribeUserCommand { /** * Constructor */ public function __construct() { } }
处理程序
<?php namespace Acme\Bar; use Laracasts\Commander\CommandHandler; class SubscribeUserCommandHandler implements CommandHandler { /** * Handle the command. * * @param object $command * @return void */ public function handle($command) { } }
或者,如果你还需要属性的样板代码,你也可以这样做。
php artisan commander:generate Acme/Bar/SubscribeUserCommand --properties="first, last"
当你添加 --properties
标志时,处理程序类将保持不变,但是命令本身将被构建,如下所示
<?php namespace Acme\Bar; class SubscribeUserCommand { /** * @var string */ public $first; /** * @var string */ public $last; /** * Constructor * * @param string first * @param string last */ public function __construct($first, $last) { $this->first = $first; $this->last = $last; } }
不错吧?这会节省你很多时间,所以请记得使用它。
调用此命令时,请使用斜杠作为类路径的分隔符:
Acme/Bar/MyCommand
。如果你更喜欢使用反斜杠,则需要将其放在引号内。
这就完成了!
阅读这些东西可能会很复杂。请务必查看 Laracasts 上的 Commands and Domain Events 系列来了解更多关于这些内容。