本书未发布

事件和监听器

未匹配的标注

如果希望你的扩展包支持 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... 

所有测试均通过,因此我们继续下一个话题

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel-package...

译文地址:https://learnku.com/docs/laravel-package...

上一篇 下一篇
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
贡献者:2
讨论数量: 0
发起讨论 只看当前版本


暂无话题~