ambengers / query-filter
Laravel package for filtering resources with request query string
Installs: 10 703
Dependents: 0
Suggesters: 0
Security: 0
Stars: 32
Watchers: 2
Forks: 5
Open Issues: 0
Requires
- php: ^7.1|^8.0
Requires (Dev)
- laravel/legacy-factories: ^1.0.4
- mockery/mockery: ~1.0
- orchestra/testbench: ^6.0|^7.0|^8.0
- phpunit/phpunit: ^8.0|^9.0
README
This packages provides an elegant way to filter your eloquent models via the request query string.
Inspired by Laracasts
Features
This packages allows you to create filters via the request query string. Out of the box, this package also features sorting, pagination and search for your eloquent models.
Installation
Run the following command in the terminal.
composer require ambengers/query-filter
Then publish the config file by running the following command.
php artisan vendor:publish --tag=query-filter-config
The config file contains the configuration for the namespace and path of the filter classes. The default namespace is App\Filters
and default path is app/Filters
.
Usage
Method-based Filters
You can generate a filter class using the make:query-filter
command.
php artisan make:query-filter PostFilter
In the filter class, you can also define your own custom filters. For example, lets add a filter for /posts?published
to get only the published posts:
use Ambengers\QueryFilter\AbstractQueryFilter; class PostFilter extends AbstractQueryFilter { /** * Filter the post to get the published ones * * @return Illuminate\Database\Eloquent\Builder */ public function published() { return $this->builder->whereNotNull('published_at'); } }
Now, you can apply the filter on your controller. For example:
use App\Filters\PostFilter; class PostController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index(PostFilter $filters) { $posts = Post::filter($filters); return PostResource::collection($posts); } }
Object-based Filters
If you like a more object oriented approach for creating your filters, you can create a $filters
array in your Filter class to declare your filters.
use Ambengers\QueryFilter\AbstractQueryFilter; class PostFilter extends AbstractQueryFilter { /** * List of filters. * * @var array */ protected $filters = [ 'published' => \App\Filters\Published::class, ]; }
$filters
array will receive a key-value pair in which the key is the param in your query string and the value is the filter object that will handle the filtering.
Then you can use the make:query-filter-object
command to generate your filter object.
Note: filter objects will use the same namespace as your filter class.
php artisan make:query-filter-object Published
The filter object is a simple invokable class that accepts the Eloquent\Builder
as first parameter and the query string value as the second parameter.
Include the filter logic in the invoke method, like so.
use Illuminate\Database\Eloquent\Builder; class Published { /** * Handle the filtering * * @param Illuminate\Database\Eloquent\Builder $builder * @param string|null $value * @return Illuminate\Database\Eloquent\Builder */ public function __invokable(Builder $builder, $value = null) { $builder->whereNotNull('published_at'); } }
Sorting
This package also allows you to sort your models by following field|direction
syntax, like so.
/** Sorting */
/posts?sort=created_at|desc
Pagination
This package also allows you to paginate your models like so.
/** Pagination */ /posts?page=2
Behind the scenes, it uses Laravel's own pagination, which the default per_page
size is 15. Of course, you can override this behaviour like so.
/** Pagination */ /posts?page=2&per_page=10
Note: If pagination keys are not present on the request query string, it will return a collection result.
Search
This package also allows you to define the columns that are searchable. By default, when you generate a filter class with make:query-filter
command,
the class will contain a $searchableColumns
array. Then, list the searchable columns of your model in this array.
class PostFilter extends AbstractQueryFilter { /** * List of searchable columns * * @var array */ protected $searchableColumns = ['subject', 'body']; }
Searchable Relationship Columns
The $searchableColumns
can also accept a key value pair if you want your model to be searchable using relationship fields:
class PostFilter extends AbstractQueryFilter { /** * List of searchable columns * * @var array */ protected $searchableColumns = [ 'subject', 'body', 'comments' => ['body'], ]; }
Loadable Relationships
This packages allows you to load relationships of models using the query string.
First, use the make:query-loader
command to create your loader class.
php artisan make:query-loader PostLoader
Then, declare the loader class within your filter class.
use App\Loaders\PostLoader; class PostFilter extends AbstractQueryFilter { /** * Loader class * * @var string */ protected $loader = PostLoader::class; }
Then on the loader class, declare the relationships that can be eager/lazy-loaded within $loadables
array.
class PostLoader extends AbstractQueryLoader { /** * Relationships that can be lazy/eager loaded * * @var array */ protected $loadables = [ 'comments', 'author' ]; }
And that's it! Now use the load
param on your query string to load relationships.
/posts?load=comments,author
Note: Relationships with multiple words can be declared using either camel or snake case within the $loadables
array.
The package will automatically convert the relationships into snake case which is typically how you will write your relationship methods.
Also, relationships that are not declared in $loadables
array will not be eager-loaded even if used in query string.
Using Loader On The Controller@show Action
Controller@show
action will typically return a single model instance instead of a collection.
However, there are cases that you will need an ability to optionally load relationships via query string as well.
Inject your loader class as an argument to your show
method, then call the filter
method on your and pass the loader instance.
class PostController extends Controller { /** * Display the specified resource. * * @param App\Models\Post $post * @param App\Loaders\PostLoader $loader * @return Illuminate\Http\JsonResponse */ public function show(Post $post, PostLoader $loader) { $post = $post->filter($loader); return response()->json($post); } }
Now you should be able to load your relationships from your query string.
/posts/1?load=comments,author
Including Soft Delete Constraints
Include soft deleted constraits when requesting for eager loaded models using the pipe symbol.
/posts/1?load=comments|withTrashed // comments will include soft deleted models /posts/1?load=comments|onlyTrashed // comments will include only soft deleted models
Preventing Method Name Clash
To customize the method name you call on your model to use the query filter, just update the value of the method
key in the query_filter config file.
return [ // The method to call to use the query filter 'method' => 'fooBar', // Now call $post->fooBar($loaders) ... ]
With Laravel Livewire
Livewire follows its own structure when sending requests to the backend. This makes it impossible for query-filter package to automatically read parameters from the request query string.
However, you can still manually assign parameters during runtime by resolving your query filter class from the container and set the parameters
like so...
public function render () { $filters = app(PostFilter::class)->parameters(['search' => 'foo']); $posts = Post::filter($filters); return view('livewire.posts.index', ['posts' => $posts]); }