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

摘要:
事实上,有三种方法可以定义模型事件,而文档没有指定它;一开始,我以为在触发保存后会调用UserSaved中的handle方法。它定义了保存模型时将触发事件UserSaved。我们还需要定义事件及其侦听器boot();将触发UserSaved事件;2.观察者,这是一种模型事件定义方法,该方法在文档中得到了高度推荐:

模型事件在 Laravel 的世界中,你对 Eloquent 大多数操作都会或多或少的触发一些模型事件,下面这篇文章主要给大家介绍了关于Laravel模型事件的实现原理,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴。

前言

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

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

1.如何使用模型事件

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

1.events属性

直接上代码:

1
2
3
4
5
6
7
classUser extendsAuthenticatable {
 useNotifiable;
 
 protected$events= [
  'saved'=> UserSaved::class,
 ];
}

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

1
2
3
4
5
6
7
8
namespaceAppEvents;
useAppUser;
classUserSaved {
 public$user;
 publicfunction__construct(User $user){
  $this->user = $user;
 }
}
1
2
3
4
5
6
namespaceAppListeners;
classUserSavedListener {
 publicfunctionhandle(UserSaved $userSaved){
  dd($userSaved);
 }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
classEventServiceProvider extendsServiceProvider
{
 /**
  * The event listener mappings for the application.
  *
  * @var array
  */
 protected$listen= [
  'AppEventsUserSaved'=> [
   'AppListenersUserSavedListener',
  ]
 ];
 
 /**
  * Register any events for your application.
  *
  * @return void
  */
 publicfunctionboot()
 {
  parent::boot();
 }
}

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

2.观察者

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
useAppUser;
classUserObserver
{
 /**
  * 监听用户创建事件.
  *
  * @param User $user
  * @return void
  */
 publicfunctioncreated(User $user)
 {
  //
 }
 
 /**
  * 监听用户创建/更新事件.
  *
  * @param User $user
  * @return void
  */
 publicfunctionsaved(User $user)
 {
  //
 }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
namespaceAppProviders;
useAppUser;
useAppObserversUserObserver;
useIlluminateSupportServiceProvider;
classAppServiceProvider extendsServiceProvider
{
 /**
  * Bootstrap any application services.
  *
  * @return void
  */
 publicfunctionboot()
 {
  User::observe(UserObserver::class);
 }
 
 /**
  * Register the service provider.
  *
  * @return void
  */
 publicfunctionregister()
 {
  //
 }
}

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

1
2
3
4
5
6
classUser extendsAuthenticatable {
 useNotifiable;
 protected$observables= [
  'customing', 'customed'
 ];
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
classUserObserver
{
 /**
  * 监听用户创建/更新事件.
  *
  * @param User $user
  * @return void
  */
 publicfunctionsaved(User $user)
 {
  //
 }
 
 publicfunctioncustoming(User $user){
 }
 
  publicfunctioncustomed(User $user){
 }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
classUser extendsAuthenticatable {
 useNotifiable;
 protected$observables= [
  'customing', 'awesoming'
 ];
  
 publicfunctioncustom(){
  if($this->fireModelEvent('customing') === false) {
   returnfalse;
  }
   
  //TODO
   if($this->fireModelEvent('customed') === false) {
   returnfalse;
  }
 }
}

3.静态方法定义

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
classEventServiceProvider extendsServiceProvider{
 /**
  * Register any events for your application.
  *
  * @return void
  */
 publicfunctionboot()
 {
  parent::boot();
  User::saved(function(User$user) {
  });
  User::saved('UserSavedListener@saved');
 }
}

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

2.模型事件实现原理

Laravel的模型事件所有的代码都在IlluminateDatabaseEloquentConcernsHasEvents这个trait下,先来看看Laravel是如何注册这些事件的,其中的$dispatcher是一个事件的调度器IlluminateContractsEventsDispatcher实例,在IlluminateDatabaseDatabaseServiceProvider的boot方法中注入。

1
2
3
4
5
6
protectedstaticfunctionregisterModelEvent($event, $callback){
  if(isset(static::$dispatcher)) {
   $name= static::class;
   static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback);
  }
 }

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

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

1
2
3
4
5
6
7
publicstaticfunctionsaving($callback){
 static::registerModelEvent('saving', $callback);
}
 
publicstaticfunctionsaved($callback){
 static::registerModelEvent('saved', $callback);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
publicstaticfunctionobserve($class){
 $instance= newstatic;
 $className= is_string($class) ? $class: get_class($class);
 
 foreach($instance->getObservableEvents() as$event) {
  if(method_exists($class, $event)) {
   static::registerModelEvent($event, $className.'@'.$event);
  }
 }
}
 
publicfunctiongetObservableEvents()
{
 returnarray_merge(
  [
   'creating', 'created', 'updating', 'updated',
   'deleting', 'deleted', 'saving', 'saved',
   'restoring', 'restored',
  ],
  $this->observables
 );
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protectedfunctionfireModelEvent($event, $halt= true)
{
 if(! isset(static::$dispatcher)) {
  returntrue;
 }
 
 $method= $halt? 'until': 'fire';
 
 $result= $this->filterModelEventResults(
  $this->fireCustomModelEvent($event, $method)
 );
 
 if($result=== false) {
  returnfalse;
 }
 
 return! empty($result) ? $result: static::$dispatcher->{$method}(
  "eloquent.{$event}: ".static::class, $this
 );
}

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

看看fireCustomModelEvent的源码:

1
2
3
4
5
6
7
8
9
10
11
protectedfunctionfireCustomModelEvent($event, $method)
{
 if(! isset($this->events[$event])) {
  return;
 }
 
 $result= static::$dispatcher->$method(new$this->events[$event]($this));
 if(! is_null($result)) {
  return$result;
 }
}

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

1
2
3
4
5
classUser extendsModel{
 protected$events= [
  'saved'=> UserSaved::class
 ]
}

那这里的触发就是:

1
$result= static::$dispatcher->fire(newUserSaved($this));

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

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

1
2
3
4
5
6
7
if($result=== false) {
 returnfalse;
}
 
return! empty($result) ? $result: static::$dispatcher->{$method}(
 "eloquent.{$event}: ".static::class, $this
);

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

原文:https://www.jb51.net/article/136377.htm

免责声明:文章转载自《Laravel模型事件的实现原理详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇上传代码到Gitee忽略部分文件或目录Sublime报错下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python之(22)基础总结(5)

1、Python3 面向对象 1.面向对象技术简介 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。 方法:类中定义的函数。 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。 数据成员:类变量或者实例变量用于处理类及...

25.Android之图像的平移、旋转及缩放学习

在Android中,项目目录下的resdrawable用来放置该项目的图片资源。   Android中提供了Bitmap类来获取图像文件信息,进行图像的平移、旋转及缩放等操作,并可以指定格式保存图像文件。 1.图像绘制   在绘制图像之前,需要从项目目录下的resdrawable中获取所需的图片资源。我们可以通过资源索引来获得该图像对象Bitmap。具体...

python接口自动化测试八:更新Cookies、session保持会话

python接口自动化测试八:更新Cookies、session保持会话  s = requests.session()  # 此方法只适用于网站是cookies这种,网站是token的没用             # 这样做的好处就是可以保存cookies并保持会话,不用每次都去获取、传参     Token请求:                   ...

Prism初研究之使用Prism实现WPF的MVVM模式

Prism初研究之使用Prism实现WPF的MVVM模式 Prism初研究之使用Prism实现WPF的MVVM模式类职责和特征视图类(View)视图模型类(View Model)模型类(Model)类间的交互数据绑定(Data Binding)实现INotifyPropertyChanged实现INotifyCollectionChanged实现ICo...

C#通过模板导出Word(文字,表格,图片)

  C#导出Word,Excel的方法有很多,这次因为公司的业务需求,需要导出内容丰富(文字,表格,图片)的报告,以前的方法不好使,所以寻找新的导出方法,在网上找到了通过模板文件导出Word的方法,记录一下过程. 一:模板的创建                                  通过模板导出,肯定需要先创建模板,然后顾名思义就是将模板中提前...

库、教程、论文实现,这是一份超全的PyTorch资源列表(Github 2.2K星)

项目地址:https://github.com/bharathgs/Awesome-pytorch-list 列表结构: NLP 与语音处理 计算机视觉 概率/生成库 其他库 教程与示例 论文实现 PyTorch 其他项目 自然语言处理和语音处理 该部分项目涉及语音识别、多说话人语音处理、机器翻译、共指消解、情感分类、词嵌入/表征、语音生成、文本语音转...