57.@某人(二)

未匹配的标注

本节说明

  • 对应视频教程第 57 小节:Mentioned Users Notifications: Part 2

本节内容

上一节我们完成了 @某人 功能的基础版本,本节我们来重构一下上一节的代码。在第 46 小节,我们曾提到过 Laravel 的事件系统 。在本节,我们来使用事件系统。

Laravel 应用中的 EventServiceProvider 有个 listen 数组包含所有的事件(键)以及事件对应的监听器(值)来注册所有的事件监听器,可以灵活地根据需求来添加事件。现在,让我们增加一个 ThreadHasNewReply 事件:
forum\app\Providers\EventServiceProvider.php

    .
    .
    protected $listen = [
        'App\Events\ThreadReceivedNewReply' => [
            'App\Listeners\NotifyMentionedUsers',
        ],
    ];
    .
    .

我们再使用event:generate命令为事件和监听器创建文件:

$ php artisan event:generate

接着修改我们新创建的两个文件内容为以下:
forum\app\Events\ThreadReceivedNewReply.php

<?php

namespace App\Events;

use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;

class ThreadReceivedNewReply
{
    use Dispatchable, SerializesModels;

    public $reply;

    /**
     * ThreadReceivedNewReply constructor.
     * @param $reply
     */
    public function __construct($reply)
    {
        $this->reply = $reply;
    }
}

forum\app\Listeners\NotifyMentionedUsers.php

<?php

namespace App\Listeners;

use App\Events\ThreadReceivedNewReply;
use App\Notifications\YouWereMentioned;
use App\User;

class NotifyMentionedUsers
{
    /**
     * Handle the event.
     *
     * @param  ThreadReceivedNewReply  $event
     * @return void
     */
    public function handle(ThreadReceivedNewReply $event)
    {
        // Inspect the body of the reply for the username mentions
        preg_match_all('/\@([^\s\.]+)/',$event->reply->body,$matches);

        // And then notify user
        foreach ($matches[1] as $name){
            $user = User::whereName($name)->first();

            if($user){
                $user->notify(new YouWereMentioned($event->reply));
            }
        }
    }
}

接下来我们改为用事件来触发通知:
forum\app\Thread.php

    .
    .
    public function addReply($reply)
    {
        $reply = $this->replies()->create($reply);

        event(new ThreadReceivedNewReply($reply));

        $this->notifySubscribers($reply);

        return $reply;
    }
    .
    .

控制器也要进行相应修改:
forum\app\Http\Controllers\RepliesController.php

    .
    .
    public function store($channelId, Thread $thread, CreatePostRequest $request)
    {
        return $thread->addReply([
                'body' => request('body'),
                'user_id' => auth()->id(),
            ])->load('owner');
    }
    .
    .

运行测试,看重构是否成功:
file
接下来让我们继续重构。我们将匹配用户名的代码封装成mentionedUsers()方法,通过此方法来获取用户名。让我们来为此编写一个测试:
forum\tests\Unit\ReplyTest.php

    .
    .
    /** @test */
    public function it_can_detect_all_mentioned_users_in_the_body()
    {
        $reply = create('App\Reply',[
           'body' => '@JaneDoe wants to talk to @JohnDoe'
        ]);

        $this->assertEquals(['JaneDoe','JohnDoe'],$reply->mentionedUsers());
    }
}

新增mentionedUsers()方法:
forum\app\Reply.php

    .
    .
    public function wasJustPublished()
    {
        return $this->created_at->gt(Carbon::now()->subMinute());
    }

    public function mentionedUsers()
    {
        preg_match_all('/\@([^\s\.]+)/',$this->body,$matches);

        return $matches[1];
    }
    .
    .

运行该测试:
file
现在我们可以修改handle()方法了:
forum\app\Listeners\NotifyMentionedUsers.php

<?php

namespace App\Listeners;

use App\Events\ThreadReceivedNewReply;
use App\Notifications\YouWereMentioned;
use App\User;

class NotifyMentionedUsers
{
    /**
     * Handle the event.
     *
     * @param  ThreadReceivedNewReply  $event
     * @return void
     */
    public function handle(ThreadReceivedNewReply $event)
    {
        collect($event->reply->mentionedUsers())
            ->map(function ($name) {
                return User::where('name',$name)->first();
            })
            ->filter()
            ->each(function ($user) use ($event){
                $user->notify(new YouWereMentioned($event->reply));
            });
    }
}

再次运行mentioned_users_in_a_reply_are_notified测试:
file
通常情况下,重构进行到这个地步已经可以了。但是我们再来看一下addReply方法:

public function addReply($reply)
{
    $reply = $this->replies()->create($reply);

    event(new ThreadReceivedNewReply($reply));

    $this->notifySubscribers($reply);

    return $reply;
}

我们新建了回复,然后触发ThreadReceivedNewReply()事件,接着通知订阅该话题的用户。看出问题了没有?我们完全可以把通知用户的动作放到ThreadReceivedNewReply()事件中:
forum\app\Providers\EventServiceProvider.php

    .
    .
    protected $listen = [
        'App\Events\ThreadReceivedNewReply' => [
            'App\Listeners\NotifyMentionedUsers',
            'App\Listeners\NotifySubscribers',
        ],
    ];
    .
    .

现在,当ThreadReceivedNewReply被触发时,会执行两个动作:NotifyMentionedUsersNotifySubscribers。运行以下命令生成相应的文件:

$ php artisan event:generate

修改文件内容如下:
forum\app\Listeners\NotifySubscribers.php

<?php

namespace App\Listeners;

use App\Events\ThreadReceivedNewReply;

class NotifySubscribers
{
    /**
     * Handle the event.
     *
     * @param  ThreadReceivedNewReply  $event
     * @return void
     */
    public function handle(ThreadReceivedNewReply $event)
    {
        $thread = $event->reply->thread;

        $thread->subscriptions
            ->where('user_id','!=',$event->reply->user_id)
            ->each
            ->notify($event->reply);
    }
}

现在我们可以对addReply()方法进行修改:
forum\app\Thread.php

    .
    .
    public function addReply($reply)
    {
        $reply = $this->replies()->create($reply);

        event(new ThreadReceivedNewReply($reply));

        return $reply;
    }

    public function scopeFilter($query,$filters)
    {
        return $filters->apply($query);
    }
    .
    .

让我们运行一下全部的测试:
file

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

上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
发起讨论 只看当前版本


暂无话题~