coliving/eloquent-filter

Eloquent Filter 是一个通过查询字符串过滤模型数据的包。易于使用且完全动态。

3.0.0 2022-04-05 09:37 UTC

This package is not auto-updated.

Last update: 2024-09-18 20:55:00 UTC


README

alt text

Latest Stable Version Run tests License GitHub stars StyleCI Build Status Monthly Downloads

Eloquent Filter 为 Laravel 的 Eloquent 模型添加自定义过滤器。易于使用且完全动态。

Bitcoin Donate Button

目录

要求

  • PHP 7.2+, 8.0, 8.1 (新版本)
  • Laravel 5.8+,6.x,7.x,8.x,9.x(推荐稳定版)

🎤 介绍

假设我们想要制作一个带有多个过滤选项参数的高级搜索页面。

alt text

没有 Eloquent Filter 的简单实现

请求 URI 可能看起来像这样

https://:8000/users/index?age_more_than=25&gender=male&created_at=25-09-2019

在控制器中的简单实现可能如下所示

<?php

namespace App\Http\Controllers;

use App\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);
        }

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

        return $users->get();
    }
}

这个解决方案很简单,但效果不错。但你需要为每个需要的过滤器添加一个条件。特别是对于更复杂的过滤,你的代码可能会迅速变成一个怪物!💥

使用 Eloquent Filter 的简单实现

Eloquent Filter 可以帮助你解决这个问题。只需设置查询字符串来与它一起工作。这将节省你的时间并最小化你的代码复杂度。

安装 Eloquent Filter 后的请求 URI 可能如下所示

https://:8000/users/list?age_more_than[operator]=>&age[value]=35&gender=male&created_at[operator]==>&created_at[value]=25-09-2019

在控制器中,你只需要这一行

/**
 * Class UsersController.
 */

namespace App\Http\Controllers;

use App\User;

class UsersController
{
    public function list()
    {
        return User::filter()->get();
    }
}

使用这个 Eloquent filter 实现,你可以使用所有文档中记录的过滤器!

🔌 安装

1- 运行以下 Composer 命令以安装最新版本

  $ composer require coliving/eloquent-filter
  • 注意 对于低于 5.8 的 Laravel 版本,应安装 2.2.5 版本

      $ composer require mehdi-fathi/eloquent-filter:2.2.5
    

2- 将 eloquentFilter\ServiceProvider::class 添加到 app.php 中的提供者

'providers' => [
  /*
   * Package Service Providers...
   */
    eloquentFilter\ServiceProvider::class
],

3- 将 Facade 'EloquentFilter' => eloquentFilter\Facade\EloquentFilter::class 添加到 app.php 中的别名

'alias' => [
  /*
   * Facade alias...
   */
    'EloquentFilter' => eloquentFilter\Facade\EloquentFilter::class,
],

就是这样!享受吧!💥

📖 基本用法

配置模型并设置白名单

将 Filterable 特性添加到你的模型中,并在白名单数组中设置你想要过滤的字段。你也可以在你的模型中覆盖此方法。

use eloquentFilter\QueryFilter\ModelFilters\Filterable;

class User extends Model
{
    use Filterable;
    
    private static $whiteListFilter =[
        'id',
        'username',
        'email',
        'created_at',
        'updated_at',
    ];
}

你可以为该过滤器设置 * 字符,如以下示例所示

private static $whiteListFilter = ['*'];

你可以在方法中动态添加或设置 $whiteListFilter。例如

将数组设置为 WhiteListFilter

  • 注意 此方法会覆盖 $whiteListFilter 数组
User::setWhiteListFilter(['name']); 

向 WhiteListFilter 添加新字段

User::addWhiteListFilter('name'); 

在控制器中使用

按照以下示例更改 Laravel 项目的控制器代码

namespace App\Http\Controllers;

/**
 * Class UsersController.
 */
class UsersController
{

    public function list()
    {
          if (!empty(request()->get('username'))) {
          
              $users = User::ignoreRequest('perpage')->filter()->with('posts')
                        ->orderByDesc('id')->paginate(request()->get('perpage'),['*'],'page');

          } else {
              $users = User::filter(
                ['username' => ['mehdi','ali']]           
                )->with('posts')->orderByDesc('id')->paginate(10,['*'],'page');
          }
    }
}

注意 Eloquent Filter 默认使用查询字符串在 Laravel 中进行查询。虽然如此,你可以将数组设置为模型的 filter 方法,以创建自己的自定义条件,而不使用查询字符串。

注意 因此,你必须自行取消 perpage 参数。你只能设置页面参数以分页此参数,该参数会从过滤器中忽略。

  • 你可以使用以下代码忽略一些请求参数。
User::ignoreRequest(['perpage'])
            ->filter()
            ->paginate(request()->get('perpage'), ['*'], 'page');

调用 ignoreRequest 来忽略一些你不想在 eloquent 过滤器条件中使用的要求。例如,每页参数永远不会在 eloquent 过滤器条件中。这与分页方法相关。在 Laravel 的 Eloquent 过滤器中,默认忽略 page 参数。

  • 你可以过滤一些请求参数来在 Eloquent 过滤器中使用。
User::AcceptRequest(['username','id'])
            ->filter()
            ->paginate(request()->get('perpage'), ['*'], 'page');

调用 AcceptRequest 将接受你想要在 Eloquent 过滤器条件中使用的要求。例如,usernameid 参数将在 eloquent 过滤器条件中。请注意,你必须在模型中设置 $whiteListFilter。此方法对于用户通过查询字符串进行查询字符串操作非常有用。

  • 另一个 eloquent 过滤器过滤器的示例用途。
User::filter()->paginate();
  • EloquentFilter::filterRequests() 获取所有 Eloquent 过滤器使用的参数。你可以设置键来获取特定索引。例如,EloquentFilter::filterRequests('username') 获取用户名索引。

  • EloquentFilter::getAcceptedRequest() 获取由 AcceptRequest 方法设置的参数。

  • EloquentFilter::getIgnoredRequest() 获取由 getIgnoreRequest 方法设置的忽略参数。

简单示例

你只需在控制器方法中传递数据 blade 表单到查询字符串或生成查询字符串。例如

简单 Where

/users/list?email=mehdifathi.developer@gmail.com

SELECT ... WHERE ... email = 'mehdifathi.developer@gmail.com'
/users/list?first_name=mehdi&last_name=fathi

SELECT ... WHERE ... first_name = 'mehdi' AND last_name = 'fathi'

Where In

此示例创建方法 whereIn

/users/list?username[]=ali&username[]=ali22&family=ahmadi

SELECT ... WHERE ... username in ('ali','ali22') AND family = 'ahmadi'

OrWhere (新功能 🔥)

此示例创建方法 orWhere

/users/list?name=mehdi&username=fathi&or[username]=ali

SELECT ... WHERE ... name = 'mehdi' AND username = 'fathi' or username = 'ali'

Where like

如果你打算根据 like 条件进行查询,你可以通过此示例进行。

/users/list?first_name[like]=%John%

SELECT ... WHERE ... first_name LIKE '%John%'

Where 通过操作符

你可以在查询字符串中设置任何 MySQL 操作符。

/users/list?count_posts[operator]=>&count_posts[value]=35

SELECT ... WHERE ... count_posts > 35
/users/list?username[operator]=!=&username[value]=ali

SELECT ... WHERE ... username != 'ali'
/users/list?count_posts[operator]=<&count_posts[value]=25

SELECT ... WHERE ... count_posts < 25

Where 嵌套关系模型 (新功能 🔥)

你只需通过查询字符串的数组即可设置所有嵌套关系。例如,用户模型与帖子相关联。帖子表与订单表相关联。你可以在查询字符串中设置 'posts[count_post]' 和 'posts[orders][name]' 来创建查询条件。请注意,你必须在 User 模型中设置 'posts.count_post' 和 'posts.orders.name'。

use eloquentFilter\QueryFilter\ModelFilters\Filterable;

class User extends Model
{
    use Filterable;
   
    private static $whiteListFilter =[
        'username',
        'posts.count_post',
        'posts.category',
        'posts.orders.name',
    ];

    /**
     * @return \Illuminate\Database\Eloquent\Relations\belongsTo
     */
    public function posts()
    {
        return $this->belongsTo('Models\Post');
    }

}
/users/list?posts[count_post]=876&username=mehdi

select * from "users" where exists 
         (select * from "posts" where "posts"."user_id" = "users"."id" 
         and "posts"."count_post" = 876)
         and "username" = "mehdi"
  • 上述示例与不使用 eloquent 过滤器的相同代码。请在代码部分下检查。
$user = new User();
$builder = $user->with('posts');
        $builder->whereHas('posts', function ($q) {
            $q->where('count_post', 876);
        })->where('username','mehdi');

Where 数组的嵌套关系模型

你可以传递数组来创建 whereIn 条件。

/users/list?posts[category][]=php&posts[category][]=laravel&posts[category][]=jquery&username=mehdi

select * from "users" where exists 
         (select * from "posts" where 
         "posts"."category" in ('php','laravel','jquery') )
         and "username" = "mehdi"

特殊参数

你可以在查询字符串中设置特殊参数 limitorderBy 来通过它们进行查询。

/users/list?f_params[limit]=1

SELECT ... WHERE ... order by `id` desc limit 1 offset 0
/users/list?f_params[orderBy][field]=id&f_params[orderBy][type]=ASC

SELECT ... WHERE ... order by `id` asc
/users/list?f_params[orderBy][field]=id,count_posts&f_params[orderBy][type]=ASC

SELECT ... WHERE ...  order by `id` asc, `count_posts` asc

Where between

如果你打算基于日期进行查询,你必须填写查询字符串中的键 startend。因此,你可以将其设置为查询字符串。这些参数用于日期筛选。

/users/list?created_at[start]=2016/05/01&created_at[end]=2017/10/01

SELECT ... WHERE ... created_at BETWEEN '2016/05/01' AND '2017/10/01'

高级 Where

/users/list?count_posts[operator]=>&count_posts[value]=10&username[]=ali&username[]=mehdi&family=ahmadi&created_at[start]=2016/05/01&created_at[end]=2020/10/01
&f_params[orderBy][field]=id&f_params[orderBy][type]=ASC

select * from `users` where `count_posts` > 10 and `username` in ('ali', 'mehdi') and 
`family` = ahmadi and `created_at` between '2016/05/01' and '2020/10/01' order by 'id' asc limit 10 offset 0

因此,查询字符串的字段与模型中的 $whiteListFilter 中的数据库行表相同,或在你的模型中声明方法作为覆盖方法。覆盖方法可以被视为自定义查询筛选器。

自定义查询过滤器

Eloquent Filter 默认不支持所有条件。在这种情况下,你可以创建一个覆盖方法。如果你打算自己创建查询筛选器,你可以轻松做到这一点。

你应该运行命令来创建特质并在模型中使用它

php artisan eloquentFilter:filter users
namespace App\ModelFilters;

use Illuminate\Database\Eloquent\Builder;

/**
 * Trait UsersFilter.
 */
trait UsersFilter
{
    /**
     * This is a sample custom query
     * @param \Illuminate\Database\Eloquent\Builder $builder
     * @param                                       $value
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function sample_like(Builder $builder, $value)
    {
        return $builder->where('username', 'like', '%'.$value.'%');
    }
}

-注意 这些查询字符串的字段与特质中的方法相同。在你的模型中使用特质

/users/list?sample_like=a

select * from `users` where `username` like %a% order by `id` desc limit 10 offset 0
use App\ModelFilters\UsersFilter;

class User extends Model
{
    use UsersFilter,Filterable;

    protected $table = 'users';
    protected $guarded = [];
    private static $whiteListFilter =[
        'id',
        'username',
        'email',
        'created_at',
        'updated_at',
    ];
    
}

自定义检测条件

有时你想要创建自定义条件来创建 Eloquent Filter 默认不支持的新查询。好消息是,你现在可以从 eloquent 过滤器中创建自定义条件。

你可以创建条件来在检查之后生成新查询。(新功能 🔥 )。例如

我们必须有两个类。第一个检测条件,第二个类生成查询。

  • 步骤 1:创建一个类来检测某些条件
use eloquentFilter\QueryFilter\Detection\DetectorContract;

/**
 * Class WhereRelationLikeCondition.
 */
class WhereRelationLikeCondition implements DetectorContract
{
    /**
     * @param $field
     * @param $params
     * @param $is_override_method
     *
     * @return string|null
     */
    public static function detect($field, $params, $is_override_method = false): ?string
    {
        if (!empty($params['value']) && !empty($params['limit']) && !empty($params['email'])) {
            $method = WhereRelationLikeConditionQuery::class;
        }

        return $method ?? null;
    }
}
  • 步骤 2:之后,创建一个类来生成查询。在此示例中,我们创建 WhereRelationLikeConditionQuery
use eloquentFilter\QueryFilter\Queries\BaseClause;
use Illuminate\Database\Eloquent\Builder;

/**
 * Class WhereRelationLikeConditionQuery.
 */
class WhereRelationLikeConditionQuery extends BaseClause
{
    /**
     * @param $query
     *
     * @return Builder
     */
    public function apply($query): Builder
    {
        return $query
            ->whereHas('posts', function ($q) {
                $q->where('comment', 'like', "%" . $this->values['like_relation_value'] . "%");
            })
            ->where("$this->filter", '<>', $this->values['value'])
            ->where('email', 'like', "%" . $this->values['email'] . "%")
            ->limit($this->values['limit']);
    }
}
  • 步骤 3:你创建方法 EloquentFilterCustomDetection 来在模型中返回条件的检测数组。
use eloquentFilter\QueryFilter\ModelFilters\Filterable;

class User extends Model
{
    use Filterable;
   
    private static $whiteListFilter =[
        'username',
        'posts.count_post',
        'posts.category',
        'posts.orders.name',
    ];

    /**
     * @return \Illuminate\Database\Eloquent\Relations\belongsTo
     */
    public function posts()
    {
        return $this->belongsTo('Models\Post');
    }

    public function EloquentFilterCustomDetection(): array
    {
        return [
            WhereRelationLikeCondition::class
        ];
    }

}
  • 查询参数中的每一个都在默认的eloquent过滤器检测之后,首次用于检测WhereRelationLikeCondition

在上面的例子中,创建一个方法EloquentFilterCustomDetection并返回条件数组类。

/users/list?username[value]=mehdi&username[limit]=10&username[email]=mehdifathi&username[like_relation_value]=mehdi&count_posts=10

select * from "users"
 where exists (select * from "posts" where 
"users"."post_id" = "posts"."id" 
and "comment" like ?) and "username" <> ? and "email" like ? and "count_posts" = ? limit 10

只需运行代码User::filter();即可查看结果。

-注意您还可以通过使用方法SetCustomDetection即时设置自定义检测。例如

$users = User::SetCustomDetection([WhereRelationLikeCondition::class])->filter();

-注意您可以通过以下代码即时禁用EloquentFilterCustomDetection

 User::SetLoadDefaultDetection(false)->filter();

-注意您可以设置多个检测条件。例如

class User extends Model
{
    use Filterable;
    public function EloquentFilterCustomDetection(): array
    {
        return [
            WhereRelationLikeCondition::class,
            WhereRelationLikeVersion2Condition::class,
            WhereRelationLikeVersion3Condition::class,
        ];
    }
}
  • EloquentFilter::getInjectedDetections()获取您所有自定义注入的检测。

-注意每个自定义检测都会在默认eloquent过滤器检测之前运行。

配置

您可以为Eloquent Filter生成一个配置文件进行配置。

发布配置

php artisan vendor:publish --provider="eloquentFilter\ServiceProvider"

配置

  • 您可以在配置文件(eloquentFilter.php)中禁用/启用Eloquent Filter。

      'enabled' => env('EloquentFilter_ENABLED', true),
    
  • Eloquent Filter识别查询字符串中的每个参数。也许您有一个不想被Eloquent Filter识别的查询字符串。您可以使用ignoreRequest来达到这个目的。但我们有一个干净的解决方案。您可以在配置文件中设置request_filter_key参数。因此,每个查询字符串都将通过request_filter_key参数进行识别。

      'request_filter_key' => '', // filter
    

例如,如果您设置'request_filter_key' => 'filter',那么Eloquent Filter将识别filter查询字符串。

/users/list?filter[email]=mehdifathi.developer@gmail.com

  • 您可以在配置文件(eloquentFilter.php)中禁用/启用Eloquent Filter的所有自定义检测。

      'enabled_custom_detection' => env('EloquentFilter_Custom_Detection_ENABLED', true),
    
  • 您应该在ignore_request中设置索引数组以忽略所有过滤器。

      'ignore_request' => [] //[ 'show_query','new_trend' ],
    

魔术方法

魔法方法是您可以在Eloquent Filter中用作包装器的一组方法。例如,在过滤之前序列化数据或更改响应中的数据等。现在Eloquent Filter有serializeRequestFilterResponseFilter

请求过滤器

Eloquent Filter有一个专门用于在Eloquent Filter处理之前更改注入请求的魔法方法。该方法称为SerializeRequestFilter。您只需在您的模型中实现SerializeRequestFilter方法即可。例如

class User extends Model
{
    use Filterable;
    public function serializeRequestFilter($request)
    {
       $request['username'] = trim($request['username']);
       return $request;
    }
}

如上述代码所示,您可以在Eloquent Filter运行之前修改模型中的每个查询参数,在serializeRequestFilter方法中。这是设置user_id或转换日期或删除空格等时的实用方法。

响应过滤器

响应过滤器是一个专门用于在Eloquent Filter处理后更改响应的魔法方法。该方法称为ResponseFilter。您应该在您的模型中实现ResponseFilter方法。例如

class User extends Model
{
    use Filterable;
    public function ResponseFilter($response)
    {
        $data['data'] = $response;
        return $data;
    }
}
  • 如果您对Eloquent Filter有任何想法,我将很高兴听到。