meius / laravel-filter
A Laravel package for applying dynamic filters to Eloquent models based on request parameters.
Fund package maintenance!
Buy Me A Coffee
Requires
- php: ^8.0
- illuminate/console: ^9.0 || ^10.0 || ^11.0 || ^12.0
- illuminate/database: ^9.0 || ^10.0 || ^11.0 || ^12.0
- illuminate/filesystem: ^9.0 || ^10.0 || ^11.0 || ^12.0
- illuminate/http: ^9.0 || ^10.0 || ^11.0 || ^v12.0
- illuminate/routing: ^9.0 || ^10.0 || ^11.0 || ^12.0
- illuminate/support: ^9.0 || ^10.0 || ^11.0 || ^12.0
Requires (Dev)
- friendsofphp/php-cs-fixer: *
- orchestra/testbench: ^7 || ^8 || ^9 || ^10
- phpstan/phpstan: ^1 || ^2
- phpunit/phpunit: ^9.6 || ^10
- squizlabs/php_codesniffer: 4.0.x-dev
README
Table of Contents
- Overview
- Requirements
- Getting Started
- Installation
- Configuration
- Usage
- Configuring Filter Aliases and Prefixes
- Examples for Other Databases
- Support
- License
Overview
The meius/laravel-filter
package provides a convenient way to apply filters to Eloquent models in a Laravel application. It allows you to define filters using attributes and apply them dynamically based on the request.
Requirements
- PHP >= 8.0
- Laravel >= 9.0
Getting Started
To get started with the meius/laravel-filter
package, follow the installation instructions below and check out the usage examples.
Installation
- Install the package via Composer:
composer require meius/laravel-filter
Configuration
- To publish the configuration file, run the following command:
php artisan vendor:publish --tag=filter-config
This will create a config/filter.php
file where you can customize the filter settings.
Usage
Creating Filters
- To create a new filter, use the
make:filter
Artisan command:php artisan make:filter {name}
Applying Filters
-
Define filters using attributes in your controller methods:
use App\Attributes\Filter\ApplyFiltersTo; use App\Models\Post; class PostController { #[ApplyFiltersTo(Post::class)] public function index() { return Post::query()->get(); } }
-
To apply filters to related models, use the
ApplyFiltersTo
attribute with multiple model classes:use App\Attributes\Filter\ApplyFiltersTo; use App\Models\Author; use App\Models\Comment; use App\Models\Post; class PostController { #[ApplyFiltersTo(Post::class, Comment::class, Author::class)] public function index() { return Post::query() ->with([ 'comments', 'author', ]) ->get(); } }
Applying Filters to Route Groups
By default, filters are applied to the web and API route groups. To customize this behavior, you must publish the configuration file and make the necessary adjustments.
-
Publish the Configuration File:
php artisan vendor:publish --tag=filter-config
-
Update the Configuration: Open the
config/filter.php
file and modify theroute_groups
section to specify which groups should have filters applied.return [ // Filters will not be applied to API route groups with this change. 'route_groups' => [ //'api', 'web', ], ];
Caching Filters
- To cache the filters for faster loading, run the following Artisan command:
php artisan filter:cache
Example
Here is an example of how to define and apply filters:
-
Create a filter:
use Illuminate\Database\Eloquent\Builder; use Meius\LaravelFilter\Filters\Filter; class TitleFilter extends Filter { /** * The key used to identify the filter parameter in the request. */ protected string $key = 'title'; protected function query(Builder $builder, $value): Builder { return $builder->where('title', 'like', "%$value%"); } }
-
Create a filter for related models:
use Illuminate\Database\Eloquent\Builder; use Meius\LaravelFilter\Filters\Filter; class AuthorIdFilter extends Filter { protected string $key = 'author_id'; protected function query(Builder $builder, $value): Builder { return $builder->whereHas('author', function (Builder $query) use ($value): void { $query->where('id', '=', $value); }); } }
-
If you need to apply a filter according to a condition, you can use the
canContinue
method:use Illuminate\Database\Eloquent\Builder; use Meius\LaravelFilter\Filters\Filter; class OwnerFilter extends Filter { protected string $key = 'owner'; protected function query(Builder $builder, $value): Builder { return $builder->where('user_id', '=', $value); } protected function canContinue(Request $request): bool { return $request->user()->hasSubscription(); } };
Using ExcludeFrom
and OnlyFor
Attributes
You can use the ExcludeFrom
and OnlyFor
attributes to conditionally apply filters.
-
Create a filter with
ExcludeFrom
:use App\Models\User; use App\Models\Category; use Meius\LaravelFilter\Attributes\ExcludeFrom; use Meius\LaravelFilter\Filters\Filter; // The filter will never be applied to the "User" model and beyond. #[ExcludeFrom(User::class, Category::class, ...)] class ContentFilter extends Filter { protected string $key = 'content'; protected function query(Builder $builder, $value): Builder { // Filter logic } }
-
Create a filter with
OnlyFor
:use App\Models\Post; use App\Models\Comment; use Meius\LaravelFilter\Attributes\OnlyFor; use Meius\LaravelFilter\Filters\Filter; // The filter will be applied to the "Post" model and beyond only. #[OnlyFor(Post::class, Comment::class, ...)] class ContentFilter extends Filter { protected string $key = 'content'; protected function query(Builder $builder, $value): Builder { // Filter logic } }
-
You can also use the
ExcludeFrom
andOnlyFor
attributes together(If you need it \@_@/):use App\Models\Comment; use App\Models\Post; use App\Models\User; use Meius\LaravelFilter\Attributes\ExcludeFrom; use Meius\LaravelFilter\Attributes\OnlyFor; use Meius\LaravelFilter\Filters\Filter; // The filter will be applied to the "Comment" and "User" models only. #[ OnlyFor(Post::class, Comment::class, User::class), ExcludeFrom(Post::class), ] class ContentFilter extends Filter { protected string $key = 'content'; protected function query(Builder $builder, $value): Builder { // Filter logic } }
-
Create a filter using properties:
use App\Models\Comment; use App\Models\Post; use Meius\LaravelFilter\Filters\Filter; class ContentFilter extends Filter { /** * The models to which the filter should exclusively apply. * * @var array<Model> */ protected array $onlyFor = [ Comment::class, Post::class, ]; /** * The models to which the filter should not be applied. * * @var array<Model> */ protected array $excludeFrom = []; protected string $key = 'content'; protected function query(Builder $builder, $value): Builder { // Filter logic } }
-
Create a filter using methods:
use App\Models\Comment; use App\Models\Post; use Meius\LaravelFilter\Filters\Filter; class ContentFilter extends Filter { protected string $key = 'content'; protected function query(Builder $builder, $value): Builder { // Filter logic } protected function onlyFor(): array { return [ User::class, Category::class, ]; } protected function excludeFrom(): array { return []; } }
Prioritization of Settings
When defining filter settings, the priority of the settings is as follows:
- Method: The highest priority. If a method is defined to specify the filter settings, it will override any settings defined via properties or attributes.
- Property: Medium priority. If a property is defined to specify the filter settings, it will override any settings defined via attributes but will be overridden by method definitions.
- Attribute: The lowest priority. If settings are defined via attributes, they will be used only if there are no corresponding settings defined via methods or properties.
This means that if there are conflicting settings defined in multiple ways, only the setting with the highest priority will be applied. For example, if a filter has both a method and an attribute defining the same setting, the method's setting will take precedence and be applied, while the attribute's setting will be ignored.
Example Request Structure
-
For filters to work correctly, the query must have the appropriate structure. Here is an example of how the query should be structured:
{ "filter": { "posts": { "title": "Deep Thoughts on the Hitchhiker's Guide", "published_after": "2005-04-28" }, "comments": { "content": "The answer to this question is 42" } } }
-
Example request:
GET /posts?filter[posts][title]=Hitchhiker&filter[posts][published_after]=2005-04-28&filter[comments][content]=42
Configuring Filter Aliases and Prefixes
Adding Filter Aliases to Models
-
To add filter aliases to your models, use the
HasFilterAlias
trait and define the$filterAlias
property in your model.namespace App\Models; use Illuminate\Database\Eloquent\Model; use Meius\LaravelFilter\Traits\HasFilterAlias; class HtmlFivePackage extends Model { use HasFilterAlias; protected string $filterAlias = 'h5p'; }
Configuring the Filter Prefix
-
You can change the filter prefix by modifying the
config/filter.php
configuration file. Update theprefix
value to your desired string.return [ 'prefix' => 'f', ];
Example JSON and URL Requests
-
JSON Request
Here,
"f"
is the filter prefix specified in the config, and"h5p"
is the alias defined in the model.{ "f": { "h5p": { "name": "blog" } } }
-
URL Request
Similarly, in the URL request,
"f"
represents the filter prefix and"h5p"
is the alias.GET /posts?f[h5p][name]=blog
Advanced Usage
Complex Filters
- Create a complex filter that combines multiple conditions:
use Illuminate\Database\Eloquent\Builder; use Meius\LaravelFilter\Filters\Filter; class ComplexFilter extends Filter { protected string $key = 'complex_filter'; protected function query(Builder $builder, $value): Builder { return $builder->where('status', 'active') ->where(function ($query) use ($value) { $query->where('name', 'like', "%{$value}%") ->orWhere('description', 'like', "%{$value}%"); }); } }
Combined Filters
- Apply multiple filters to a single query:
use Illuminate\Database\Eloquent\Builder; use Meius\LaravelFilter\Filters\Filter; class CombinedFilter extends Filter { protected string $key = 'combined_filter'; protected function query(Builder $builder, $value): Builder { return $builder->where('category', $value['category']) ->where('price', '>=', $value['min_price']) ->where('price', '<=', $value['max_price']); } }
Examples for Other Databases
Using the query
method, you can create filters for different databases.
PostgreSQL
- Create a filter for a PostgreSQL database:
use Illuminate\Database\Eloquent\Builder; use Meius\LaravelFilter\Filters\Filter; class TitleFilter extends Filter { protected string $key = 'title'; protected function query(Builder $builder, $value): Builder { return $builder->whereRaw('title ILIKE ?', ["%{$value}%"]); } }
SQLite
- Create a filter for an SQLite database:
use Illuminate\Database\Eloquent\Builder; use Meius\LaravelFilter\Filters\Filter; class TitleFilter extends Filter { protected string $key = 'title'; protected function query(Builder $builder, $value): Builder { return $builder->where('title', 'like', "%{$value}%"); } }
Support
For support, please open an issue on the GitHub repository.
Contributing
We welcome contributions to the meius/laravel-filter
library. To contribute, follow these steps:
- Fork the Repository: Fork the repository on GitHub and clone it to your local machine.
- Create a Branch: Create a new branch for your feature or bugfix.
- Write Tests: Write tests to cover your changes.
- Run Tests: Ensure all tests pass by running
phpunit
. - Submit a Pull Request: Submit a pull request with a clear description of your changes.
For more details, refer to the CONTRIBUTING.md file.
License
This package is open-sourced software licensed under the MIT license.