Laravel
作为一款流行的 PHP 框架,以其丰富的功能著称,能够显著提升 Web 开发效率。Laravel Scopes
便是其中一项实用功能,可用于为 Eloquent
模型定义可重用且可链接的查询约束。本文将深入解析 Laravel Scopes
的概念,并带领您逐步实现它们在 Laravel
项目中的应用。
什么是 Laravel 范围?
Laravel
作用域是一种将查询约束封装为可重用且易于使用的方法的机制。它提供了一种便捷的方式来应用查询条件,有效减少代码重复,并提升代码组织的清晰度。
Laravel 范围的类型:
Laravel 作用域的两种类型:全局作用域
和局部作用域
。
全球范围:
全局作用域定义在模型内部,并自动应用于该模型的所有查询。它提供了一种便捷的方式来添加始终应用的通用查询条件。例如,您可以设置一个全局作用域,默认仅检索激活用户或仅检索已发布文章。当您需要全局强制执行某些条件,而不想在每个查询中显式添加它们时,全局作用域非常有用。
生成范围
创建新的全局范围,可以使用php artisan make:scope
命令,此命令能生成范围并将其放置在应用程序的app/Models/Scopes
目录中:
php artisan make:scope ActiveUserScope
创建全局作用域是一个便捷的过程
1、使用 make:scope 命令生成一个实现 Illuminate\Database\Eloquent\Scope 接口的类。
2、在生成的类中实现 apply 方法。
3、在 apply 方法中,根据需要添加 where 约束或其他类型的子句,以构建所需的查询条件。
<?php
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class ActiveUserScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*/
public function apply(Builder $builder, Model $model): void
{
$builder->where( column: 'is_active', operator: true);
}
}
应用全局范围
将全局作用域应用于模型的步骤如下:
1、重写模型的 booted 方法。
2、在 booted 方法中,调用模型的 addGlobalScope 方法。
3、将作用域的实例作为 addGlobalScope 方法的唯一参数。
<?php
namespace App\Models;
use App\Models\Scopes\ActiveUserScope;
class User extends Model
{
protected static function booted()
{
static::addGlobalScope(new ActiveUserScope());
}
}
把上例中的范围添加到 App\Models\User
模型后,再调用 all()
、get()
、first()
等方法,将会执行以下 SQL 查询:
select * from `users` where `is_active` = true
匿名全球范围
Eloquent
提供了使用闭包定义全局作用域的灵活性,方便创建不需要专用类的简单作用域。
1、指定自定义作用域名称作为 addGlobalScope
方法的第一个参数。
2、在闭包中,定义作用域的逻辑,通常是添加 where
约束或其他类型的子句。
/**
* The "booted" method of the model.
* @return void
*/
no usages
protected static function booted(): void
{
static::addGlobalScope( scope: 'activeUser', function (Builder $builder) {
$builder->where( column: 'is_active', operator: true);
});
}
删除全局范围
从特定查询中删除全局作用域,请使用 withoutGlobalScope
方法,此方法将全局范围的类名作为其唯一参数:
User :: withoutGlobalScope ( ActiveUserScope :: class )-> get ();
如果使用闭包定义全局作用域,请提供分配给全局作用域的字符串名称:
User :: withoutGlobalScope ( 'activeUser' )-> get ();
要删除查询的一个或多个全局范围,可以使用withoutGlobalScopes
方法:
// 删除所有作用域...
User :: withoutGlobalScopes ()-> get ();
// 删除特定范围...
User :: withoutGlobalScopes ([
ActiveUserScope :: class ,
HasEmailVerifiedScope :: class
])-> get ();
本地范围:
本地范围被定义为模型内的方法,用于在每次使用的基础上应用查询约束。当您想要根据用户输入或其他因素动态应用特定条件时,本地范围非常有用。提供了一种灵活的方式来向查询添加条件,而不会污染控制器/服务/存储库代码。
作用域应始终返回相同的查询构建器实例或void:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* @param Builder $builder
* @return void
*/
public function scopeEmailVerified(Builder $builder): void
{
$builder->whereNotNull('email_verified_at');
}
/**
* @param Builder $builder
* @return void
*/
public function scopeActiveUser(Builder $builder): void
{
$builder->where('is_active', true);
}
}
使用本地范围
定义范围后,可以在查询模型时调用其方法。但务必注意,调用该方法时不应包含范围前缀,还可以将调用链接到不同的范围:
use App\Models\User;
$users = User::emailVerified()->activeUser()->get();
在 SQL 中:
select * from `users` where `email_verified_at` is not null and `is_active` = true
要使用“or”查询运算符组合多个 Eloquent
模型范围,可能需要使用闭包来实现正确的逻辑分组:
User :: emailVerified ()-> orWhere (function (Builder $query ) {
$query -> activeUser ();
})-> get ();
在 SQL 中:
select * from `users` where (`email_verified_at` is not null or (`is_active` = true));
由于这可能会变得很麻烦,Laravel 提供了一个“更高阶”的orWhere
方法,能够流畅地将范围链接在一起,而不需要闭包:
User :: emailVerified ()->orWhere-> activeUser ();
动态范围
有时可能想要定义一个接受参数的范围,只需在作用域方法的签名中包含其他参数即可,范围参数应在 $query
参数之后声明:
/**
* @param Builder $query
* @param string $role
* @return void
*/
public function scopeOfRole ( Builder $query , string $role ): void
{
$query -> where ( 'role' , $role );
}
将所需的参数添加到作用域方法的签名后,可以在调用作用域时传递这些参数:
$users = User :: ofRole ( 'admin' )-> get ();
实现 Laravel 范围:
当我们需要定义很多范围时,为了避免类文件过大,我们可以使用 Trait
来将所有范围定义在一个独立的文件中,然后让模型类使用它。
use App\Models\User\UserScopeHelper;
class User extends Model
{
use UserScopeHelper;
/** 你的代码 */
}
<?php
declare(strict_types=1);
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\Builder;
trait UserScopeHelper
{
/**
* 只查询激活用户
*
* @param Builder $builder
* @return void
*/
public function scopeActiveUser(Builder $builder): void
{
$builder->where('is_active', true);
}
/**
* 只查询已验证邮箱的用户
*
* @param Builder $builder
* @return void
*/
public function scopeEmailVerified(Builder $builder): void
{
$builder->whereNotNull('email_verified_at');
}
/**
* 根据角色查询用户
*
* @param Builder $builder
* @param string $role
* @return void
*/
public function scopeOfRole(Builder $builder, string $role): void
{
$builder->where('role', $role);
}
}
为了方便使用范围,请在模型中使用 Trait:
<?php
namespace App\Models;
use App\Models\Traits\UserScopeHelper;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
use UserScopeHelper;
}
Laravel 范围的优点:
1、代码可重用性:范围允许您定义一次查询约束并在应用程序的不同部分中重用它们。这有助于消除代码重复并促进更干净、更易于维护的代码。
2、提高可读性:通过将查询约束封装在命名方法中,作用域使代码更具表现力且更易于理解。范围还有助于减少重复查询条件的噪音,从而产生更清晰、更易读的查询。
3、灵活性:Laravel 作用域提供了向查询动态添加条件的灵活性。通过本地作用域,您可以根据不同的条件轻松链接多个作用域方法,使您的代码更能适应不断变化的需求。
结论:
Laravel Scopes
提供了一种强大而优雅的方式来为 Eloquent
模型定义可重用的查询约束。通过将查询条件封装在作用域内,开发人员可以实现更简洁的代码,增强代码的可重用性,并提高 Laravel 应用程序的可维护性。凭借其易于实现和灵活性,Laravel 作用域成为所有 Laravel 开发人员的宝贵工具。
发表评论