3分钟短文:Laravel 从软删除说到模型作用域的概念

引言

上一节我们讲了通过模型方法新建条目,或者更新数据。对于写操作还有更为重要的一个方法, 就是数据的删除。删除数据,有物理删除和软删除的区别。

我们从软删除的使用,再顺便说一说模型内的作用域的概念。

代码时间

常规的删除操作分两步进行,一步是把数据从数据库中查询出来,使用laravel模型的方法, 则返回的是一个模型对象。第二步,调用模型对象的delete方法。代码如下:

$contact=Contact::find(5);$contact->delete();

如果像上面的代码那样,已知数据条目的ID,那么可以直接使用destroy方法进行删除:

Contact::destroy(1);

该方法可以可以用于批量删除传入的指定ID数组的条目:

Contact::destroy([1,5,7]);

当然了,delete方法只是链式调用的一个方法,我们通过查询构造器过滤后的数据集, 都可以调用该方法将其删除:

Contact::where('updated_at','>',Carbon::now()->subYear())->delete();

上面代码实现的是,超过一年没有更新过的contact,将其删除。这可能会有很多,也没有问题。

上面的delete方法,destroy方法,都是对数据的物理删除。数据库的表内记录直接移除了,这在重要的表, 比如user,order,payment这些关系用户权限,资金支付等等的重要数据资源上,物理删除是不被允许的。

所以引入了软删除的概念,就是在表内添加一个字段,用于标记,这一行条目是否算是删除状态。在laravel中, 这个软删除字段默认是 deleted_at。你也可以在模型中手动指定。

如果你使用系统的migrate方法创建迁移文件,那么只用在构造方法中添加如下代码:

Schema::table('contacts',function(Blueprint$table){

$table->softDeletes();});

那么迁移成功后,生成的contacts表内会添加deleted_at字段。

然后在模型中,引入软删除的功能,将其进行全局生效的使用。模型中相关代码如下:

useIlluminate\Database\Eloquent\Model;useIlluminate\Database\Eloquent\SoftDeletes;classContactextendsModel{

useSoftDeletes;// 这是一个 trait

protected$dates=['deleted_at'];// 指定deleted_at的datetime格式}

通过追寻源代码,我们注意到 SoftDeletes 其实是注册了一个模型内的全局作用域方法:

publicstaticfunctionbootSoftDeletes(){

static::addGlobalScope(newSoftDeletingScope);}

这样在应用程序内,使用该模型的所有方法,都会被追加全局可见的查询条件。我们看源码这样一段,就是用于标记进行删除的:

protectedfunctionrunSoftDelete(){

$time=$this->freshTimestamp();

// ...

$this->{$this->getDeletedAtColumn()}=$time;

// ...

$query->update($columns);}

为了说明问题,我们把中间几行代码都省略了。大家注意, 想我们的常规操作一样,就是获取一个时间戳$time, 然后把字段赋值:

$this->deleted_at=$time;

最后使用update方法更新模型,并修改数据库条目。是不是把知识点都连贯起来了?

既然说到了模型作用域,我们不妨延伸一下,说说这个设计点,以及适用的场景。

比如说有一个查询条件在代码内到处都要用,有没有简写方法,写一次其他地方可以随意调用呢?这就是本地作用域的方法了。来看一个实例:

$activeVips=Contact::where('vip',true)->where('trial',false)->get();

比如说这两个where约束很常用,我们要是能简写为类似下面这样会更直观:

$activeVips=Contact::activeVips()->get();

实现此方法,很简单,只需在模型文件内添加如下方法:

classContactextendsModel{

publicfunctionscopeActiveVips($query)

{

return$query->where('vip',true)->where('trial',false);

}}

给本地的作用域方法添加传入的参数:

classContactextendsModel{

publicfunctionscopeStatus($query,$status)

{

return$query->where('status',$status);

}}

用的时候直接把数据作为入参传送即可:

$friends=Contact::status('friend')->get();

这些方法都是要手动显式调用才能使用的。如果是想软删除条目那样,默认把所有的查询都追加 自定义的查询条件,就需要我们上面说的全局作用域了。

声明一个全局作用域很简单,只需在模型文件内添加如下代码:

classContactextendsModel{

protectedstaticfunctionboot()

{

parent::boot();

static::addGlobalScope('active',function(Builder$builder){

$builder->where('active',true);

});

}}

那么所有的模型查询,都会默认加上声明中的where约束。一下子节省了很多冗余的代码。

如果你的全局作用域写的逻辑会有点多喝复杂,可以将其独立出来,写成类,以便调用。创建 app/Scopes/ActiveScope.php 文件:

namespaceApp\Scopes;useIlluminate\Database\Eloquent\Scope;useIlluminate\Database\Eloquent\Model;useIlluminate\Database\Eloquent\Builder;classActiveScopeimplementsScope{

publicfunctionapply(Builder$builder,Model$model)

{

return$builder->where('active',true);

}}

在模型 Contact 内调用:

useApp\Scopes\ActiveScope;useIlluminate\Database\Eloquent\Model;classContactextendsModel{

protectedstaticfunctionboot()

{

parent::boot();

static::addGlobalScope(newActiveScope);

}}

如果你的代码中这样的全局作用域用处很多,许多表结构,或者模型的设计逻辑上, 都兼容了此用法,那么独立成一个Scope类更为实用。

写在最后

本文从laravel模型的写操作删除动作,讲到了软删除的概念。进而引申出来本地作用域和全局作用域的使用。软删除几乎贯穿了我们应用的始终,需要大家勤学苦练。作用域的概念,设计起来很灵活, 但是维护需要一些设计规范,更适合团队作战的中型大型应用。

Happy coding :-)

我是@程序员小助手,持续分享编程知识,欢迎关注。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200909A0J04M00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券