bakgul/laravel-query-helper

本包的最新版本(v1.0.2)没有提供许可证信息。

一个包,用于添加一些包装器来过滤、分组、排序等,以加快查询速度。

v1.0.2 2023-02-22 17:19 UTC

This package is auto-updated.

Last update: 2024-09-22 20:44:54 UTC


README

此包旨在为Laravel的Eloquent查询构建器添加方便且相当灵活的功能。

安装

首先,安装包。

sail composer require bakgul/laravel-query-helper

然后,发布配置。

sail artisan vendor:publish --tag=query-helper

用法

过滤

首先,您需要为希望应用过滤器的每个模型添加 IsFilterable 特性和一个过滤器数组。此数组可以有两个键

  • Self: 应用到该模型上的过滤器列表。
  • With: 一个关联数组,包含可以用于过滤主模型的关联模型。此数组中的键必须是关系的方法名。

假设我们有以下模型和关系。

class User extends ...
{
    use IsFilterable, ...;

    public static $filters = [
        'self' => [
            \Bakgul\LaravelQueryHelper\Filters\Name::class,
            \Bakgul\LaravelQueryHelper\Filters\Email::class,
        ],
        'with' => [
            'roles' => Role::class,
        ]
    ];

    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
}
class Role extends Model
{
    use IsFilterable;

    private static array $filters = [
        'self' => [
            \Bakgul\LaravelQueryHelper\Filters\Name::class,
        ],
        'with' => [
            'users' => User::class,
            'abilities' => Ability::class,
        ],
    ];

    public function users()
    {
        return $this->belongsToMany(User::class);
    }

    public function abilities()
    {
        return $this->belongsToMany(Ability::class);
    }
}
class Ability extends Model
{
    use IsFilterable;

    private static array $filters = [
        'self' => [
            \Bakgul\LaravelQueryHelper\Filters\Name::class
        ],
        'with' => [
            'roles' => Role::class,
        ]
    ];

    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
}
$users = User::filter($request->filters)->get();

当您调用 filter 方法时,它将从 User 模型开始,递归地遍历 filters 数组,生成一个过滤数组。

为了避免递归中的无限循环,我们在添加主类到树中后停止每个分支。这意味着您可以使用以下逻辑进行过滤

  • 拥有某些角色并且属于某些用户的用户。

这个例子可能看起来不太有用,但它解释了功能。

由于构造过滤数组是一个昂贵的操作,我们将在首次创建时将其缓存。

$request->filters 应该具有以下示例的结构。这里的重要事项

  • self 过滤器将直接传递。
  • with 过滤器将被收集在 with 键下。
[
    // *** will be replaced by % by the Text filter.
    // ***x means the string that ends with 'x'
    // x*** means the string that start with 'x',
    // ***x*** means the string that contains 'x',
    // x means the string that is 'x'
    'name' => ['***x***', '***y'],
    'with' => [
        'roles' => [
            'name' => ['editor***'],
            'with' => [
                'abilities' => [
                    'name' => ['delete']
                ]
            ]
        ]
    ]
]

上面的示例将根据以下列表过滤用户

  • 其名称包含 'x' 或以 'y' 结尾
  • 其一个角色的名称以 'editor' 开头
  • 该角色可以删除某些内容。

多态关系过滤

与其他关系不同,多态关系应列在 $filters 数组的 self 键下。

class Post extends Model
{
    use IsFilterable;

    private static array $filters = [
        'self' => [
            \Bakgul\LaravelQueryHelper\Filters\MorphMany::class
        ],
        'with' => [
            'user' => User::class,
        ],
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function comments()
    {
        return $this->morphToMany(Comment::class, 'commentable');
    }
}

请求中的过滤器应如下所示

[
    'morph_many' => [
        // method name: 'to' if the relation method is 'morphToMany'
        //              'by' if the relation method is 'morphedByMany'
        'to',
        // relationsip name
        'comments',
        // prefix:
        //     if 'to' then this will be 'comment_id'
        //     if 'by' then this will be 'commentable_id' and 'commentable_type' 
        'comment',
        // the N number of ids that will be cheched in the column up above
        2, 3, 5
    ]
]

分组

分组在PHP级别上操作。如果您想在数据库级别分组数据,则不能使用此部分。否则,这是如何使用它的

  • IsGrouppable 特性添加到您想要分组的模型中。
  • 如果您希望始终按一些列分组模型,请将 protected static $groupKeys = ['name', 'year'] 属性添加到模型中。
  • 然后这样使用它
/**
 * users table has these columns:
 *     first_name, 
 *     last_name, 
 *     email, 
 *     ... some irrelevant columns
 *     created_at
 */

$users = User::group(['first_name']);

group 方法可以在 IsGrouppable 特性中找到,作为 scopeGroup,它接受以下参数

  • keys: 分组键列表
  • take: 每组中的项目数量。默认为零,表示 '所有'。
  • isLast: 当您想要获取最新记录时,将其设置为 true。默认为 false。
  • select: 将要选择的列数组。默认为 ['*'],表示 '所有'。
  • column: 当需要时列的名称。我使用它进行时间修饰符,如年月。其默认值是 'created_at'。

但是,如果你想要使用不存在的列来分组用户,你可以通过包中提供的修饰符来实现。这些修饰符可以在src/Modifiers中找到。

修饰符将动态更改SQL查询,以添加新字段。例如

$users = User::group(
    keys: ['year', 'email_provider'],
    take: 5,
    isLast: true,
    select: ['first_name', 'last_name', 'email']
    column: 'updated_at'
);

上面的方法将向每个用户添加yearemail_provider字段。year将从updated_at中提取,而email_provideremail中提取。每个用户将包含所选的3个列和这两个。

修改

这用于分组功能,已经解释过了,但为了提醒,你也可以在分组之外使用它。

  • IsModifyable特质添加到模型中。
  • modify方法作为查询构建器的一部分调用。
$users = User::modify(
    keys: ['year', 'month'],
    select: ['name', 'email'],
    column: 'updated_at'
)->get();

排序

这是一个相当简单的函数,允许你在一个方法中传递所有排序列。

User::sort(['name'], ['email', 'desc']);

扩展功能

过滤

为了扩展可用的过滤器,你需要创建自己的过滤器类并在模型中使用它们。假设你有一个包含city的表,需要对其应用过滤器。

首先创建一个类。类的名称必须是你将在请求中传递的键的Pascal大小写版本。

namespace App\Filters;

class City extends Text
{
    public $column = 'city';
}

或者,你可以创建自己的过滤逻辑而不是使用Text过滤器。

namespace App\Filters;

class City extends Filter
{
    protected function filter(Builder $query, mixed $filter): Builder
    {
        // if you want to accept multiple values, call filters method
        return $this->filters($query, $filter, $this->callback());

        // otherwise...
        return $query->where('city', $filter);
    }

    protected function callback(): callable
    {
        return fn ($query, $filter) => $query->where('city', $filter);
    }
}

创建你的新过滤器类后,将其添加到模型中,以便可以使用它。

class Address extends Model
{
    private static array $filters = [
        'self' => [
            \Bakgul\LaravelQueryHelper\Filters\Name::class,
            \App\Filters\City::class,
        ],
        'with' => [
            'user' => User::class,
            'country' => Country::class,
        ],
    ];
}

现在,你可以根据城市过滤地址,或者过滤在该城市/城市中的用户。

Adress::filter(['city' => ['ankara', 'london']])->get();

User::filter(['with' => ['city' => ['ankara', 'london']]])->get();

分组

如果你想要使用当前列而不进行任何操作,你不需要采取任何行动。只需将列名传递到分组键数组中。

但是,假设你有一个domain列,其中存储网站的地址,并且你想根据顶级域名(.com、.net等)对其进行分组。

首先你需要一个修饰符来提取该部分并将其存储在查询中的新字段中。

namespace App\Modifiers;

class TopLevelDomain extends Modify
{
    public function modifyQuery(Builder $query, array $keys, string $column): Builder
    {
        $raw = $this->rawQuery();

        return $query->when(
            in_array('top_level_domain', $keys),
            fn ($q) => $q->addSelect($raw)
        );
    }

    private function rawQuery(): Expression
    {
        return DB::raw("REPLACE(domain, SUBSTRING_INDEX(domain, '.', 1) , '') as top_level_domain");
    }
}

然后你需要将其添加到config/query-helper.php中的modifiers数组中。

    'modifiers' => [
        // default ones,
        \App\Modifiers\TopLevelDomain::class,
    ]

现在你可以这样使用它

$customers = Customer::group(['top_level_domain']);

许可

这是一个开源软件,许可协议为MIT许可