事件和监听器
如果希望你的扩展包支持 Laravel 的事件与监听器功能,你就来对啦。
Laravel 事件系统提供了一种方法,可以挂载到应用程序中特定的业务逻辑中,例如注册事件。可以使用 event()
辅助方法来触发和调用它们,该辅助方法接收 Event
对象作为参数。当事件被调用时,会自动触发该事件绑定监听器的 handle()
方法,事件与监听器的绑定关系在事件服务提供者(EventServiceProvider)中注册。该模式有助于使代码保持非紧密耦合,提高程序健壮性。
扩展包在运行特定任务时触发相关事件的情况很常见,使用者可不太注意到这些情况。但是,为了扩展包的健壮性,我们会在扩展包中监听指定的事件。为此,我们需要有自己的服务提供者,这就是我们在本节中研究的内容。
创建一个新的事件
首先,我们通过以前设置的路由,每当有新的Post
时,我们就发出一个事件。
在“src/”目录中的一个新的“Events”文件夹中,创建一个新的“PostWasCreated.php”文件。在' PostWasCreated '事件类中,我们将接受在构造函数中创建的' Post ',并将其保存到一个public实例变量' $post '中。
// 'src/Events/PostWasCreated.php'
<?php
namespace JohnDoe\BlogPackage\Events;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use JohnDoe\BlogPackage\Models\Post;
class PostWasCreated
{
use Dispatchable, SerializesModels;
public $post;
public function __construct(Post $post)
{
$this->post = $post;
}
}
当在“PostController”中创建一个新的“Post”时,我们就可以立马发出这个事件(不要忘记将创建好的事件类use进来);
// 'src/Http/Controllers/PostController.php'
<?php
use JohnDoe\BlogPackage\Events\PostWasCreated;
class PostController extends Controller
{
public function store()
{
// authentication and validation checks...
$post = $author->posts()->create([...]);
event(new PostWasCreated($post));
return redirect(...);
}
}
测试我们发出的事件
要确保成功触发此事件,请在“CreatePostTest”特性测试中添加一个测试。我们可以很容易地伪造Laravel的“事件”门面,并断言(参见[Laravel关于Fakes的文档](《Laravel 中文文档》# Event -fake),该事件已经发出,并且与传递的Post模型有关。
// 'tests/Feature/CreatePostTest.php'
use Illuminate\Support\Facades\Event;
use JohnDoe\BlogPackage\Events\PostWasCreated;
use JohnDoe\BlogPackage\Models\Post;
class CreatePostTest extends TestCase
{
use RefreshDatabase;
// other tests
/** @test */
function an_event_is_emitted_when_a_new_post_is_created()
{
Event::fake();
$author = factory(User::class)->create();
$this->actingAs($author)->post(route('posts.store'), [
'title' => 'A valid title',
'body' => 'A valid body',
]);
$post = Post::first();
Event::assertDispatched(PostWasCreated::class, function ($event) use ($post) {
return $event->post->id === $post->id;
});
}
}
现在已经知道我们创建的事件被正确的触发了,接下来让我们连接我们自己的监听器。
Now that we know that our event is fired correctly, let's hook up our own listener.
创建一个新的监听器
在触发PostWasCreated事件之后,为了便于演示,我们修改一下帖子的标题。在src/目录中,创建一个新的文件夹Listeners
。在这个文件夹中,创建一个描述我们的操作的文件:UpdatePostTitle.php:
// 'src/Listeners/UpdatePostTitle.php'
<?php
namespace JohnDoe\BlogPackage\Listeners;
use JohnDoe\BlogPackage\Events\PostWasCreated;
class UpdatePostTitle
{
public function handle(PostWasCreated $event)
{
$event->post->update([
'title' => 'New: ' . $event->post->title
]);
}
}
测试监听器
尽管我们已经使用测试证明了事件发出时的正确结果,但是为事件的监听器进行单独的测试仍然是值得的。如果将来有问题出现,这个测试将直接把您引向问题的根源:监听器。在这个测试中(这个简单的例子中),我们将通过实例化UpdatePostTitle监听器并将一个PostWasCreated事件传递给它的handle()方法来断言监听器的handle()方法确实改变了博客文章的标题:
// 'tests/Feature/CreatePostTest.php'
/** @test */
function a_newly_created_posts_title_will_be_changed()
{
$post = factory(Post::class)->create([
'title' => 'Initial title',
]);
$this->assertEquals('Initial title', $post->title);
(new UpdatePostTitle())->handle(
new PostWasCreated($post)
);
$this->assertEquals('New: ' . 'Initial title', $post->fresh()->title);
}
现在,我们已经通过了事件的发出测试,并且我们已经知道监听器显示了处理事件的正确结果,让我们将两者结合起来并创建一个自定义事件服务提供者。
创建事件服务提供者
就像在Laravel中一样,我们的包可以有多个服务提供者,只要我们将它们加载到主应用程序服务提供者中(在下一节中)。
首先,在 src/
目录下创建一个新的文件夹Providers
,添加一个名为EventServiceProvider.php
的文件并注册我们的事件和监听器
// 'src/Providers/EventServiceProvider.php'
<?php
namespace JohnDoe\BlogPackage\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use JohnDoe\BlogPackage\Events\PostWasCreated;
use JohnDoe\BlogPackage\Listeners\UpdatePostTitle;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
PostWasCreated::class => [
UpdatePostTitle::class,
]
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
}
}
注册事件服务提供者
在我们主要的BlogPackageServiceProvider
中,我们需要在register()
方法中注册事件服务提供者,如下所示(别忘了把它use进来);
// 'BlogPackageServiceProvider.php'
use JohnDoe\BlogPackage\Providers\EventServiceProvider;
public function register()
{
// merge config files
$this->app->register(EventServiceProvider::class);
}
测试事件/监听器串联
之前我们伪造了Event
门面,但是在此测试中,我们想要确认事件是否被触发,该事件导致了侦听器上的一个句柄方法,并最终更改了我们的文章标题,这与我们所期望的完全一样。测试断言很简单:只需假设在创建新post后标题已更改。我们将把这个方法添加到CreatePostTest特性测试中:
// 'tests/Feature/CreatePostTest.php'
/** @test */
function the_title_of_a_post_is_updated_whenever_a_post_is_created()
{
$author = factory(User::class)->create();
$this->actingAs($author)->post(route('posts.store'), [
'title' => 'A valid title',
'body' => 'A valid body',
]);
$post = Post::first();
$this->assertEquals('New: ' . 'A valid title', $post->title);
}
这个测试通过了,但是如果我们运行全套的测试呢?
修改失败的测试
如果我们使用composer test
运行全套的测试,则回发现有一个失败的测试
There was 1 failure:
1) JohnDoe\BlogPackage\Tests\Feature\CreatePostTest::authenticated_users_can_create_a_post
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'My first fake title'
+'New: My first fake title'
这是我们所介绍的事件的回归,有两种方法可以纠正此错误:
1.在authenticated_users_can_create_a_post test测试中更改期望的标题
2.在测试运行之前伪造任何事件来阻止实际的处理程序被调用
最好的选择取决于具体的情况,我们现在先使用选项2
// 'tests/Feature/CreatePostTest.php'
/** @test */
function authenticated_users_can_create_a_post()
{
Event::fake();
$this->assertCount(0, Post::all());
// the rest of the test...
所有测试均通过,因此我们继续下一个话题
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: