jerome/filterable

通过无缝API请求集成和高级缓存策略,简化动态Eloquent查询过滤。

1.1.6 2024-09-25 10:23 UTC

README

Filterable

关于Filterable

Latest Version on Packagist Tests Check & fix styling Total Downloads

The Filter class provides a flexible and powerful way to apply dynamic filters to Laravel's Eloquent queries. It supports caching, user-specific filtering, and custom filter methods, making it suitable for a wide range of applications, from simple blogs to complex data-driven platforms.

特性

  • 动态过滤:轻松根据请求参数应用过滤器。
  • 缓存:通过缓存查询结果来提高性能。
  • 特定用户过滤:轻松实现依赖于认证用户的过滤器。
  • 自定义过滤器方法:扩展类以添加自己的过滤器方法。

安装

要将 Filterable 包集成到您的 Laravel 项目中,您可以通过 Composer 安装它。在您的项目目录中运行以下命令

composer require jerome/filterable

安装后,该包应自动将其服务提供者注册到 Laravel 的服务容器中,使其功能在您的应用程序中随时可用。这利用了 Laravel 的包自动发现机制,该机制在 Laravel 5.5 及更高版本中得到支持。

如果您使用的是不支持包自动发现的 Laravel 版本,您需要手动在您的 config/app.php 文件中的 providers 数组中注册 FilterableServiceProvider

'providers' => [
    // Other service providers...

    Filterable\Providers\FilterableServiceProvider::class,
],

对于现代 Laravel 安装,通常不需要此步骤,因为自动发现应该为您处理它。

安装和注册后,您就可以使用 Filterable 功能来增强您 Laravel 应用程序的数据查询能力了。

使用方法

创建过滤器类

您可以使用以下 Artisan 命令创建新的过滤器类

php artisan make:filter PostFilter

此命令将在 app/Filters 目录中生成一个新的过滤器类。然后,您可以自定义此类以添加自己的过滤器方法。

Filter 类是一个基类,它提供了应用过滤器到 Eloquent 查询的核心功能。您可以通过扩展此类来创建自己的过滤器类,以适应特定的模型。要使用 Filter 类,您首先需要扩展它以创建适合您特定模型的自己的过滤器类。以下是一个针对 Post 模型的基本示例

namespace App\Filters;

use Filterable\Filter;
use Illuminate\Database\Eloquent\Builder;

class PostFilter extends Filter
{
    protected array $filters = ['status', 'category'];

    protected function status(string $value): Builder
    {
        return $this->builder->where('status', $value);
    }

    protected function category(int $value): Builder
    {
        return $this->builder->where('category_id', $value);
    }
}

要添加新的过滤器,只需在您的自定义过滤器类中定义一个新的方法。此方法应遵循 PHP 的 camelCase 命名约定,并根据过滤器的目的以描述性方式命名。一旦实现了方法,确保在 $filters 数组中注册其名称以激活它。以下是这样做的方法

namespace App\Filters;

use Filterable\Filter;

class PostFilter extends Filter
{
    protected array $filters = ['last_published_at'];

    protected function lastPublishedAt(int $value): Builder
    {
        return $this->builder->where('last_published_at', $value);
    }
}

在此示例中,在 PostFilter 类中创建了一个新的过滤器 lastPublishedAt。过滤器名称 last_published_at$filters 数组中注册。

实现 Filterable 特性和 Filterable 接口

要在您的 Eloquent 模型中使用 Filter 类,您需要实现 Filterable 接口并使用 Filterable 特性。以下是一个针对 Post 模型的示例

namespace App\Models;

use Filterable\Interfaces\Filterable as FilterableInterface;
use Filterable\Traits\Filterable as FilterableTrait;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements FilterableInterface
{
    use FilterableTrait;
}

注意:包中包含了 Filterable 接口和 Filterable 特性,应在模型中使用它们以启用过滤。虽然 Filterable 接口是可选的,但建议使用以确保一致性。

应用过滤器

您可以将过滤器应用于 Eloquent 查询,如下所示

use App\Models\Post;

$filter = new PostFilter(request(), cache());
$posts = Post::filter($filter)->get();

在控制器中应用过滤器

您可以在控制器方法中如此应用自定义过滤器

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

class PostController extends Controller
{
    public function index(Request $request, PostFilter $filter)
    {
        $query = Post::filter($filter);

        $posts = $request->has('paginate')
            ? $query->paginate($request->query('per_page', 20))
            : $query->get();

        return response()->json($posts);
    }
}

应用仅限于认证用户范围的过滤器

您还可以应用特定于认证用户的过滤器。使用 forUser 方法设置应用过滤器的用户

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

class PostController extends Controller
{
    public function index(Request $request, PostFilter $filter)
    {
        $filter->forUser($request->user());

        $query = Post::filter($filter);

        $posts = $request->has('paginate')
            ? $query->paginate($request->query('per_page', 20))
            : $query->get();

        return response()->json($posts);
    }
}

在主要过滤器之前应用预过滤器

您还可以在主要过滤器之前应用预过滤器。使用 registerPreFilters 方法设置应应用的预过滤器

use App\Models\Post;
use App\Filters\PostFilter;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Builder;

class PostController extends Controller
{
    public function index(Request $request, PostFilter $filter)
    {
        $filter->registerPreFilters(function (Builder $query) {
            return $query->where('published', true);
        });

        $query = Post::filter($filter);

        $posts = $request->has('paginate')
            ? $query->paginate($request->query('per_page', 20))
            : $query->get();

        return response()->json($posts);
    }
}

在前端使用过滤器

您可以通过发送带有查询参数的请求来在前端使用过滤器。例如,要按状态过滤帖子,可以发送如下请求

const response = await fetch('/posts?status=active');

const data = await response.json();

此请求将返回所有状态为 active 的帖子。

您还可以串联所有要应用的过滤器。例如,要按状态和类别过滤帖子,可以发送如下请求

const response = await fetch('/posts?status=active&category_id=2');

const data = await response.json();

此请求将返回所有状态为 active 且与 ID 为 2 的类别相关的帖子。

注意:任何不匹配过滤器名称的查询参数将被忽略。

缓存

在您的过滤器类中,您可以通过使用 enableCaching 静态方法来控制缓存。将 $useCache 静态属性设置为 true 以启用缓存,或设置为 false 以禁用缓存。您还可以通过修改 $cacheExpiration 属性来自定义缓存的持续时间。

注意:默认情况下禁用缓存。

/**
 * Indicates if caching should be used.
 *
 * @var bool
 */
protected static bool $useCache = false;

启用和禁用缓存

  • 启用缓存:要开始缓存,请确保已启用缓存。这通常在设置期间完成,或者根据应用程序上下文动态完成,例如仅在生产环境中启用缓存以提高性能和减少数据库负载。
// AppServiceProvider.php

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot(): void
{
    // Enable caching globally through methods...
    Filter::enableCaching();
}
  • 禁用缓存:如果您需要临时关闭缓存,例如在开发期间确保每次请求都加载新鲜数据以及帮助调试,可以禁用缓存。
// AppServiceProvider.php

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot(): void
{
    // Disable caching globally through methods...
    Filter::disableCaching();
}

此配置允许您从 AppServiceProvider 中集中管理缓存设置。根据环境或特定场景调整缓存行为有助于有效地优化性能和资源利用率。

namespace App\Filters;

use Filterable\Filter;

class PostFilter extends Filter
{
    protected array $filters = ['last_published_at'];

    protected function lastPublishedAt(int $value): Builder
    {
        return $this->builder->where('last_published_at', $value);
    }
}

$filter = new PostFilter(request(), cache());

// Control caching
$filter->setCacheExpiration(1440); // Cache duration in minutes

当然!以下是详细使用指南部分,解释了如何使用 Filter 类中的日志功能。本指南旨在帮助开发人员有效地理解和实现过滤操作中的日志记录。

日志记录

Filter 类集成了强大的日志功能,有助于调试和监控过滤器对查询构建器的应用。此功能对于跟踪问题、了解过滤器影响和确保系统按预期运行至关重要。

配置日志记录器

  1. 设置日志记录器:在您可以记录任何活动之前,必须向 Filter 类提供日志记录器实例。此日志记录器应符合 Psr\Log\LoggerInterface。通常,这将在构造函数中设置,或者如果日志记录器可能会在应用程序生命周期中更改,则通过设置方法进行设置。
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create a logger instance
$logger = new Logger('name');
$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));

// Set the logger to the filter class
$filter->setLogger($logger);
  1. 依赖注入:如果您使用 Laravel,可以利用其服务容器自动将日志记录器注入到您的 Filter 类中。
// In a service provider or similar setup
$this->app->when(Filter::class)
    ->needs(LoggerInterface::class)
    ->give(function () {
        return new Logger('name', [new StreamHandler('path/to/your.log', Logger::WARNING)]);
    });

使用自定义通道设置日志记录器

您可以为您的Filter类设置一个特定的日志通道,方法是通过直接在日志配置中配置它,或者通过在Laravel的日志配置中定义它然后注入它。以下是两种方法的实现方式

  1. 直接配置:

    • 直接创建一个具有特定通道和处理器(handler)的日志器。这种方法简单直接,让您可以完全控制日志器的配置
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create a logger instance for the Filter class with a custom channel
$logger = new Logger('filter');
$logger->pushHandler(new StreamHandler(storage_path('logs/filter.log'), Logger::DEBUG));

// Set the logger to the filter class
$filter->setLogger($logger);
  1. 使用Laravel的日志配置:

    • Laravel允许您在它的日志配置文件(config/logging.php)中定义自定义通道。您可以在其中为Filter类定义一个特定的通道,然后使用Log外观(facade)检索它
// In config/logging.php

'channels' => [
    'filter' => [
        'driver' => 'single',
        'path' => storage_path('logs/filter.log'),
        'level' => 'debug',
    ],
],
  • 现在,您可以使用Log外观在您的服务提供者或类中将此日志器设置好
use Illuminate\Support\Facades\Log;

// In your AppServiceProvider or wherever you set up the Filter class
$filter->setLogger(Log::channel('filter'));

启用和禁用日志

  • 启用日志:要开始记录日志,请确保已启用日志记录。这通常在设置期间完成,或者根据应用程序上下文动态完成(例如,仅在开发环境中记录日志)。
// AppServiceProvider.php

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot(): void
{
    // Enable logging globally through methods...
    Filter::enableLogging();
}
  • 禁用日志:如果您需要临时关闭日志记录(例如,在生成环境中以提高性能),则可以禁用它
// AppServiceProvider.php

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot(): void
{
    // Disable logging globally through methods...
    Filter::disableLogging();
}

日志操作

  • 自动日志:一旦设置并启用日志器,Filter类将根据调用的方法和应用的过滤器自动记录相关操作。这包括在添加过滤器、执行查询以及缓存命中或未命中时记录日志。

  • 自定义日志:您可以在定义的过滤器中添加自定义日志,或者通过扩展Filter类来实现。这有助于记录默认日志未覆盖的特定条件或附加数据。

public function customFilter($value) {
    if (self::shouldLog()) {
        $this->getLogger()->info("Applying custom filter with value: {$value}");
    }
    // Filter logic here
}

检查日志是否已启用

  • 条件日志:在记录任何自定义消息之前,检查日志是否已启用,以避免不必要的处理或日志错误。
if (Filter::shouldLog()) {
    $this->getLogger()->info('Performing an important action');
}

测试

可以使用PHPUnit测试您的过滤器。以下是一个确保status过滤器正确应用的示例测试

namespace Tests\Unit;

use Tests\TestCase;
use App\Models\Post;
use App\Filters\PostFilter;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;

class PostFilterTest extends TestCase
{
    use RefreshDatabase;

    public function testFiltersPostsByStatus(): void
    {
        $activePost = Post::factory()->create(['status' => 'active']);
        $inactivePost = Post::factory()->create(['status' => 'inactive']);

        $filter = new PostFilter(new Request(['status' => 'active']));
        $filteredPosts = Post::filter($filter)->get();

        $this->assertTrue($filteredPosts->contains($activePost));
        $this->assertFalse($filteredPosts->contains($inactivePost));
    }
}

请确保您已设置了必要的测试环境,包括所需的迁移或工厂定义。

许可

本项目采用MIT许可 - 有关详细信息,请参阅LICENSE.md文件。

贡献

贡献使开源社区成为一个如此了不起的学习、灵感和创造的地方。您所做的任何贡献都将被高度重视

如果您有改进此项目的建议,请fork存储库并创建一个pull request。您也可以简单地创建一个带有“enhancement”标签的问题。

别忘了给项目加星标!再次感谢!

  1. Fork项目
  2. 创建您的功能分支(git checkout -b feature/amazing-feature
  3. 提交您的更改(git commit -m 'Add some amazing-feature'
  4. 将更改推送到分支(git push origin feature/amazing-feature
  5. 打开一个pull request

作者

  • [Jerome Thayananthajothy] - 初始工作 - Thavarshan

请参阅参与此项目的贡献者名单。

致谢