深入解析 Laravel Scopes:精简代码,优化查询

admin 2024-01-31 124 阅读 0评论

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 开发人员的宝贵工具。

发表评论

快捷回复: 表情:
Addoil Applause Badlaugh Bomb Coffee Fabulous Facepalm Feces Frown Heyha Insidious KeepFighting NoProb PigHead Shocked Sinistersmile Slap Social Sweat Tolaugh Watermelon Witty Wow Yeah Yellowdog
提交
评论列表 (有 0 条评论, 124人围观)
0.159641s