Laravel Eloquent 模型中通过 Trait 来添加监听器。
laravel Eloquent 模型中通过 Trait 来添加监听器。
Eloquent 模型会触发许多事件,让你在模型的生命周期的多个时间点进行监控:
retrieved, creating, created, updating,updated,
saving, saved,deleting, deleted, restoring, restored。
事件让你在每当有特定模型类进行数据库保存或更新时,执行代码。
上面是 laravel china 上 laravel 手册中对于 laravel Eloquent 的事件描述。
并且上面提供了两种在 Eloquent 模型中添加事件的方法:
- 1.在 Eloquent 模型中添加
$dispatchesEvents
属性。
/**
* 此模型的事件映射.
*
* @var array
*/
protected $dispatchesEvents = [
'saved' => UserSaved::class,
'deleted' => UserDeleted::class,
];
- 2.使用观察者
Observer
,并将观察者在服务提供者中的 boot 方法中注册。
这两种方法都很不错,但是在有些场景下应该会有更好的方法来实现 Eloquent 事件。
现在我举一个大家开发中应该很常见的一个场景:
添加操作者信息,
有人会说添加操作者还不简单啊,在每个对数据操作的地方将操作者信息数据一并写入就好了。
确认这样是可以,但是我觉得这样写不简洁优雅。
既然我们在用一个很优雅的框架,那么为什么我们不思考一下用更简洁的方式来实现呢。
下面我就用自己认为较为简洁的方式简单分享出来:
-
3.通过给模型添加 Trait 来实现模型事件。
-
a.在你认为合适的地方新建一个用来添加操作人的 Trait 文件,内容如下。
<?php
/**
* Created by PhpStorm.
* User: coderChen
* Date: 2018/9/2
* Time: 16:51
*/
namespace App\Models\Traits;
use App\Observers\OperatorObserve;
trait OperatorTrait
{
//这里模型实例化的时候,会执行该静态方法。
public static function bootOperatorTrait(){
//给模型绑定操作人监听器
static::observe(OperatorObserve::class);
}
}
- b.在合适的地方,我们新建一个操作人监听器类 OperatorObserve,内容如下:
<?php
namespace App\Observers;
// creating, created, updating, updated, saving,
// saved, deleting, deleted, restoring, restored
use Illuminate\Database\Eloquent\Model;
class OperatorObserver
{
//新增修改会触发该方法
public function saving(Model $model){
//这里面做写入操作人处理逻辑
}
//删除会出发该方法
public function deleting(Model $model){
//这里面做写操作人处理逻辑
}
}
- c.最后一步就是在需要记录操作人的模型去 USE 这个 OperatorTrait 文件。
<?php
namespace App;
use App\Models\Traits\OperatorTrait;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
//通过 use OperatorTrait 来实现操作人自动写入。
use OperatorTrait;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password','operator'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
到这里,通过给模型添加 Trait 来实现模型事件就完成了,可能有些同学会有一写疑问,为什么模型会自动执行 trait 中的某些方法,答案就在下面,
注意,这里你应该了解什么是静态延迟绑定,不然会被多个 static 调用给绕晕:
我们先看 Illuminate\Database\Eloquent
命名空间下的 Model 抽象类,
该类是所有 Eloquent 模型的基类,我们看到它的构造函数:
/**
* Create a new Eloquent model instance.
*
* @param array $attributes
* @return void
*/
public function __construct(array $attributes = [])
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
接下来我们去看到构造函数中调用的 bootIfNotBooted() 方法:
/**
* Check if the model needs to be booted and if so, do it.
*
* @return void
*/
protected function bootIfNotBooted()
{
//如果该模型未被实例化过,就去执行 if 里面的代码,去实例化它。
if (! isset(static::$booted[static::class])) {
//将模型标注为已实例化。
static::$booted[static::class] = true;
$this->fireModelEvent('booting', false);
//通过静态延迟绑定去执行该模型的 boot() 启动方法
static::boot();
$this->fireModelEvent('booted', false);
}
}
接下来看到在 bootIfNotBooted() 中的 boot() 方法,另外两个 fireModelEvent() 暂时先不管:
/**
* The "booting" method of the model.
*
* @return void
*/
protected static function boot()
{
static::bootTraits();
}
接下来去看到 boot() 方法中的 bootTraits() 方法,欸,等下,我们关注一下这个方法的名字,启动 traits,在模型中能自动运行 trait 中的某些方法应该就是它的功劳了:
/**
* Boot all of the bootable traits on the model.
*
* @return void
*/
protected static function bootTraits()
{
//获取该模型的类名,包含其命名空间,比如这样:App\User
$class = static::class;
//用于存放已经执行过的方法缓存
$booted = [];
//class_user_recursive 方法获取该类以及该类的父类所有的 trait
foreach (class_uses_recursive($class) as $trait) {
//通过给 trait 名称前面添加 boot 来生成对应 trait 中的 启动方法
$method = 'boot'.class_basename($trait);
//检查模型类中是否存在 trait 中的 自动启动方法,并且检查该方法是否已经被执行过。
if (method_exists($class, $method) && ! in_array($method, $booted)) {
//执行 trait 中的方法
forward_static_call([$class, $method]);
//将已经执行过的方法放入放到 已执行方法的缓存中,防止重复执行
$booted[] = $method;
}
}
}
好了,到这儿就算是已经知道了 Eloquent 模型中的启动方法执行的原理了,
在 githab 上有一个 laravel Eloquent 的数据验证包也是运用的上面的原理,链接在此,有兴趣的同学可以去研究下。
其实这里还要注意的一点就是:
对于 trait 中的启动方法命名应该是在 trait 名称前面添加 boot 字符。
当然,这里只是说了如何通过 trait 来添加监听器,也只是分析了添加监听器的原理。
往后有时间我会继续分享一下监听器的工作原理,其实也就是事件的工作原理。
当然以上都是作者自己的看法,如果大家认为我的看法可以更进一步改进的话,欢迎留言讨论。
本作品采用《CC 协议》,转载必须注明作者和本文链接
更简单的方法
相应Model 里直接use 这个trait就可以了,
这里只是提个简单方法,但不建议这么用。
@Jourdon 是的,也可以在 trait 中启动方法中调用
static::saving
static::deleteing
等方法来注册对应的观察闭包。
更方便代码低耦合了
@Jourdon 请恕在下愚昧,这样写好像不能注入其他实例,比较有局限性. 请大佬指点12
@飘儿白 你说的对,正常写法还是要用楼主的方法,独立出来一个是扩展复用,另一个是方便理解,我写的方法只是简单,但不一定实用,不过在开发composer包倒是比较方便。
干脆点:
:laughing: