Mohamd-Fouladgar/eloquent-builder

v5.0.0 2024-04-06 12:27 UTC

README

alt text

Latest Version on Packagist Test Status Code Style Status Total Downloads

此包允许您根据传入的请求参数构建优雅的查询。这极大地简化了查询和条件,将使您的代码更清晰、易于维护。

版本兼容性

基本用法

假设您想获取以下请求参数的用户列表

//Get api/user/search?age_more_than=25&gender=male&has_published_post=true
[
    'age_more_than'  => '25',
    'gender'         => 'male',
    'has_published_post' => true,
]

在常见的实现中,预期以下代码将执行

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index(Request $request)
    {
        $users = User::where('is_active', true);

        if ($request->has('age_more_than')) {
            $users->where('age', '>', $request->age_more_than);
        }

        if ($request->has('gender')) {
            $users->where('gender', $request->gender);
        }

        // A User model may have an infinite numbers of Post(One-To-Many).
        if ($request->has('has_published_post')) {
            $users->where(function ($query) use ($request) {
                $query->whereHas('posts', function ($query) use ($request) {
                    $query->where('is_published', $request->has_published_post);
                });
            });
        }

        return $users->get();
    }
}

但是 在使用 EloquentBuilder 之后,上述代码将变为以下形式

<?php

namespace App\Http\Controllers;

use App\User;
use EloquentBuilder;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index(Request $request)
    {
        return EloquentBuilder::model(User::class)
            ->filters($request->all())
            ->thenApply()
            ->get();
    }
}

您只需为要添加到查询中的每个参数定义一个过滤器。

安装

您可以通过 composer 安装此包

composer require mohammad-fouladgar/eloquent-builder

警告: Lumen 框架不再受支持!

过滤器命名空间

所有过滤器的默认命名空间为 App\EloquentFilters,其基础名称为模型。例如,对于 User 模型,过滤器命名空间将为 App\EloquentFilters\User

├── app
├── Console
│   └── Kernel.php
├── EloquentFilters
│   └── User
│       ├── AgeMoreThanFilter.php
│       └── GenderFilter.php
└── Exceptions
    └── Handler.php

通过配置文件自定义

您可以选择使用以下命令发布配置文件

php artisan vendor:publish --provider="Fouladgar\EloquentBuilder\ServiceProvider" --tag="config"

并设置您模型的过滤器命名空间,它们将位于

return [
    /*
     |--------------------------------------------------------------------------
     | Eloquent Filter Settings
     |--------------------------------------------------------------------------
     |
     | Here you should specify default all you Eloquent Model Filters.
     |
     */
    'namespace' => 'App\\EloquentFilters\\',
];

定义一个过滤器

编写过滤器很简单。定义一个类,该类从 Fouladgar\EloquentBuilder\Support\Foundation\Contracts\Filter 抽象类扩展。此类要求您实现一个方法:apply。根据需要,apply 方法可以添加查询的 where 约束。每个过滤器类应以 Filter 字尾。

例如,以下是一个过滤器定义的示例

<?php

namespace App\EloquentFilters\User;

use Fouladgar\EloquentBuilder\Support\Foundation\Contracts\Filter;
use Illuminate\Database\Eloquent\Builder;

class AgeMoreThanFilter extends Filter
{
    /**
     * Apply the age condition to the query.
     */
    public function apply(Builder $builder, mixed $value): Builder
    {
        return $builder->where('age', '>', $value);
    }
}

提示:您还可以在过滤器中使用 本地作用域。因为,它们是查询构建器的实例。

通过 artisan 命令定义过滤器

如果您想轻松创建一个过滤器,可以使用 eloquent-builder:make artisan 命令。此命令将接受至少两个参数,分别是 ModelFilter

php artisan eloquent-builder:make user age_more_than

您还可以同时创建多个过滤器。为了实现此目标,您应该将多个名称传递给 Filter 参数。

php artisan eloquent-builder:make user age_more_than gender

使用过滤器

您可以使用多种方法使用过滤器

<?php

// Use by a model class name
$users = EloquentBuilder::model(\App\Models\User::class)->filters(request()->all())->thenApply()->get();

// Use by existing query
$query = \App\Models\User::where('is_active', true);

$users = EloquentBuilder::model($query)
        ->filters(request()->all())
        ->thenApply()
        ->where('city', 'london')
        ->get();

// Use by instance of a model and push filter
$users = EloquentBuilder::model(new \App\Models\User())
        ->filters(request()->filter)
        ->filter(['age_more_than' => '30'])
        ->filter(['gender' => 'female'])
        ->thenApply()
        ->get();

提示:建议将您的查询参数放在以下过滤器键中

user/search?filter[age_more_than]=25&filter[gender]=male

然后这样使用它们:request()->filter

使用预定义过滤器

此包提供了一些使用字符串约定的预定义过滤器,您可以在过滤器类中轻松使用它们。

提示:所有字符串约定中的值都将根据使用的过滤器进行验证。

日期过滤器

日期过滤是您可能使用的最常用的过滤器之一,您可以通过以下约定在过滤器中使用它们:between:date1,date2before:datebefore_or_equal:dateafter:dateafter_or_equal:datesame:dateequals:date

示例

api/user/search?birth_date=before:2018-01-01

# These are similar between convention:
api/user/search?birth_date=between:2018-01-01,2022-01-01
api/article/search?birth_date=2018-01-01,2022-01-01 
api/article/search?birth_date[]=2018-01-01&birth_date[]=2022-01-01 

# These are similar equals convention:
api/user/search?birth_date=equals:2018-01-01
api/user/search?birth_date=same:2018-01-01
api/user/search?birth_date=2018-01-01

您只需要定义一个过滤器,并使用 Fouladgar\EloquentBuilder\Concerns\FiltersDatesTrait 特性。例如

<?php

namespace App\EloquentFilters\User;

use Fouladgar\EloquentBuilder\Concerns\FiltersDatesTrait;
use Fouladgar\EloquentBuilder\Support\Foundation\Contracts\Filter;
use Illuminate\Database\Eloquent\Builder;

class BirthDateFilter extends Filter
{
    use FiltersDatesTrait;

    public function apply(Builder $builder, mixed $value): Builder
    {
        return $this->filterDate($builder, $value, 'birth_date');
    }
}

数字过滤器

另一个预定义过滤器是数字过滤器,您可以在过滤器中使用它们。例如,对于价格过滤器、分数过滤器以及任何数字过滤器,这非常有用。您可以遵循以下数字约定
between:number1,number2gt:numbergte:numberlt:numberlte:numberequals:number

示例

api/user/search?score=gte:500

# These are similar between convention:
api/user/search?score=between:100,1010
api/article/search?score=100,1010
api/article/search?score[]=100&score[]=1010

# These are similar equals convention:
api/user/search?score=equals:2222
api/user/search?score=2222

例如,创建一个 ScoreFilter 并使用 Fouladgar\EloquentBuilder\Concerns\FiltersNumbersTrait 特性,如下所示

<?php

namespace App\EloquentFilters\User;

use Fouladgar\EloquentBuilder\Concerns\FiltersNumbersTrait;
use Fouladgar\EloquentBuilder\Support\Foundation\Contracts\Filter;
use Illuminate\Database\Eloquent\Builder;

class ScoreFilter extends Filter
{
    use FiltersNumbersTrait;

    public function apply(Builder $builder, mixed $value): Builder
    {
        return $this->filterNumber($builder, $value, 'score');
    }
}

排序过滤器

您可能希望对查询过滤器进行排序。有一些用法可以使它变得

示例

api/user/search?sort_by[birth_date]=desc&sort_by[id]=asc

api/user/search?sort_by[]=birth_date:desc&sort_by[]=id:asc

# The default direction is `asc`:
api/user/search?sort_by[]=birth_date

例如,创建一个SortByFilter并使用Fouladgar\EloquentBuilder\Concerns\SortableTrait特性。

<?php

namespace App\EloquentBuilders\User;

use Fouladgar\EloquentBuilder\Concerns\SortableTrait;
use Fouladgar\EloquentBuilder\Support\Foundation\Contracts\Filter;
use Illuminate\Database\Eloquent\Builder;

class SortByFilter extends Filter
{
    use SortableTrait;

    protected array $sortable = [
        'birth_date', 'score',
    ];

    public function apply(Builder $builder, mixed $value): Builder
    {
        return $this->applySort($builder, $value);
    }
}

提示:可排序的列(s)应由$sortable属性指定。

授权过滤器

过滤器类还包含一个authorize方法。在这个方法中,您可以检查认证用户是否确实有权应用给定的过滤器。例如,您可以确定用户是否拥有高级账户,是否可以应用StatusFilter以列出在线或离线的人

/**
 * Determine if the user is authorized to make this filter.
 */
 public function authorize(): bool
 {
     if(auth()->user()->hasPremiumAccount()){
        return true;
     }

    return false;
 }

默认情况下,您无需实现authorize方法,过滤器将应用于您的查询构建器。如果authorize方法返回false,将自动返回带有403状态码的HTTP响应。

忽略null值上的过滤器

如果包含null值,将忽略过滤器参数。

假设您有一个类似的请求

//Get api/user/search?filter[name]&filter[gender]=null&filter[age_more_than]=''&filter[published_post]=true

EloquentBuilder::model(User::class)->filters($request->filter)->thenApply();

// filters result will be:
$filters = [
    'published_post' => true
];

只有"published_post"过滤器将应用于您的查询。

按域/模块自定义

当您有一个具有自定义目录结构的laravel项目时,您可能需要在多个目录中拥有多个过滤器。为此,您可以使用setFilterNamespace()方法并将所需的命名空间传递给它。

例如,假设您有一个实现基于域的结构的项目

.
├── app
├── bootstrap
├── config
├── database
├── Domains
│   ├── Store
│   │   ├── database
│   │   │   └── migrations
│   │   ├── src
│   │       ├── Filters // We put our Store domain filters here!
│   │       │   └── StoreFilter.php
│   │       ├── Entities
│   │       ├── Http
│   │          └── Controllers
│   │       ├── routes
│   │       └── Services
│   ├── User
│   │   ├── database
│   │   │   └── migrations
│   │   ├── src
│   │       ├── Filters // We put our User domain filters here!
│   │       │   └── UserFilter.php
│   │       ├── Entities
│   │       ├── Http
│   │          └── Controllers
│   │       ├── routes
│   │       └── Services
...

在上面的例子中,每个域都有自己的过滤器目录。因此,我们可以设置并使用自定义命名空间过滤器如下

$stores = EloquentBuilder::model(\Domains\Entities\Store::class)
            ->filters($request->all())
            ->setFilterNamespace('Domains\\Store\\Filters')
            ->thenApply()
            ->get();

注意:在调用setFilterNamespace()时,将忽略默认命名空间和配置文件。

作为依赖注入使用

您可能需要在constructfunction方法中将EloquentBuilder用作DependencyInjection

假设您有一个UserController,并且您想对用户应用一些过滤器来获取用户列表

<?php

namespace App\Controllers;

use App\Http\Resources\UserResource;
use App\Models\User;
use Fouladgar\EloquentBuilder\EloquentBuilder as Builder;
use Fouladgar\EloquentBuilder\Exceptions\FilterException;
use Illuminate\Http\Request;

class UserController
{
    public function index(Request $request, User $user, Builder $builder)
    {
        $users = $user->newQuery()->where('is_active', true);
        try {
            $builder->model($users)
                    ->filters($request->filter)
                    ->thenApply();
        } catch (FilterException $filterException) {
            //...
        }

        return UserResource::collection($users->get());
    }
}

就是这样。

测试

composer test

贡献

有关详细信息,请参阅CONTRIBUTING

安全

如果您发现任何安全相关的问题,请通过电子邮件fouladgar.dev@gmail.com而不是使用问题跟踪器。

许可证

Eloquent-Builder是在MIT许可证下发布的。有关详细信息,请参阅捆绑的LICENSE文件。

用❤️为您打造。