Laravel模型事件的实现原理详解


Posted in PHP onMarch 14, 2018

前言

Laravel的ORM模型在一些特定的情况下,会触发一系列的事件,目前支持的事件有这些:creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored,那么在底层是如何实现这个功能的呢?

下面话不多说了,来一起看看详细的介绍吧。

1.如何使用模型事件

先来看看如何使用模型事件,文档里面写了两种方法,实际上总共有三种方式可以定义一个模型事件,这里以saved事件来做例子,其他事件都一样。

1.events属性

直接上代码:

class User extends Authenticatable {
 use Notifiable;

 protected $events = [
  'saved' => UserSaved::class,
 ];
}

这个比较难以理解,而且文档并没有详细说明,刚开始以为saved被触发后会调用UserSaved里面的handle方法,实际上并不是。这个数组只是对事件做的一个映射,它定义了在模型的saved的时候会触发UserSaved这个事件,我们还要定义该事件以及其监听器才可以:

namespace App\Events;
use App\User;
class UserSaved {
 public $user;
 public function __construct(User $user){
  $this->user = $user;
 }
}
namespace App\Listeners;
class UserSavedListener {
 public function handle(UserSaved $userSaved){
  dd($userSaved);
 }
}

然后还要到EventServiceProvider中去注册该事件和监听器:

class EventServiceProvider extends ServiceProvider
{
 /**
  * The event listener mappings for the application.
  *
  * @var array
  */
 protected $listen = [
  'App\Events\UserSaved' => [
   'App\Listeners\UserSavedListener',
  ]
 ];

 /**
  * Register any events for your application.
  *
  * @return void
  */
 public function boot()
 {
  parent::boot();
 }
}

这样在saved节点的时候,UserSaved事件会被触发,其监听器UserSavedListener的handle方法会被调用。

2.观察者

这是文档比较推崇的一个模型事件定义方法,也比较好理解,先定义一个观察者:

use App\User;
class UserObserver
{
 /**
  * 监听用户创建事件.
  *
  * @param User $user
  * @return void
  */
 public function created(User $user)
 {
  //
 }

 /**
  * 监听用户创建/更新事件.
  *
  * @param User $user
  * @return void
  */
 public function saved(User $user)
 {
  //
 }
}

然后在某个服务提供者的boot方法中注册观察者:

namespace App\Providers;
use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
 /**
  * Bootstrap any application services.
  *
  * @return void
  */
 public function boot()
 {
  User::observe(UserObserver::class);
 }

 /**
  * Register the service provider.
  *
  * @return void
  */
 public function register()
 {
  //
 }
}

这样在模型事件触发的时候,UserObserver的相应方法就会被调用。其实,在使用观察者的时候,除了一些系统自带的,我们还可以定义一些自己的事件:

class User extends Authenticatable {
 use Notifiable;
 protected $observables = [
  'customing', 'customed'
 ];
}

然后在观察者里面定义同名方法:

class UserObserver
{
 /**
  * 监听用户创建/更新事件.
  *
  * @param User $user
  * @return void
  */
 public function saved(User $user)
 {
  //
 }

 public function customing(User $user){
 }

  public function customed(User $user){
 }
}

由于是我们自己定义的事件,所以触发的时候也必须手动触发,在需要触发的地方调用模型里面的一个fireModelEvent方法即可。不过由于该方法是protected的,所以只能在自己定义的模型方法里面,当然如果通过反射来调用,或许可以直接在$user对象上触发也说不定,这个我没试,大家可以自行测试下。

class User extends Authenticatable {
 use Notifiable;
 protected $observables = [
  'customing', 'awesoming'
 ];
 
 public function custom(){
  if ($this->fireModelEvent('customing') === false) {
   return false;
  }
  
  //TODO
   if ($this->fireModelEvent('customed') === false) {
   return false;
  }
 }
}

3.静态方法定义

我们还可以通过模型上的对应静态方法来定义一个事件,在EventServiceProvider的boot方法里面定义:

class EventServiceProvider extends ServiceProvider{
 /**
  * Register any events for your application.
  *
  * @return void
  */
 public function boot()
 {
  parent::boot();
  User::saved(function(User$user) {
  });
  User::saved('UserSavedListener@saved');
 }
}

通过静态方法定义的时候,可以直接传递进入一个闭包,也可以定义为某个类的方法,事件触发时候传递进入的参数就是该模型实例。

2.模型事件实现原理

Laravel的模型事件所有的代码都在Illuminate\Database\Eloquent\Concerns\HasEvents这个trait下,先来看看Laravel是如何注册这些事件的,其中的$dispatcher是一个事件的调度器Illuminate\Contracts\Events\Dispatcher实例,在Illuminate\Database\DatabaseServiceProvider的boot方法中注入。

protected static function registerModelEvent($event, $callback){
  if (isset(static::$dispatcher)) {
   $name = static::class;
   static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback);
  }
 }

这里是Laravel事件注册的地方,其中以eloquent.saved:App\User为事件名,$callback作为处理器来注册。这个注册事件的方法,只会注册以观察者和静态方法定义的。假如你定义为模型的$events属性的话,Laravel是不会注册的,会在触发事件的时候同步触发,接下来会分析。

然后在HasEvents中定义了一堆的方法如下,这些就是我们上面通过静态方法来定义事件监听器的原理,不多说一看就懂。

public static function saving($callback){
 static::registerModelEvent('saving', $callback);
}

public static function saved($callback){
 static::registerModelEvent('saved', $callback);
}

那么如何通过观察者的形式来定义事件监听器呢?看源码:

public static function observe($class){
  $instance = new static;
  $className = is_string($class) ? $class : get_class($class);

  foreach ($instance->getObservableEvents() as $event) {
   if (method_exists($class, $event)) {
    static::registerModelEvent($event, $className.'@'.$event);
   }
  }
 }

 public function getObservableEvents()
 {
  return array_merge(
   [
    'creating', 'created', 'updating', 'updated',
    'deleting', 'deleted', 'saving', 'saved',
    'restoring', 'restored',
   ],
   $this->observables
  );
 }

先获取到observer的类名,然后判断是否存在事件名对应的方法,存在则调用registerModelEvent注册,这里事件名还包括我们自己定义在observables数组中的。

事件以及监听器都定义好后,就是如何触发了,前面说到有一个方法fireModelEvent,来看看源码:

protected function fireModelEvent($event, $halt = true)
 {
  if (! isset(static::$dispatcher)) {
   return true;
  }

  $method = $halt ? 'until' : 'fire';

  $result = $this->filterModelEventResults(
   $this->fireCustomModelEvent($event, $method)
  );

  if ($result === false) {
   return false;
  }

  return ! empty($result) ? $result : static::$dispatcher->{$method}(
   "eloquent.{$event}: ".static::class, $this
  );
 }

其中比较关键的一个方法是fireCustomModelEvent,它接受一个事件名以及触发方式。顺带一提,filterModelEventResults这个方法的作用就是把监听器的返回值为null的过滤掉。

看看fireCustomModelEvent的源码:

protected function fireCustomModelEvent($event, $method)
 {
  if (! isset($this->events[$event])) {
   return;
  }

  $result = static::$dispatcher->$method(new $this->events[$event]($this));
  if (! is_null($result)) {
   return $result;
  }
 }

这个就是用来触发我们通过$events定义的事件了,假如我们这么定义:

class User extends Model{
 protected $events = [
  'saved' => UserSaved::class
 ]
}

那这里的触发就是:

$result = static::$dispatcher->fire(new UserSaved($this));

顺带一提,Laravel中触发事件的方法有两个,一个是常用的fire,还有一个是util,这两个的差别就是fire会把监听器的返回值返回,而util永远返回null

然后接下来就是会去触发通过观察者和静态方法定义的监听器了,这一段代码:

if ($result === false) {
   return false;
  }

  return ! empty($result) ? $result : static::$dispatcher->{$method}(
   "eloquent.{$event}: ".static::class, $this
  );

这里会先判断$events定义的监听器是否返回false以及返回值是否为空,如果为false则直接结束事件,如果返回不为false而且为空的话,会再去触发通过观察者和静态方法定义的监听器,并且把监听器的返回值返回。
完。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
使用PHPMYADMIN操作mysql数据库添加新用户和数据库的方法
Apr 02 PHP
PHP session会话的安全性分析
Sep 08 PHP
PHP中使用FFMPEG获取视频缩略图和视频总时长实例
May 04 PHP
php使用date和strtotime函数输出指定日期的方法
Nov 14 PHP
php实现简单文件下载的方法
Jan 30 PHP
跨浏览器PHP下载文件名中的中文乱码问题解决方法
Mar 05 PHP
PHP 常用的header头部定义汇总
Jun 19 PHP
PHP实现的限制IP投票程序IP来源分析
May 04 PHP
iOS自定义提示弹出框实现类似UIAlertView的效果
Nov 16 PHP
浅谈php://filter的妙用
Mar 05 PHP
Laravel框架实现多数据库连接操作详解
Jul 12 PHP
tp5.1 框架数据库常见操作详解【添加、删除、更新、查询】
May 26 PHP
PHP实现根据数组某个键值大小进行排序的方法
Mar 13 #PHP
Laravel多用户认证系统示例详解
Mar 13 #PHP
PHP简单实现防止SQL注入的方法
Mar 13 #PHP
PHP+JS实现的实时搜索提示功能
Mar 13 #PHP
PHP实现的简单路由和类自动加载功能
Mar 13 #PHP
php生成复杂验证码(倾斜,正弦干扰线,黏贴,旋转)
Mar 12 #PHP
php把字符串指定字符分割成数组的方法
Mar 12 #PHP
You might like
用php+mysql一个名片库程序
2006/10/09 PHP
ob_start(),ob_start('ob_gzhandler')使用
2006/12/25 PHP
PHP 缓存实现代码及详细注释
2010/05/16 PHP
PHP实现多维数组转字符串和多维数组转一维数组的方法
2015/08/08 PHP
PHP对XML内容进行修改和删除实例代码
2016/10/26 PHP
php对接java现实加签验签的实例
2016/11/25 PHP
php表单处理操作
2017/11/16 PHP
JavaScript入门教程(12) js对象化编程
2009/01/31 Javascript
jQuery的Ajax的自动完成功能控件简要说明
2013/02/22 Javascript
父页面显示遮罩层弹出半透明状态的dialog
2014/03/04 Javascript
Javascript基础教程之定义和调用函数
2015/01/18 Javascript
jQuery判断一个元素是否可见的方法
2015/06/05 Javascript
《JavaScript高级编程》学习笔记之object和array引用类型
2015/11/01 Javascript
AngularJS实现动态编译添加到dom中的方法
2016/11/04 Javascript
解决jquery appaend元素中id绑定事件失效的问题
2017/09/12 jQuery
Vue组件通信的四种方式汇总
2018/02/08 Javascript
详解vue路由篇(动态路由、路由嵌套)
2019/01/27 Javascript
通过JS深度判断两个对象字段相同
2019/06/14 Javascript
Postman参数化实现过程及原理解析
2020/08/13 Javascript
JavaScript实现前端倒计时效果
2021/02/09 Javascript
浅谈python装饰器探究与参数的领取
2017/12/01 Python
Python面向对象之类的封装操作示例
2019/06/08 Python
TFRecord格式存储数据与队列读取实例
2020/01/21 Python
树莓派升级python的具体步骤
2020/07/05 Python
Django搭建项目实战与避坑细节详解
2020/12/06 Python
如何用Matlab和Python读取Netcdf文件
2021/02/19 Python
说出ArrayList,Vector, LinkedList的存储性能和特性
2015/01/04 面试题
国际商务系学生个人的自我评价
2013/11/26 职场文书
客服文员岗位职责
2013/11/29 职场文书
暑期社会实践证明书
2014/11/17 职场文书
2014小学一年级班主任工作总结
2014/12/05 职场文书
普宁寺导游词
2015/02/04 职场文书
2015年防汛工作总结
2015/05/15 职场文书
AJAX学习笔记
2021/05/18 Javascript
python基础之文件操作
2021/10/24 Python
python中的random模块和相关函数详解
2022/04/22 Python