laraviet/commander

Laravel 中的命令和领域事件

2.0 2014-12-10 03:59 UTC

This package is auto-updated.

Last update: 2024-08-27 16:39:59 UTC


README

此包为您提供了一种简单的方法,在您的 Laravel 项目中利用命令和领域事件。

安装

按照惯例,通过 Composer 安装 Commander。

"require": {
    "laraviet/commander": "~2.*"
}

接下来,更新 app/config/app.php 以在 providers 数组中包含对包的 service provider 的引用。

'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();
	}

注意我们如何将用户的指令(或命令)表示为可读的类:PostJobListingCommandexecute 方法将期望命令的类路径,作为一个字符串。上面,我们使用有用的 PostJobListingCommand::class 来获取它。或者,您也可以手动将路径作为字符串写出。

命令 DTO

很简单,对吧?我们创建一个命令来表示指令,然后将这个命令扔到命令总线中。下面是这个命令可能的样子

<?php namespace Acme\Jobs;

use Laracasts\Commander\Laraviet\BaseArrayCommand;

class PostJobListingCommand extends BaseArrayCommand 
{
}

当您在 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->$data);

        $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($data)
    {
        // Persistence data
        $job = static::create($data);

        $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特性行使。它只是将事件存储在数组中。

事件监听器

现在,由于处理器类已分发所有事件,这意味着你可以注册任意数量的监听器。要监听的事件名称遵循简单的约定

  • Path\To\Raised\Event => Path.To.Raised.Event

所以,基本上,我们用点替换斜杠,使其看起来更具面向对象。所以,如果我们发布

  • 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

这将生成SubscribeUserCommandSubscribeUserCommandHandler类。默认情况下,它将在“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;

use Laracasts\Commander\Laraviet\BaseArrayCommand;

class SubscribeUserCommand extends BaseArrayCommand
{
}

很酷,对吧?这将为你节省大量时间,所以请记住使用它。

在调用此命令时,使用斜杠作为类路径:Acme/Bar/MyCommand。如果你愿意使用反斜杠,则需要将其放在引号内。

这就完成了!

阅读这些内容可能会很复杂。请务必查看Laracasts上的命令和领域事件系列,了解更多相关信息。