定制 dibakar/laravel-dynamic-filters 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

dibakar/laravel-dynamic-filters

最新稳定版本:1.0.0

Composer 安装命令:

composer require dibakar/laravel-dynamic-filters

包简介

A flexible and dynamic filtering system for Laravel Eloquent models that allows for easy implementation of complex filtering and searching capabilities in your applications.

README 文档

README

Latest Version on Packagist Total Downloads License PHP Version Laravel Version

A robust and flexible filtering system for Laravel Eloquent models that makes building complex, dynamic queries a breeze. This package provides an elegant, fluent API for filtering, searching, and sorting your Eloquent models with minimal configuration.

✨ Features

  • Expressive Filtering: Chainable methods and intuitive syntax for complex queries
  • Advanced Search: Full-text search with fuzzy matching and term normalization
  • Relationship Support: Filter across model relationships with nested conditions
  • Type Safety: Strict type checking and automatic value casting
  • Performance Optimized: Efficient query building with minimal overhead
  • Security First: Whitelisting and input validation out of the box
  • Extensible: Easy to create and register custom filters
  • Modern PHP: Built with PHP 8.1+ features and type hints

🚀 Installation

Requirements

  • PHP 8.1 or higher
  • Laravel 10.x or later
  • Composer

Install via Composer

composer require dibakar/laravel-dynamic-filters

Configuration (Optional)

Publish the configuration file to customize the package behavior:

php artisan vendor:publish --provider="Dibakar\LaravelDynamicFilters\DynamicFiltersServiceProvider" --tag="config"

This will create a dynamic-filters.php file in your config directory with sensible defaults.

Service Provider & Facade

The package uses Laravel's package auto-discovery, but you can manually register it in config/app.php if needed:

'providers' => [
    // Other service providers...
    Dibakar\LaravelDynamicFilters\DynamicFiltersServiceProvider::class,
],

'aliases' => [
    // Other aliases...
    'DynamicFilter' => Dibakar\LaravelDynamicFilters\Facades\DynamicFilter::class,
],

📦 Version Compatibility

Laravel PHP Package
12.x 8.2+ ^1.0
11.x 8.2+ ^1.0
10.x 8.1+ ^1.0

🚀 Quick Start

1. Prepare Your Model

Add the HasDynamicFilter trait to your Eloquent model and define the filterable, searchable, and sortable fields:

use Dibakar\LaravelDynamicFilters\Traits\HasDynamicFilter;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasDynamicFilter;
    
    /**
     * Fields that can be searched.
     */
    protected $searchable = [
        'title', 
        'content',
        'author.name',    // Search in relationships
        'tags.name'       // Search in many-to-many relationships
    ];
    
    /**
     * Fields that can be filtered with operators.
     */
    protected $filterable = [
        'id',
        'status',
        'category_id',
        'published_at',
        'views',
        'is_featured',
    ];
    
    /**
     * Fields that can be used for sorting.
     */
    protected $sortable = [
        'created_at' => 'desc',  // Default sort
        'title' => 'asc',
        'views' => 'desc',
    ];
    
    /**
     * Default filter presets.
     */
    protected $filterPresets = [
        'published' => [
            'status' => 'published',
            'sort' => '-published_at',
        ],
        'popular' => [
            'views' => ['gt' => 1000],
            'is_featured' => true,
            'sort' => '-views',
        ],
    ];
        'status',                   // Simple filter: ?status=published
        'category_id',              // Exact match: ?category_id=5
        'created_at' => [           // Date filtering
            'operators' => ['=', '>', '<', '>=', '<=', '!='],
            'cast' => 'date',
        ],
        'views' => [                // Numeric filtering
            'operators' => ['=', '>', '<', '>=', '<=', '!='],
            'cast' => 'int',
        ],
        'author_id' => 'author.id', // Relationship filtering
        'tag_id' => 'tags.id'      // Many-to-many relationship
    ];
    
    /**
     * Get the default filters that should be applied to all queries.
     *
     * @return array
     */
    public function getDefaultFilters()
    {
        return [
            'status' => 'published',
            'sort' => '-created_at',
        ];
    }
}

2. Basic Filtering

Filter your models using query parameters in your controller:

// GET /posts?status=published&created_at[gt]=2023-01-01&sort=-views,title
public function index(Request $request)
{
    $posts = Post::filter($request->query())
        ->with(['author', 'category', 'tags']) // Eager load relationships
        ->paginate($request->per_page ?? 15);

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

3. Search Functionality

Search across searchable fields with a simple API:

// GET /posts?q=laravel+framework
public function search(Request $request)
{
    $posts = Post::search($request->q)
        ->filter($request->except('q')) // Apply additional filters
        ->paginate($request->per_page ?? 15);

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

4. Sorting Results

Sort your results using the sort parameter in your requests. The - prefix indicates descending order.

Basic Sorting

// In your controller
$posts = Post::sort($request->input('sort'))->get();

// Or chain it with filters
$posts = Post::filter($filters)
    ->sort($request->input('sort', 'created_at,desc'))
    ->paginate(15);

// Example requests:
// GET /posts?sort=title             // Sort by title (ascending)
// GET /posts?sort=title,asc         // Same as above (explicit ascending)
// GET /posts?sort=title,desc        // Sort by title (descending)
// GET /posts?sort=-title            // Alternative: Sort by title (descending)
// GET /posts?sort=views,desc&sort=title,asc  // Multiple sort fields
// GET /posts?sort=created_at,desc   // Sort by created_at (newest first)
// GET /posts?sort=author.name,asc   // Sort by relationship field

Default Sorting

Define default sorting in your model:

// In your model
protected $sortable = [
    'created_at' => 'desc',  // Default sort
    'title' => 'asc',
    'views' => 'desc',
    'author.name' => 'asc',  // Sort by relationship
];

Sorting in API Requests

// In your controller
public function index(Request $request)
{
    $validated = $request->validate([
        'sort' => 'sometimes|string',
        // other validation rules
    ]);

    return Post::filter($request->except('sort'))
        ->sort($validated['sort'] ?? null)
        ->paginate($request->per_page ?? 15);
}

Available Sort Options

  • field - Sort ascending
  • -field - Sort descending
  • relation.field - Sort by relationship field

5. Pagination

Pagination works seamlessly with Laravel's built-in pagination:

// GET /posts?page=2&per_page=20
$posts = Post::filter($request->query())
    ->paginate($request->per_page ?? 15);

🚀 Advanced Usage

1. Complex Filter Groups

Create complex filter conditions with AND/OR logic:

// Example: (status = 'published' AND (title LIKE '%Laravel%' OR views > 100)) AND (author_id = 1 OR author_id = 2)
$filters = [
    '_group' => [
        'boolean' => 'and',
        'filters' => [
            'status' => 'published',
        ],
        'nested' => [
            [
                'boolean' => 'or',
                'filters' => [
                    'title' => ['like' => '%Laravel%'],
                    'views' => ['gt' => 100],
                ],
            ],
            [
                'boolean' => 'or',
                'filters' => [
                    'author_id' => [1, 2],
                ],
            ],
        ],
    ],
];

$posts = Post::filter($filters)->get();

2. Custom Filter Classes

For complex filtering logic, create a custom filter class:

<?php

namespace App\Filters;

use Dibakar\LaravelDynamicFilters\Contracts\FilterContract;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;

class PublishedInLastDaysFilter implements FilterContract
{
    /**
     * Apply the filter to the query.
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param mixed $value
     * @param string $property
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function apply(Builder $query, $value, string $property): Builder
    {
        $days = is_numeric($value) ? (int) $value : 7; // Default to 7 days if invalid
        
        return $query->where('published_at', '>=', Carbon::now()->subDays($days));
    }
    
    /**
     * Validate the filter value.
     *
     * @param mixed $value
     * @return bool
     */
    public function validate($value): bool
    {
        return is_numeric($value) && $value > 0;
    }
    
    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function getValidationMessage(): string
    {
        return 'The days parameter must be a positive number.';
    }
}

Register your custom filter in config/dynamic-filters.php:

'custom_filters' => [
    'published_in_days' => \App\Filters\PublishedInLastDaysFilter::class,
    'active_users' => \App\Filters\ActiveUsersFilter::class,
    // Add more custom filters as needed
],

Now use it in your API:

// GET /posts?published_in_days=30
$recentPosts = Post::filter(request()->query())->get();

3. Filter Presets

Define common filter presets in your configuration:

// config/dynamic-filters.php
'presets' => [
    'recent' => [
        'created_at' => ['gt' => now()->subMonth()->toDateString()],
        'sort' => '-created_at',
    ],
    'popular' => [
        'views' => ['gt' => 1000],
        'status' => 'published',
        'sort' => '-views,title',
    ],
    'featured' => [
        'is_featured' => true,
        'status' => 'published',
        'sort' => '-created_at',
    ],
],

Use presets in your API:

# Get recent posts
GET /posts?preset=recent

# Get popular posts
GET /posts?preset=popular

# Combine with additional filters
GET /posts?preset=featured&category=laravel

4. API Resource Integration

Easily integrate with Laravel's API Resources:

// app/Http/Resources/PostResource.php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'slug' => $this->slug,
            'excerpt' => $this->excerpt,
            'content' => $this->when($this->showFullContent, $this->content),
            'status' => $this->status,
            'views' => $this->views,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
            'author' => UserResource::make($this->whenLoaded('author')),
            'category' => CategoryResource::make($this->whenLoaded('category')),
            'tags' => TagResource::collection($this->whenLoaded('tags')),
        ];
    }
}

// In your controller
public function index(Request $request)
{
    $posts = Post::filter($request->query())
        ->with(['author', 'category', 'tags'])
        ->paginate($request->per_page ?? 15);

    return PostResource::collection($posts);
}

5. Performance Optimization

Database Indexing

// In a migration
public function up()
{
    Schema::table('posts', function (Blueprint $table) {
        // Single column indexes
        $table->index('status');
        $table->index('published_at');
        $table->index('views');
        
        // Composite index for common filter combinations
        $table->index(['status', 'published_at']);
        $table->index(['category_id', 'status', 'published_at']);
    });
}

Selective Field Loading

// Only select the fields you need
$posts = Post::select([
        'id', 
        'title', 
        'slug', 
        'excerpt', 
        'status', 
        'published_at',
        'author_id',
        'category_id'
    ])
    ->with([
        'author:id,name,avatar',
        'category:id,name,slug',
        'tags:id,name,slug'
    ])
    ->filter($filters)
    ->paginate(15);

Performance Tips

  1. Index Your Database: Add indexes to columns used in filtering, searching, and sorting to improve query performance.

  2. Use Select Wisely: Only select the columns you need to reduce memory usage.

    $posts = Post::filter($filters)
        ->select('id', 'title', 'created_at')
        ->with('author:id,name')
        ->get();
  3. Eager Load Relationships: Use with() to avoid N+1 query problems.

    $posts = Post::filter($filters)
        ->with(['author', 'category'])
        ->get();
  4. Limit Result Size: Always use pagination or limit for large datasets to improve performance.

    $posts = Post::filter($filters)->paginate(15);
  5. Consider Caching: For expensive queries, consider implementing caching at the application level using Laravel's caching system.

6. Error Handling

Handle filter validation errors gracefully:

try {
    $posts = Post::filter($request->query())->paginate(15);
    return PostResource::collection($posts);
} catch (\Dibakar\LaravelDynamicFilters\Exceptions\InvalidFilterException $e) {
    return response()->json([
        'message' => 'Invalid filter parameters',
        'errors' => $e->getMessage()
    ], 400);
} catch (\Exception $e) {
    return response()->json([
        'message' => 'An error occurred while processing your request.',
        'error' => config('app.debug') ? $e->getMessage() : 'Server error'
    ], 500);
}

7. Testing Your Filters

Write tests to ensure your filters work as expected:

// tests/Feature/PostFilterTest.php

public function test_can_filter_posts_by_status()
{
    $published = Post::factory()->create(['status' => 'published']);
    $draft = Post::factory()->create(['status' => 'draft']);

    $response = $this->getJson('/api/posts?status=published');

    $response->assertStatus(200)
        ->assertJsonCount(1, 'data')
        ->assertJsonFragment(['id' => $published->id])
        ->assertJsonMissing(['id' => $draft->id]);
}

public function test_can_search_posts()
{
    $laravelPost = Post::factory()->create([
        'title' => 'Getting Started with Laravel',
        'content' => 'Laravel is a web application framework...'
    ]);
    
    $symfonyPost = Post::factory()->create([
        'title' => 'Symfony vs Laravel',
        'content' => 'Comparison between the two frameworks...'
    ]);

    $response = $this->getJson('/api/posts?q=laravel');

    $response->assertStatus(200)
        ->assertJsonCount(2, 'data')
        ->assertJsonFragment(['id' => $laravelPost->id])
        ->assertJsonFragment(['id' => $symfonyPost->id]);
}

Available Operators

Operator Description Example
= Equals ?status=active
!= Not equals ?status[neq]=inactive
> Greater than ?views[gt]=100
>= Greater than or equal ?rating[gte]=4
< Less than ?price[lt]=100
<= Less than or equal ?age[lte]=30
like Like (case-sensitive) ?name[like]=%john%
ilike Like (case-insensitive) ?email[ilike]=%gmail.com
in In array ?status[in]=active,pending
not_in Not in array ?id[not_in]=1,2,3
between Between values ?created_at[between]=2023-01-01,2023-12-31
null Is null ?deleted_at[null]
notnull Is not null ?updated_at[notnull]

Security

By default, only fields defined in the $filterable array can be filtered. This is a security measure to prevent unauthorized filtering on sensitive fields.

You can also define a global whitelist in the config file that applies to all models:

'global_whitelist' => [
    'id',
    'status',
    'created_at',
    'updated_at',
],

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email dibakarmitra07@gmail.com instead of using the issue tracker.

License

The MIT License (MIT). Please see License File for more information.

统计信息

  • 总下载量: 131
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-09-17

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固