【Laravel】7. 模型的作用域

527 阅读2分钟

作者:拾年之璐   公众号:知行研究院

很多情况下,我们在数据查找时,有一部分条件会被重复且大量使用,通过作用域,将常用的SQL封装,会简化操作。

7.1 本地作用域

1、在某个条件下,只是这个模型对应的数据表使用,别的表并不使用,那么可以使用 本地作用域 将常用的SQL封装起来。

2、比如,在用户模块中,需要大量查询 状态为1 的数据,然后在且其他条件:

$users = User::where('status', 1)
    ->where('price', '>', 90)
    ->get();

这时,我们就可以将 状态为1 的这个片段,封装成一个单独的方法:

class User extends Model
{
    /**
     * App\Models\Models\User
     *
     * 查找 状态为1 的条件
     *
     * 语法:scope 开头,后面名称尽可能包含语义
     *
     * @param $query
     * @return mixed
     */
    public function scopeStatusTrue($query)
    {
        return $query->where('status', 1);
    }
}

然后在这个模型下调用:

$users = User::statusTrue()
    ->where('price', '>', 90)
    ->get();
return [$users];

3、上面这种方式是比较死板的。可以通过参数传递的方式,使本地作用域更灵活:

/**
 * App\Models\Models\User
 *
 * 查找 状态为value1,性别为value2 的条件
 *
 * 语法:scope 开头,后面名称尽可能包含语义
 *
 * @param $query
 * @param int $value1 状态
 * @param string $value2 性别
 * @return mixed
 */
public function scopeStatusTrue($query, $value1 = 1, $value2 = '男')
{
    return $query->where('status', $value1)->where('gender', $value2);
}

然后再使用:

$users = User::statusTrue(1, '女')
    ->where('price', '>', 90)
    ->get();

return [$users];

7.2 全局作用域

全局作用域,顾名思义就是在任意地方都可以有效的封装条件;

1、比如有个需求,不管在哪里操作,总是显示status 为1 的用户;那么首先在 app 目录下创建一个用于全局作用域的目录:Scopes,然后创建一个类:StatusScope,并让其实现Scope implements Scope,如下:

image-20210131151825531

这里,会报错,按 Alt+Enter ,根据提示添加代码即可。此时,完整的代码是这样的:

<?php
namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class StatusScope implements Scope
{

    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param \Illuminate\Database\Eloquent\Builder $builder
     * @param \Illuminate\Database\Eloquent\Model $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        // TODO: Implement apply() method.
        
    }
}

然后在上面的 apply 方法中写入内容:

public function apply(Builder $builder, Model $model)
{
    // TODO: Implement apply() method.
    return $builder->where('status', 1);
}

此时,还不能实现全局,因为需要在模型设置个开关,让其富有灵活性;

User 模型中,写入如下内容:

use App\Scopes\StatusScope;//这里会自动引入

class User extends Model
{
    //输入:booted,根据提示添加即可
    protected static function booted()
    {
        parent::booted(); // TODO: Change the autogenerated stub
        //添加这行代码
        static::addGlobalScope(new StatusScope());
    }
}

然后,在控制层,不需要添加任何设置,即可自动为所有查询添加 status=1 的条件。

2、如果某个全局,只是针对某个模块,并不需要创建一个全局类,直接闭包即可实现:

use Illuminate\Database\Eloquent\Builder;//注意,引入的是这个Builder

class User extends Model
{
    //输入:booted,根据提示添加即可
    protected static function booted()
    {
        ///parent::booted(); // TODO: Change the autogenerated stub
        ///static::addGlobalScope(new StatusScope());
        
        //使用闭包实现,第一个参数是状态名,必须起名
        static::addGlobalScope('status', function (Builder $builder) {
            return $builder->where('status', 1);
        });
        
        //可以再创建一个:
        static::addGlobalScope('gender', function (Builder $builder) {
            return $builder->where('gender', '男');
        });
    }
}

3、如果某个查询,不需要这个全局条件,可以单独移出掉:

//取消名称为status 的全局
$users = User::withoutGlobalScope('status')->get();

///取消名为status,gender的两个全局:
$users = User::withoutGlobalScopes(['status', 'gender'])->toSql();

//取消所有的全局类的条件
$users = User::withoutGlobalScope(StatusScope::class)->get();

以上。