57.@某人(二)
- 本系列文章为
laracasts.com
的系列视频教程——Let's Build A Forum with Laravel and TDD 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频, 支持正版 ;- 视频源码地址:github.com/laracasts/Lets-Build-a-...;
- 本项目为一个 forum(论坛)项目,与本站的第二本实战教程 《Laravel 教程 - Web 开发实战进阶》 类似,可互相参照。
本节说明
- 对应视频教程第 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');
}
.
.
运行测试,看重构是否成功:
接下来让我们继续重构。我们将匹配用户名的代码封装成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];
}
.
.
运行该测试:
现在我们可以修改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
测试:
通常情况下,重构进行到这个地步已经可以了。但是我们再来看一下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
被触发时,会执行两个动作:NotifyMentionedUsers
和NotifySubscribers
。运行以下命令生成相应的文件:
$ 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);
}
.
.
让我们运行一下全部的测试: