Advanced filters
Restify has base filters for usual search
or matching
.
Definition
To declare an advanced filter you should create a class that extends the Binaryk\LaravelRestify\Filters\AdvancedFilter
.
Say we have a filter that filters all ready to publish posts:
use Binaryk\LaravelRestify\Filters\AdvancedFilter;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
class ReadyPostsFilter extends AdvancedFilter
{
public function filter(RestifyRequest $request, Relation|Builder $query, $value)
{
// TODO: Implement filter() method.
}
public function rules(Request $request): array
{
return [];
}
};
Register filter
Then add the filter to the repository filters
method:
// PostRepository.php
public function filters(RestifyRequest $request): array
{
return [
ReadyPostsFilter::new(),
];
}
Authorize filter
You can authorize certain filters to be active for specific users:
// PostRepository.php
public function filters(RestifyRequest $request): array
{
return [
ReadyPostsFilter::new()->canSee(
fn($request) => $request->user()->isAdmin()
),
];
}
Apply advanced filter
To apply an advanced filter, the frontend has to send the filters
query param with a base64 encoded filter:
const filters = btoa(JSON.stringify([
{
'key': 'ready-posts-filter',
'value': null,
}
]))
const response = await axios.get(`api/restify/posts?filters=${filters}`);
The frontend has to encode into base64 an array of filters. Each filter contains 2 things:
-
key
- which is theke-bab
form of the filter class name, or a custom$uriKey
defined in the filter -
value
- this is optional, and represents the value the advanced filter will as a third argument in thefilter
method
Apply advanced filters via POST Request (Version 9.3.0+)
Starting from version 9.3.0, Laravel Restify introduces the ability to apply advanced filters using a POST request. This enhancement simplifies the process of sending complex filter payloads without the need for base64 encoding. Now, you can send the filters directly as JSON in the request body:
const filters = [
{
'key': 'ready-posts-filter',
'value': null,
}
];
const response = await axios.post(`api/restify/posts/apply-restify-advanced-filters`, { filters });
Custom uri key
Since your class names could change along the way, you can define a $uriKey
property to your filters, so the frontend will use always the same key
when applying a filter:
class ReadyPostsFilter extends AdvancedFilter
{
public static $uriKey = 'ready-posts';
//...
};
Custom title
class ReadyPostsFilter extends AdvancedFilter
{
public static $title = 'Ready to publish posts';
//...
};
Custom description
class ReadyPostsFilter extends AdvancedFilter
{
public static $description = 'Filter all posts that are ready to publish';
//...
};
Custom meta
class ReadyPostsFilter extends AdvancedFilter
{
public function meta(): array
{
return [
'icon' => 'icon',
'color' => 'red',
'operators' => [
'like' => 'Like',
'eq' => 'Equal',
]
];
}
};
Meta will be rendered key/value in the frontend:
{
...
"icon": "icon",
"color": "red",
"operators": {
"like": "Like",
"eq": "Equal"
}
}
Advanced filter value
The third argument of the filter
method is the raw value send by the frontend. Sometimes it might be an array, so you have to get the value using array access:
$value['activation']['active']
To avoid this, there is an input
method defined into the parent class, so you can use:
public function filter(RestifyRequest $request, Relation|Builder $query, $value)
{
$value = $this->input('activation.active', false);
}
This method gets a default value as a second parameter in case the frontend didn't define it.
Advanced filter rules
The rules
method return an associative array with laravel rules for the payload the frontend should send in the value
property for this specific filter. The payload is validated right before it gets to the filter method:
public function rules(Request $request): array
{
return [
'created_at' => ['required'],
];
}
So the frontend should send the created_at
value:
{
'key': 'ready-posts-filter',
'value': { created_at: '2021-01-01' }
}
And you can get this value into the filter
method using the advanced filter value:
$value = $this->input('created_at', now());
Variations
Restify ships a few types of build in filter classes you can extend for specific needs.
Date filters
Defining the filter:
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Filters\TimestampFilter;
class CreatedAfterDateFilter extends TimestampFilter
{
public function filter(RestifyRequest $request, $query, $value)
{
$query->whereDate('created_at', '>', $value);
}
}
Using filter:
public function filters(RestifyRequest $request)
{
return [
CreatedAfterDateFilter::new(),
];
}
JavaScript implementation:
const filters = btoa(JSON.stringify([
{
'key': 'created-after-date-filter',
'value': moment()->timestamp
}
]))
const response = await axios.get('api/restify/posts?filters=' + filters);
Select Filters
Defining the filter:
<?php
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Filters\SelectFilter;
use Illuminate\Http\Request;
class SelectCategoryFilter extends SelectFilter
{
public function filter(RestifyRequest $request, $query, $value)
{
// $value could be 'movie' or 'article'
$query->where('category', $value);
}
public function options(Request $request)
{
return [
'Movie category' => 'movie',
'Article Category' => 'article',
];
}
}
Using filter:
// App/Restify/PostRepository.php
public function filters(RestifyRequest $request)
{
return [
SelectCategoryFilter::new(),
];
}
JavaScript implementation:
const filters = btoa(JSON.stringify([
{
'key': 'select-category-filter',
'value': 'article'
}
]))
const response = await axios.get('api/restify/posts?filters=' + filters);
Boolean filter
Defining the filter:
<?php
use Binaryk\LaravelRestify\BooleanFilter;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Illuminate\Http\Request;
class ActiveBooleanFilter extends BooleanFilter
{
public function filter(RestifyRequest $request, $query, $value)
{
$query->where('is_active', $value['is_active']);
}
public function options(Request $request)
{
return [
'Is Active' => 'is_active',
];
}
}
Using filter:
// App/Restify/PostRepository.php
public function filters(RestifyRequest $request)
{
return [
ActiveBooleanFilter::new(),
];
JavaScript implementation:
const filters = btoa(JSON.stringify([
{
'key': 'active-boolean-filter',
'value': {
'is_active': true,
}
}
]))
const response = await axios.get('api/restify/posts?filters=' + filters);
Multiple filters
You can combine filters as you prefer:
const filters = btoa(JSON.stringify([
{
'key': 'active-boolean-filter',
'value': {
'is_active': true,
}
},
{
'key': 'select-category-filter',
'value': 'article'
},
]))
const response = await axios.get('api/restify/posts?filters=' + filters);
Get available filters
await axios.get('resitfy-api/posts/filters');
The response will look like this:
{
"data": [
{
"key": "active-boolean-filter",
"type": "boolean",
"options": [
{
"label": "Is Active",
"property": "is_active"
}
]
},
{
"key": "select-category-filter",
"type": "select",
"options": [
{
"label": "Movie category",
"property": "movie"
},
{
"label": "Article Category",
"property": "article"
}
]
},
{
"key": "created-after-date-filter",
"type": "timestamp",
"options": []
},
{
"key": "email",
"type": "value",
"description": "Email",
"label": "Email",
"meta": {
"operator": "like"
}
}
]
}
Along with custom filters, you can also include in the response the primary filters (as matches), by using ?include
query param:
/api/restify/posts/filters?include=matches,searchables,sortables
Handling Additional Payload Data in Advanced Filters
In some scenarios, you might want to send additional data beyond the standard key and value in your filter payload. For instance, you may need to specify an operator or a column to apply more complex filtering logic. Laravel Restify Advanced Filters provide a way to handle these additional payload fields using the $this->rest()
method.
Example Payload
Consider the following payload:
const filters = [
{
'key': ValueFilter::uriKey(),
'value': 'Valid%',
'operator' => 'like',
'column' => 'description',
}
];
const response = await axios.post(`api/restify/posts/apply-restify-advanced-filters`, {filters});
In this payload, besides the standard key and value, we are also sending operator and column. The operator specifies the type of SQL operation, and the column specifies the database column to filter.
Using $this->rest()
to Access Additional Data
To handle these additional fields in your filter class, you need to ensure they are accessible via the $this->rest()
method. Here is how you can achieve that:
class ValueFilter extends AdvancedFilter
{
public function filter(RestifyRequest $request, Builder|Relation $query, $value)
{
$operator = $this->rest('operator');
$column = $this->rest('column');
$query->where($column, $operator, $value);
}
public function rules(Request $request): array
{
return [];
}
}