laragear/refine

使用请求查询键和匹配方法过滤数据库查询。

v1.2.1 2024-04-10 06:39 UTC

This package is auto-updated.

Last update: 2024-09-10 07:26:30 UTC


README

Latest Version on Packagist Latest stable test run Codecov coverage Maintainability Sonarcloud Status Laravel Octane Compatibility

使用请求查询键和匹配方法过滤数据库查询。

// https://myblog.com/posts/?author_id=10

class PostController
{
    public function all(Request $request)
    {
        return Post::refineBy(PostRefiner::class)->paginate()
    }
}

class PostRefiner
{
    public function authorId($query, $value)
    {
        $query->where('author_id', $value);
    }
}

成为赞助商

您的支持使我能够保持此包免费、更新和可维护。或者,您也可以 传播这个消息!

需求

  • Laravel 10 或更高版本。

安装

使用 Composer 将此包添加到您的项目中

composer require laragear/refine

使用方法

此包通过将逻辑从控制器中移除,解决了使用 URL 参数细化数据库查询的问题。

例如,假设您想显示所有由给定作者 ID 创建的文章。通常,您会在控制器中检查这一点并修改查询。

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

public function all(Request $request)
{
    $request->validate([
        'author_id' => 'sometimes|integer'
    ]);
    
    $query = Post::query()->limit(10);
    
    if ($request->has('author_id')) {
        $query->where('author_id', $request->get('author_id'));
    }

    return $query->get();
}

虽然这对几个 URL 参数来说看起来无害,但随着需要更多细化,这会逐渐累积:在特定时间发布、带有特定标签集、按特定列排序等。最终,这会使控制器动作变得杂乱。

相反,Laragear Refine 将该逻辑移动到自己的 "Refiner" 对象中,该对象通过仅向查询构建器的 refineBy() 方法发出 refiner 类名称来处理。

use App\Models\Post;
use Illuminate\Http\Request;
use App\Http\Refiners\PostRefiner;

public function all(Request $request)
{
    return Post::query()->refineBy(PostRefiner::class);
}

魔法很简单:只要在传入请求中存在相应的 URL 参数键,就会执行每个 refiner 方法。键会自动规范化为 camelCase 以匹配方法,因此 author_id 键将执行 authorId() 并使用其值。

GET https://myapp.com/posts?author_id=20
namespace App\Http\Refiners;

class PostRefiner
{
    public function authorId($query, $value)
    {
        $query->where('author_id', $value);
    }
}

创建 Refiner

使用您想创建的 Refiner 名称调用 make:refiner

php artisan make:refiner PostRefiner

您将收到位于 app\Http\Refiners 目录中的 refiner。

namespace App\Http\Refiners;

use Laragear\Refine\Refiner;

class PostRefiner extends Refiner
{
    /**
     * Create a new post query filter instance.
     */
    public function __construct()
    {
        //
    }
}

如您所见,除了构造函数外,该类为空。下一步是定义匹配请求键的方法。

定义方法

您可以通过简单地将这些创建为公共的,使用它们对应的 camelCase 键来定义您希望在 URL 参数键存在时执行的方法。

// For `author_id=value`
public function authorId($query, mixed $value, Request $request)
{
    // ...
}

在 Refiner 类中设置的任何方法都会接收到查询构建器实例、请求的值以及 Illuminate\Http\Request 实例本身。在每个方法内部,您可以自由地根据需要修改查询构建器,甚至调用授权门或检查用户权限。

namespace App\Http\Refiners;

use App\Models\Post;
use Illuminate\Http\Request;
use Laragear\Refine\Refiner;

class PostRefiner extends Refiner
{
    public function authorId($query, mixed $value, Request $request)
    {
        // Only apply the filter if the user has permission to see all posts.
        if ($request->user()->can('view any', Post::class)) {
            $query->where('author_id', $value);
        }
    }
}

仅某些键

在很少的情况下,您可能有一个不希望作为细化过程一部分执行的方法,特别是如果您的 Refiner 扩展了另一个 Refiner。在这种情况下,您可以使用 getKeys() 方法指示哪些 URL 参数键应该用于匹配它们对应的方法。

use Illuminate\Http\Request;

public function getKeys(Request $request): array
{
    return [
        'author_id',
        'published_before',
        'published_after',
    ];
}

或者,如果您正在使用 FormRequest,您始终可以返回验证数据的键。

use Illuminate\Http\Request;
use Illuminate\Foundation\Http\FormRequest;

public function getKeys(Request $request): array
{
    if ($request instanceof FormRequest) {
        return array_keys($request->validated()); 
    }
    
    return array_keys($request->keys());
}

必填键

有时您可能希望在 URL 参数中没有设置键的情况下运行一个方法。为此,使用 getObligatoryKeys() 方法返回应始终运行的键(和方法)。

例如,如果我们想运行 orderBy() 方法,即使没有设置 order_by URL 参数,我们也只需要返回该键。

public function getObligatoryKeys(): array
{
    return ['order_by'];
}

然后,该方法应该能够在 URL 参数未设置时接收一个 null 值。

public function orderBy($query, ?string $value, Request $request)
{
    // If the value was not set, use the publishing timestamp as the column to sort.
    $value ??= 'published_at'
    
    $query->orderBy($value, $request->query('order') ?? 'asc');
}

依赖注入

Refiner类始终使用应用程序容器进行解析。您可以在类构造函数中类型提示任何依赖项,并在以后的方法中使用它。

namespace App\Http\Refiners;

use Illuminate\Contracts\Auth\Access\Gate;
use Laragear\Refine\Refiner;
use App\Models\Post;

class PostRefiner extends Refiner
{
    public function __construct(protected Gate $gate)
    {
        //
    }
    
    public function authorId($query, $value)
    {
        if ($this->gate->check('view any', Post::class)) {
            // ...
        }
    }
}

验证

您还可以通过实现ValidateRefiner接口将验证逻辑包含到您的Refiner中。从那里,您应该设置您的验证规则,以及可选的消息和自定义属性(如果需要)。

如果您期望查询中始终需要键,这将非常有用,因为validationRules()是一个执行此操作的绝佳位置。

namespace App\Http\Refiners;

use Laragear\Refine\Contracts\ValidatesRefiner;
use Laragear\Refine\Refiner;

class PostRefiner extends Refiner implements ValidatesRefiner
{
    // ...
    
    public function validationRules(): array
    {
        return ['author_id' => 'required|integer'];
    }
}

注意

验证规则将在请求查询上直接运行,而不是在请求输入上。

应用Refiner

在您的Builder实例中,只需调用带有Refiner类名称(或其在应用程序容器上注册的别名)的refineBy()即可将查询应用于。

use App\Models\Post;
use App\Http\Refiners\PostRefiner;

Post::refineBy(PostRefiner::class)->paginate();

refineBy()是注册到Eloquent Builder和基础Query Builder的宏,您可以在自定义改进之后使用它。

use App\Http\Requests\PostRequest;
use App\Http\Refiners\PostRefiner;
use Illuminate\Support\Facades\DB;

public function rawPosts(PostRequest $request)
{
    return DB::table('posts')
        ->whereNull('deleted_at')
        ->refineBy(PostRefiner::class)
        ->limit(10)
        ->get();
}

自定义键

您可以通过指定作为第二个参数的键来在运行时覆盖请求中查找的键。这些将替换您在类中设置的自定义键

public function all(Request $request)
{
    $validated = $request->validate([
        // ...
    ])

    return Post::query()->refineBy(PostFilter::class, ['author_id', 'order_by'])->paginate();
}

模型Refiner

您可以使用包含的ModelRefiner快速创建针对模型数据库查询的Refiner。Model Refiner自动简化以下URL参数:

  • query通过搜索既定的主键预定列中包含的文本进行搜索。
  • only[]仅检索某些列。
  • has[]检索至少有一个相关模型的项。
  • has_not[]检索没有相关模型的项。
  • with[]检索包括关系或嵌套关系的项。
  • with_count[]包括给定关系的计数。
  • with_sum[]包括给定关系列的计数。
  • trashed将已删除的项包含在查询中。
  • order_by|order_by_desc确定用于排序的列。
  • limit|per_page限制检索的项数。

创建模型Refiner

只需使用--model选项调用make:refiner

php artisan make:refiner ArticleRefiner --model 

您将收到一个扩展基本ModelRefiner的Refiner。在这里,您应该设置用于验证URL参数值的关联、列、总和以及Refiner应使用的排序。这样,您可以控制哪些列或关系被允许设置在查询中。

namespace App\Http\Refiners;

use Laragear\Refine\ModelRefiner;

class ArticleRefiner extends ModelRefiner
{
    /**
     * Return the columns that should only be included in the query.
     *
     * @return string[]
     */
    protected function getOnlyColumns(): array
    {
        return [];
    }

    /**
     * Return the relations that should exist for the query.
     *
     * @return string[]
     */
    protected function getHasRelations(): array
    {
        return [];
    }

    /**
     * Return the relations that should be missing for the query.
     *
     * @return string[]
     */
    protected function getHasNotRelations(): array
    {
        return [];
    }

    /**
     * Return the relations that can be queried.
     *
     * @return string[]
     */
    protected function getWithRelations(): array
    {
        return [];
    }

    /**
     * Return the relations that can be counted.
     *
     * @return string[]
     */
    protected function getCountRelations(): array
    {
        return [];
    }

    /**
     * Return the relations and the columns that should be sum.
     *
     * @return string[]
     */
    protected function getWithSumRelations(): array
    {
        // Separate the relation name using hyphen (`-`). For example, `published_posts-votes`.
        return [];
    }

    /**
     * Return the columns that can be used to sort the query.
     *
     * @return string[]
     */
    protected function getOrderByColumns(): array
    {
        return [];
    }
}

与普通Refiner一样,您还可以覆盖验证键和/或请求中检查的键,甚至每个查询键应该如何精炼

namespace App\Http\Refiners;

use Illuminate\Support\Arr;
use Laragear\Refine\ModelRefiner;

class ArticleRefiner extends ModelRefiner
{
    public function validationRules(): array
    {
        return Arr::only(parent::validationRules(), ['with', 'with.*', 'order_by']);
    }

    public function getKeys(Request $request): array
    {
        return Arr::only(parent::getKeys(), ['with', 'order_by']);
    }
    
    public function query(Builder $query, string $search): void
    {
        $query->where('name', 'like', $this->normaliseQuery($search));
    }
    
    // ...
}

提示

即使您使用snake_case验证关系,在构建关系查询时,这些关系将自动转换为camelCase,即使它们由点符号分隔。无需更改大小写。

全文搜索

默认情况下,当接收到作为“查询”的字符串时,Model Refiner将使用ILIKE运算符在单个或多个列中搜索。此方法适用于所有SQL引擎。

或者,您可以通过将$fullTextSearch设置为true在您的Model Refiner中,使用PostgreSQL或MySQL全文搜索功能

namespace App\Http\Refiners;

use Illuminate\Support\Arr;
use Laragear\Refine\ModelRefiner;

class ArticleRefiner extends ModelRefiner
{
    protected bool $fullTextSearch = true;

    // ...
}

求和关系

ModelRefiner支持使用关系名称和由连字符分隔的列来求和关系列。您可能需要在getSumRelations()方法中返回关系数组和可能求和的列。

protected function getSumRelations(): array
{
    return [
        'user_comments-claps',
        'user_comments-down_votes',
        'user_comments-up_votes',
    ];
}

上述代码将调用查询模型的 userComments() 关联。

Laravel Octane 兼容性

  • 没有单例使用了过期的应用实例。
  • 没有单例使用了过期的配置实例。
  • 没有单例使用了过期的请求实例。
  • 正在写入的静态属性是 Refiner 方法的缓存,每次运行一个唯一的 Refiner 都会增长。
  • 正在写入的静态属性是 Abstract Refiner 方法的缓存,只写入一次。

缓存的 Refiner 方法不应该无限制增长,除非你调用了数十个 Refiner 类多次。在任何情况下,你都可以使用 RefineQuery::flushCachedRefinerMethods() 来清空缓存的 Refiner 方法。

使用此包与 Laravel Octane 一起使用应该没有问题。

安全

如果你发现任何安全相关的问题,请发送电子邮件到 darkghosthunter@gmail.com,而不是使用问题跟踪器。

许可证

此特定软件包版本在发布时根据 MIT 许可协议 许可。

LaravelTaylor Otwell 的商标。版权所有 © 2011-2024 Laravel LLC。