indexzer0/eloquent-filtering
Filter Laravel Eloquent models using simple arrays and request data—no custom query spaghetti. Define allowed filters on your models, support complex search, and keep queries readable, maintainable, and easy to extend for APIs and dashboards.
Pivot filters can be specified in two ways:
Pivot, MorphPivot).Model).If you are utilising Custom Intermediate Table Models, you may define filters on that model.
Filters defined on this model will be applied to the intermediate table.
/*
* User Model
*/
class User extends Model implements IsFilterable
{
use Filterable;
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class)->using(RoleUser::class);
}
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::relation('roles', [FilterType::HAS])->includeRelationFields(),
);
}
}
/*
* Role Model
*/
class Role extends Model implements IsFilterable
{
use Filterable;
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)->using(RoleUser::class);
}
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::relation('users', [FilterType::HAS])->includeRelationFields(),
);
}
}
/*
* RoleUser Pivot Model
*/
class RoleUser extends Pivot implements IsFilterable
{
use Filterable;
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::field('assigned_by', [FilterType::EQUAL]),
);
}
}
/*
* Applying `assigned_by` pivot filter
* within `roles` `$has` relation filter.
*/
User::filter([
[
'type' => '$has',
'target' => 'roles',
'value' => [
[
'type' => '$eq',
'target' => 'assigned_by',
'value' => 'admin',
]
]
]
]);
/*
* Applying `assigned_by` pivot filter
* within `users` `$has` relation filter.
*/
Role::filter([
[
'type' => '$has',
'target' => 'users',
'value' => [
[
'type' => '$eq',
'target' => 'assigned_by',
'value' => 'admin',
]
]
]
]);
Sometimes you may only want the pivot filter to be allowed when filtering by relationship one way but not the other way.
For example:
User $has Role.Role $has User.To achieve this you can specify the allowed "from" model for the pivot filter.
/*
* RoleUser Pivot Model
*/
class RoleUser extends Pivot implements IsFilterable
{
use Filterable;
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::field('assigned_by', [FilterType::EQUAL])
->pivot(User::class),
);
}
}
If you are NOT utilising Custom Intermediate Table Models, you may define pivot filters on your main models.
Filter::field() filters can be marked as pivot filters if you want the filter to be applied to a column on the intermediate table linking the models.
You must specify the parent model fqcn.
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::field('tagged_by', [FilterType::EQUAL])
->pivot(Post::class),
);
}
In the below example of class Post and class Tag.
allowedFilters method of both classes.posts or tags relationship.class Post extends Model implements IsFilterable
{
use Filterable;
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
}
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::field('tagged_by', [FilterType::EQUAL])->pivot(Tag::class),
Filter::relation('tags', [FilterType::HAS])->includeRelationFields()
);
}
}
class Tag extends Model implements IsFilterable
{
use Filterable;
public function posts(): BelongsToMany
{
return $this->belongsToMany(Post::class);
}
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::field('tagged_by', [FilterType::EQUAL])->pivot(Post::class),
Filter::relation('posts', [FilterType::HAS])->includeRelationFields()
);
}
}
With the models setup as described above. The following filters are allowed.
/*
* Applying `tagged_by` pivot filter
* within `tags` `$has` relation filter.
*/
Post::filter([
[
'type' => '$has',
'target' => 'tags',
'value' => [
[
'type' => '$eq',
'target' => 'tagged_by',
'value' => 'admin',
]
]
]
]);
/*
* Applying `tagged_by` pivot filter
* within `posts` `$has` relation filter.
*/
Tag::filter([
[
'type' => '$has',
'target' => 'posts',
'value' => [
[
'type' => '$eq',
'target' => 'tagged_by',
'value' => 'admin',
]
]
]
]);
With the models setup as described above. The following filters are denied.
/*
* Applying `tagged_by` pivot filter
* when not in context of `tags` `$has` relation filter.
* throws DeniedFilterException.
*/
Post::filter([
[
'type' => '$eq',
'target' => 'tagged_by',
'value' => 'admin',
]
]);
/*
* Applying `tagged_by` pivot filter
* when not in context of `posts` `$has` relation filter.
* throws DeniedFilterException.
*/
Tag::filter([
[
'type' => '$eq',
'target' => 'tagged_by',
'value' => 'admin',
]
]);
/*
* Applying `tagged_by` pivot filter
* when in context of another BelongsToMany relation
* but User::class not defined in the ->pivot() method in the tags model.
* throws DeniedFilterException.
*/
User::filter([
[
'type' => '$has',
'target' => 'tags',
'value' => [
[
'type' => '$eq',
'target' => 'tagged_by',
'value' => 'admin',
]
]
]
]);
When defining a pivot filter for MorphToMany relations, you can specify a list of models in the ->pivot() method.
class Epic extends Model implements IsFilterable
{
// ...
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::relation('labels', [FilterType::HAS])->includeRelationFields()
);
}
}
class Issue extends Model implements IsFilterable
{
// ...
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::relation('labels', [FilterType::HAS])->includeRelationFields()
);
}
}
class Label extends Model implements IsFilterable
{
// ...
public function allowedFilters(): AllowedFilterList
{
return Filter::only(
Filter::field('labeled_by', [FilterType::EQUAL])
->pivot(Epic::class, Issue::class)
);
}
}
How can I help you explore Laravel packages today?