thejano / laravel-filterable
Add filtration functionality to Laravel Models
Installs: 2 220
Dependents: 0
Suggesters: 0
Security: 0
Stars: 68
Watchers: 2
Forks: 3
Open Issues: 0
Requires
- php: ^8.1
- illuminate/contracts: ^10.0|^11.0
- phpdocumentor/type-resolver: ^1.5
- spatie/laravel-package-tools: ^1.9.0
- spatie/php-structure-discoverer: ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- nesbot/carbon: ^2.63
- nunomaduro/collision: ^7.10.0|^8.0.1
- orchestra/testbench: ^8.0|^9.0
- pestphp/pest: ^2.31
- pestphp/pest-plugin-laravel: ^2.0
- phpunit/phpunit: ^10.0
README
This package adds filtration functionality to Laravel Models. It would be based on Filterable and Query Filter classes. The package will provide commands to generate Filterable and Query Filter classes. By default, it will add some default filtration out of the box to you models like ordering, get data between two dates and more.
Imagine you have a url containing the following parameters:
/posts?slug=the-new-web&published=true&category=web-development&tags[]=web&tags[]=laravel&tags[]=flutter
Laravel request all method request()->all()
will return something like this:
[ "slug" => "the-new-web", "published" => "true", "category" => "web-development", "tags" => [ "web", "laravel", "flutter"], ]
Normally, you should do the logic one by one to perform the filtration
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Post; class PostController extends Controller { public function index(Request $request) { $query = Post::query(); if ($request->has('title')) { $query->where('title', 'LIKE', '%' . $request->input('title') . '%'); } if ($request->has('published')) { $query->where('published', (bool) $request->input('published')); } if ($request->has('category')){ $query->whereHas('category', function ($query) use ($request) { return $query->where('category_slug', $request->input('category')); }); } if ($request->has('tags')){ $query->whereHas('tag', function ($query) use ($request) { return $query->where('tag_slug', $request->input('tags')); }); } $posts = $query->get(); return view('posts',compact('posts')); } }
For simple queries, it would be fine, but when you have a bunch of filters, you can not control them and none of them are reusable.
With this package you need to only pass filterable()
scope method to your model before returning the records, check below example:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Post; class PostController extends Controller { public function index(Request $request) { $posts = Post::filterable()->get(); return view('posts',compact('posts')); } }
Requirement
The package requires:
- PHP 8.1 or higher
- Laravel 10.x or higher
Installation
You can install the package via composer:
composer require thejano/laravel-filterable
You can publish the config file with:
php artisan vendor:publish --tag="filterable-config"
Usage
To add the magic you should add only hasFilterableTrait
to your model.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use TheJano\LaravelFilterable\Traits\HasFilterableTrait; class Post extends Model { use HasFactory; use HasFilterableTrait; }
Then you need to create a FilterableClass and some Query Filters to define your rules.
To remove the pain of creating the classes, already I added some commands.
- For creating a Filterable class, you need to run this command:
php artisan make:filterable PostsFilterable
It would generate a class under \App\Filters\Filterable\PostsFilterable
and it contains:
<?php namespace App\Filters\Filterable; use TheJano\LaravelFilterable\Abstracts\FilterableAbstract; use TheJano\LaravelFilterable\Interfaces\FilterableInterface; class PostsFilterable extends FilterableAbstract implements FilterableInterface { /** * It contains list of Query Filters * * @var Array */ protected array $filters = []; }
- And now let's create a Query Filter class:
php artisan make:query-filter PublishedQueryFilter
It would generate a class under \App\Filters\QueryFilter\PublishedQueryFilter
and it contains:
<?php namespace App\Filters\QueryFilter; use Illuminate\Database\Eloquent\Builder; use TheJano\LaravelFilterable\Abstracts\QueryFilterAbstract; use TheJano\LaravelFilterable\Interfaces\QueryFilterInterface; class PublishedQueryFilter extends QueryFilterAbstract implements QueryFilterInterface { /** * Can be used to map the values. * It can be returned through resolveValue method * * @var Array */ protected array $mapValues = []; /** * Handle The Query Filter * * * @param Builder $builder Query Builder * @param string $value * @return Builder **/ public function handle(Builder $builder, $value): Builder { return $builder; } }
You will do the logic for each column inside each Query Filter separately. Let's implement the logic here
public function handle(Builder $builder, $value): Builder { return $builder->where('published', $value); }
The returned value is a string, and it does not return any data. So we should map the value.
There is a property $mapValues
inside the class.
protected array $mapValues = [ 'true' => true, 'false' => false, ];
And finally, we should resolve the mapped value through resolveValue()
method.
protected array $mapValues = [ 'true' => true, 'false' => false, ]; public function handle(Builder $builder, $value): Builder { $value = $this->resolveValue($value); // return Builder if the value is null if (is_null($value)) { return $builder; } return $builder->where('published', $value); }
To make the Query Filter live, we should append it to the $columns
property of PostsFilterable
class.
public array $filters = [ 'published' => 'App\\Filters\\QueryFilter\\PublishedQueryFilter', ];
- When we create a Query Filter directly you can pass the Filterable class as a parameter to auto-insert into
$filters
property.
php artisan make:query-filter PublishedQueryFilter --filterable=PostsFilterable
Now your PostsFilterable
class should contain something like this:
<?php namespace App\Filters\Filterable; use TheJano\LaravelFilterable\Abstracts\FilterableAbstract; use TheJano\LaravelFilterable\Interfaces\FilterableInterface; class PostsFilterable extends FilterableAbstract implements FilterableInterface { /** * It contains list of Query Filters * * @var Array */ public array $filters = [ 'published' => 'App\\Filters\\QueryFilter\\PublishedQueryFilter', ]; }
The final step is enabling PostsFilterable
to your model.
There are 3 ways to enable it:
-
Do nothing :) It would enable all default Query Filters to your model.
-
Passing Filterable class to
filterableClass()
method of the model.
<?php namespace App\Models; use App\Filters\Filterable\PostsFilterable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use TheJano\LaravelFilterable\Traits\HasFilterableTrait; class Post extends Model { use HasFactory; use HasFilterableTrait; /** * Enable the filterable class to the model * * @return void */ public function filterableClass() { return PostsFilterable::class; } }
- Passing as a parameter to
fliterable()
scope of your model. The scope accepts 3 parameters
public function scopeFilterable(Builder $builder, $request = null, $filterableClass = null, $filters = []): Builder
If you pass Filterable class as 1st parameter, under the hood the package will handle it for you and ignore the $request
variable and uses the request()
helper function. Let's check the code below.
<?php namespace App\Http\Controllers; use App\Filters\Filterable\PostFilterable; use App\Models\Post; use Illuminate\Http\Request; class PostController extends Controller { public function index(Request $request) { $posts = Post::filterable(PostFilterable::class)->get(); return view('posts', compact('posts')); } }
Also, you can pass some additional Query Filters through $filters
parameter, for example:
<?php namespace App\Http\Controllers; use App\Filters\Filterable\PostFilterable; use App\Filters\QueryFilter\TitleQueryFilter; use App\Models\Post; use Illuminate\Http\Request; class PostController extends Controller { public function index(Request $request) { $posts = Post::filterable(PostFilterable::class,[ 'title' => TitleQueryFilter::class ])->get(); return view('posts', compact('posts')); } }
Instead only Request
you can pass array of parameters to filter too.
Default Query Filters
Last but not least, by default the package deliveries some Query Filters with every Filterable class. The configuration file contains the available Query Filters, which are:
date
query filter, it returns records between 2 dates (from and to). By default, it usescreated_at
field.
/posts?date[from]=2022-06-01&date[to]=2022-07-01
Or you can pass a custom field. The delimiter is BY
/posts?date[fromBYupdated_at]=2022-06-01&date[toBYupdated_at]=2022-07-01
order
query filter, it orders the records as ASC or DESC. By default, it usescreated_at
field.
/posts?order=asc
Or you can pass a custom field.
/posts?order[id]=asc
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.