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

7.1 本地作用域

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

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

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

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

  1. class User extends Model
  2. {
  3. /**
  4. * App\Models\Models\User
  5. *
  6. * 查找 状态为1 的条件
  7. *
  8. * 语法:scope 开头,后面名称尽可能包含语义
  9. *
  10. * @param $query
  11. * @return mixed
  12. */
  13. public function scopeStatusTrue($query)
  14. {
  15. return $query->where('status', 1);
  16. }
  17. }

然后在这个模型下调用:

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

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

  1. /**
  2. * App\Models\Models\User
  3. *
  4. * 查找 状态为value1,性别为value2 的条件
  5. *
  6. * 语法:scope 开头,后面名称尽可能包含语义
  7. *
  8. * @param $query
  9. * @param int $value1 状态
  10. * @param string $value2 性别
  11. * @return mixed
  12. */
  13. public function scopeStatusTrue($query, $value1 = 1, $value2 = '男')
  14. {
  15. return $query->where('status', $value1)->where('gender', $value2);
  16. }

然后再使用:

  1. $users = User::statusTrue(1, '女')
  2. ->where('price', '>', 90)
  3. ->get();
  4. return [$users];

7.2 全局作用域

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

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

7. 模型的作用域 - 图1

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

  1. <?php
  2. namespace App\Scopes;
  3. use Illuminate\Database\Eloquent\Builder;
  4. use Illuminate\Database\Eloquent\Model;
  5. use Illuminate\Database\Eloquent\Scope;
  6. class StatusScope implements Scope
  7. {
  8. /**
  9. * Apply the scope to a given Eloquent query builder.
  10. *
  11. * @param \Illuminate\Database\Eloquent\Builder $builder
  12. * @param \Illuminate\Database\Eloquent\Model $model
  13. * @return void
  14. */
  15. public function apply(Builder $builder, Model $model)
  16. {
  17. // TODO: Implement apply() method.
  18. }
  19. }

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

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

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

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

  1. use App\Scopes\StatusScope;//这里会自动引入
  2. class User extends Model
  3. {
  4. //输入:booted,根据提示添加即可
  5. protected static function booted()
  6. {
  7. parent::booted(); // TODO: Change the autogenerated stub
  8. //添加这行代码
  9. static::addGlobalScope(new StatusScope());
  10. }
  11. }

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

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

  1. use Illuminate\Database\Eloquent\Builder;//注意,引入的是这个Builder
  2. class User extends Model
  3. {
  4. //输入:booted,根据提示添加即可
  5. protected static function booted()
  6. {
  7. ///parent::booted(); // TODO: Change the autogenerated stub
  8. ///static::addGlobalScope(new StatusScope());
  9. //使用闭包实现,第一个参数是状态名,必须起名
  10. static::addGlobalScope('status', function (Builder $builder) {
  11. return $builder->where('status', 1);
  12. });
  13. //可以再创建一个:
  14. static::addGlobalScope('gender', function (Builder $builder) {
  15. return $builder->where('gender', '男');
  16. });
  17. }
  18. }

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

  1. //取消名称为status 的全局
  2. $users = User::withoutGlobalScope('status')->get();
  3. ///取消名为status,gender的两个全局:
  4. $users = User::withoutGlobalScopes(['status', 'gender'])->toSql();
  5. //取消所有的全局类的条件
  6. $users = User::withoutGlobalScope(StatusScope::class)->get();

以上。