PHP群:95885625 Hbuilder+MUI群:81989597 站长QQ:634381967
    您现在的位置: 首页 > 开发编程 > Laravel教程 > 正文

    Laravel的消息通知原理讲解

    作者:admin来源:网络浏览:时间:2017-09-29 11:08:10我要评论
    分享到
    导读:laravel给我们提供了多渠道的消息通知功能,包括邮件,短信,数据库,slack等通知方式。本文主要分析基于数据库的消息通知的底层实现。为了方...
    laravel给我们提供了多渠道的消息通知功能,包括邮件,短信,数据库,slack等通知方式。本文主要分析基于数据库的消息通知的底层实现。为了方便,本文将需要接受通知消息的模型称为接收者。

    通过官方文档可以知道,当我们需要开启一个model接收消息通知的功能时,需要在模型中添加Illuminate\Notifications\Notifiable 这个trait。通过代码可以发现,这个trait实际上只是使用了HasDatabaseNotifications, RoutesNotifications; 这两个trait,接下来让我们一起看看具体是怎样通过这两个trait来实现消息通知的。

      先来看看HasDatabaseNotifications。

    1. public function notifications() 
    2.        return $this->morphMany(DatabaseNotification::class'notifiable')->orderBy('created_at''desc'); 
    3.  public function unreadNotifications() 
    4.  return $this->morphMany(DatabaseNotification::class'notifiable')->whereNull('read_at')->orderBy('created_at''desc'); 

    接收者可以接收多个渠道的通知,这是一种一对多的关联关系,所以这里通过HasDatabaseNotifications为接受者添加了两个多态关联关系到DatabaseNotification::class模型,让我们可以方便的获取到接收者对应的通知。

      了解了发送消息模型和接收消息模型的关联关系后,再来看看这两个模型的具体定义。先分析一下DatabaseNotification::class。
     

    1. class DatabaseNotification extends Model 
    2.      public $incrementing = false; 
    3.     /** 
    4.      * The guarded attributes on the model. 
    5.      * 
    6.      * @var array 
    7.      */ 
    8.     protected $guarded = []; 
    9.     /** 
    10.      * The attributes that should be cast to native types. 
    11.      * 
    12.      * @var array 
    13.      */ 
    14.     protected $casts = [ 
    15.         'data' => 'array'
    16.         'read_at' => 'datetime'
    17.     ]; 
    18.     protected $table = 'notifications'
    19.     public function notifiable() 
    20.     { 
    21.         return $this->morphTo(); 
    22.     } 
    23.     //标记已读,也就是赋值给read_at字段 www.bcty365.com
    24.     public function markAsRead() 
    25.     { 
    26.       if (is_null($this->read_at)){ 
    27.         $this->forceFill(['read_at' => $this->freshTimestamp()])->save(); 
    28.       } 
    29.     } 
    30.     public function newCollection(array $models = []) 
    31.     { 
    32.         return new DatabaseNotificationCollection($models); 
    33.     } 

    至此,已经建立好了接收者和通知之间的多态联系关系。

    很多童鞋到这里可能就会纳闷了,那么如何将不同类型的通知关联到不同的模型呢?

    还记得我们在创建消息通知时在app/notifications下创建的通知吗,现在再来看看laravel为我们生成的这个模型。
     

    1. class salePromotion extends Notification implements ShouldQueue 
    2.     use Queueable; 
    3.     public  $data
    4.     public function __construct($data
    5.     { 
    6.         $this->data=$data
    7.     } 
    8.     //设置通知的渠道是基于数据库 www.bcty365.com 
    9.     public function via($notifiable
    10.     { 
    11.         return ['database']; 
    12.     } 
    13.     //设置在notifications表中的data字段对应格式 
    14.     public function toDatabase($notifiable
    15.     { 
    16.              return [ 
    17.                  'data' => $this->data, 
    18.              ]; 
    19.     } 

    可见,这个salePromotion是继承了我们刚才的DatabaseNotification模型的,也就是说它同时也继承了DatabaseNotification模型和接受者的关联关系,所以这多个通知类其实都是对应到了数据库里面的notifications表上,在发送通知时(由于是数据库通知,实际上是往notification表中写数据)写入具体的通知类的数据。

    很自然地,这时候我们就会想知道那么是如何发送的呢?当接受者只有一个的时候,我们通常只需要调用$user->notify(new InvoicePaid($invoice)),而接受者是一个collection时,我们会比较经常用Notification::send($customers, new salePromotion($data));。

    还记得前面我们说的另外一个trait吗,RoutesNotifications,开扒~
    重点我们来看看notify这个方法:
     

    1. public function notify($instance
    2.    { 
    3.        app(Dispatcher::class)->send($this$instance); 
    4.    } 

    app(Dispatcher::class)返回Illuminate\Notifications\ChannelManager对象,看看它的send方法是怎么定义的。

    1. public function send($notifiables$notification
    2.  //将$notifiables转换成集合或者数组的形式,返回collection或者数组 
    3.   $notifiables = $this->formatNotifiables($notifiables); 
    4.  //检测是否开启队列,队列我们就不分析了 
    5.  if ($notification instanceof ShouldQueue) { 
    6.   return $this->queueNotification($notifiables,$notification); 
    7.  } 
    8.   return $this->sendNow($notifiables$notification); 

    看来真正在干活的是sendNow这个方法呀。

    1. public function sendNow($notifiables$notificationarray $channels = null) 
    2.       $notifiables = $this->formatNotifiables($notifiables); 
    3.       //为了防止发送期间内通知类数据被改动,这里通过克隆来避免这个问题 
    4.       $original = clone $notification
    5.     foreach ($notifiables as $notifiable) { 
    6.     //为该条通知生产一个uuid 
    7.      $notificationId = Uuid::uuid4()->toString(); 
    8.      //获取发送要采用的通道,可以采取多通道,此时取到的是数组 
    9.      $channels = $channels ?: $notification->via($notifiable); 
    10.      if (emptyempty($channels)) { 
    11.            continue
    12.      } 
    13.      foreach ($channels as $channel) { 
    14.      //恢复上面克隆的通知对象 
    15.        $notification = clone $original
    16.       /** 
    17.       因为传入的通知对象可以在外部修改,所以这里才要加上检测, 
    18.       当用户没修改的时候才将id赋值为系统自动生成的,那么为什么 
    19.       不把前面生成uuid的语句放if里面?? 
    20.       **/ 
    21.        if (!$notification->id) { 
    22.             $notification->id = $notificationId
    23.        } 
    24.        //是否成功触发通知事件 
    25.        if (! $this->shouldSendNotification($notifiable$notification$channel)) { 
    26.                 continue
    27.        } 
    28.         //终于要发了。。 
    29.          $response = $this->driver($channel
    30.          ->send($notifiable$notification); 
    31.          //触发消息发送事件 
    32.          $this->app->make('events'
    33.          ->fire(new Events\NotificationSent($notifiable$notification$channel$response
    34.                 ); 
    35.             } 
    36.         } 
    37.     } 

    简单看看shouldSendNotification()
     

    1. protected function shouldSendNotification($notifiable$notification$channel
    2.   /**如果已经返回一个非空的响应,则触发通知发送事件 
    3.    $this->app->make('events')返Illuminate\Events\Dispatcher对象**/ 
    4.     return $this->app->make('events')->until( 
    5.      new Events\NotificationSending($notifiable,$notification$channel)) !== false; 
    1. /**由于我们设置数据库通知的方式,$this->driver($channel)返回Illuminate\Notifications\Channels\DatabaseChannel**/ 
    2. $this->driver($channel)->send($notifiable$notification); 

    逐渐逼近boss!DatabaseChannel的send方法~

    1. public function send($notifiable,Notification $notification
    2.     { 
    3.         return $notifiable->routeNotificationFor('database')->create([ 
    4.         'id' => $notification->id, 
    5.         'type' => get_class($notification), 
    6.         'data' => $this->getData($notifiable$notification), 
    7.         'read_at' => null, 
    8.         ]); 
    9.     } 

    来看看$notifiable->routeNotificationFor(‘database’)返回了什么

    1. public function routeNotificationFor($driver
    2.     { 
    3.         if (method_exists($this$method = 'routeNotificationFor'.Str::studly($driver))) { 
    4.             return $this->{$method}(); 
    5.         } 
    6.         switch ($driver) { 
    7.             case 'database'
    8.                 return $this->notifications(); 
    9.             case 'mail'
    10.                 return $this->email; 
    11.             case 'nexmo'
    12.                 return $this->phone_number; 
    13.         } 
    14.     } 

    可见,routeNotificationFor方法返回的是通知的发送地址,由于本文分析的是数据库通知,所以该方法返回的是该model的关联notifications对象。
    所以,没错,当我们调用接收者的notify方法时,最终是在关联的notifications表中将具体通知类对象的数据插入(驱动是database情况下)。
    下面是notification表的结构(type字段是通知类的类名,notifiable_id是接收者的id,notifiable_type是接收者的类名):

    Laravel的消息通知原理讲解

    插入数据后: 

    Laravel的消息通知原理讲解

    总结

    可见,laravel的消息通知的主要流程是:
    1.创建一个继承自DatabaseNotification的通知类

    2.在接收者模型中添加Notifiable trait,其中通过HasDatabaseNotifications添加模型间的关联关系,通过RoutesNotifications添加发送通知的方法

    3.发送通知:chanelManager只是提供了调用的接口,具体的发送是通过在chanelManager中的sendNow方法中利用获取到对应的驱动对象来完成发送,在本文中,可以看到数据库里面的send方法就是对notifications表执行写入操作。

    转载请注明(B5教程网)原文链接:http://www.bcty365.com/content-153-6025-1.html
    相关热词搜索: Laravel消息通知
    网友评论: