midsonlajeanty/laravel-filters

A package to filter Laravel models.

v0.1.1 2026-07-26 14:47 UTC

This package is auto-updated.

Last update: 2026-07-26 14:48:09 UTC


README

Laravel Filters - Strict, attribute-driven model filtering for Laravel

Build Status Total Downloads Latest Stable Version License

Strict, attribute-driven model filtering for Laravel.

Features

  • No repetitive if scopes. Filter properties map directly to database columns. Attributes like #[MapTo] handle SQL operators automatically.
  • Strict typing. Filters are strictly typed DTOs. PHP types are automatically casted from the request, including Carbon, Enum, Uuid, Spatie\ModelStates, and custom Value Objects.
  • Array expansion. Comma-separated query strings (?tags=php,laravel) are automatically exploded into array properties. Configurable per-property via #[Delimiter].
  • Decoupled architecture. Hydration and filtering flow through a pipeline of extensible Pipes. Custom operators and casters are easily injected via the registry.
  • Fail-safe validation. Missing #[Required] parameters or invalid casts instantly throw a standard Laravel ValidationException with field-level errors.

Getting started

Requires PHP 8.3+ and Laravel 11.0+.

Install the package in your Laravel project:

composer require midsonlajeanty/laravel-filters

Optionally, publish the configuration to customize global delimiters, invalid value actions, casters, and operators:

php artisan vendor:publish --tag="laravel-filters-config"

Usage

Preparing the Model

Add the Filterable trait to any Eloquent model you want to filter:

use Illuminate\Database\Eloquent\Model;
use Mds\LaravelFilters\Traits\Filterable;

class User extends Model
{
    use Filterable;
}

Declaring a filter

Generate a new filter class:

php artisan make:filter UserFilter

Declare public properties representing your expected query string inputs. By default, properties map to columns of the same name with an = operator. PHP types are strictly enforced and automatically casted.

namespace App\Filters;

use App\Enums\UserStatus;
use Carbon\Carbon;
use Mds\LaravelFilters\Attributes\MapTo;
use Mds\LaravelFilters\Attributes\Required;
use Mds\LaravelFilters\Attributes\Sorts;
use Mds\LaravelFilters\QueryFilter;

final class UserFilter extends QueryFilter
{
    public function __construct(
        #[Required]
        public readonly UserStatus $status,

        #[MapTo('email', operator: 'contains')]
        public readonly ?string $search = null,

        #[MapTo('created_at', operator: 'gte')]
        public readonly ?Carbon $from_date = null,

        #[Sorts(
            allowed: ['created_at', 'name', 'email'],
            default: ['created_at' => 'desc'],
        )]
        public readonly array $sort = [],
    ) {}
}

Custom filtering methods

If a property requires complex logic instead of a simple column mapping, define a method named filter + property name (PascalCase). It receives the Builder and the casted value:

final class ActiveFilter extends QueryFilter
{
    public function __construct(
        public readonly bool $active,
    ) {}

    public function filterActive($query, bool $value): void
    {
        $query->whereNotNull('activated_at');
    }
}

Applying the filter

Type-hint the Filter in your Controller. It is hydrated directly from the Request via Laravel's container:

use App\Filters\UserFilter;

class UserController extends Controller
{
    public function index(UserFilter $filter)
    {
        return User::filter($filter)->paginate();
    }
}

Advanced Features

Query Name Mapping

Rename the expected query string parameter using #[QueryName]:

use Mds\LaravelFilters\Attributes\QueryName;

final class UserFilter extends QueryFilter
{
    public function __construct(
        #[QueryName('q')]
        public readonly ?string $search = null,
    ) {}
}

Now ?q=john will populate the $search property.

Explicit Casters

Force a specific caster for a property using #[Cast]:

use Mds\LaravelFilters\Attributes\Cast;
use App\Casters\MyCaster;

final class UserFilter extends QueryFilter
{
    public function __construct(
        #[Cast(MyCaster::class)]
        public readonly ?string $status = null,
    ) {}
}

Typed Arrays

Cast each element of an array to a specific type using #[ArrayOf]:

use Mds\LaravelFilters\Attributes\ArrayOf;

final class UserFilter extends QueryFilter
{
    public function __construct(
        #[ArrayOf('int')]
        public readonly array $ids = [],
    ) {}
}

?ids=1,2,3 will be casted to [1, 2, 3] (integers).

Custom Date Formats

Specify a date format for Carbon casting using #[DateFormat]:

use Carbon\Carbon;
use Mds\LaravelFilters\Attributes\DateFormat;

final class UserFilter extends QueryFilter
{
    public function __construct(
        #[DateFormat('d/m/Y')]
        public readonly ?Carbon $birth_date = null,
    ) {}
}

Custom Delimiters

Override the default array delimiter (,) on a per-property basis using #[Delimiter]:

use Mds\LaravelFilters\Attributes\Delimiter;

final class UserFilter extends QueryFilter
{
    public function __construct(
        #[Delimiter('|')]
        public readonly array $tags = [],
    ) {}
}

?tags=php|laravel['php', 'laravel'].

Hidden Properties

Exclude a property from the toArray() output using #[Hidden]:

use Mds\LaravelFilters\Attributes\Hidden;

final class UserFilter extends QueryFilter
{
    public function __construct(
        #[Hidden]
        public readonly ?string $internal = null,
    ) {}
}

Associative Array Operators

Pass an associative array as a query parameter to apply multiple operators on the same column:

?price[gte]=10&price[lte]=100
final class ProductFilter extends QueryFilter
{
    public function __construct(
        public readonly array $price = [],
    ) {}
}

Available built-in operators: eq, gt, gte, lt, lte, between, in, contains, starts, ends.

Artisan Generators

Generate custom operators and casters:

php artisan make:operator CustomOperator
php artisan make:caster CustomCaster

Configuration

The invalid_value_action config key controls how the package behaves when a value cannot be casted:

  • 'throw' (default): throws a ValidationException with field-level errors.
  • 'ignore': silently ignores the invalid parameter.
  • 'null': sets the value to null.

Contributing

You have a lot of options to contribute to this project! You can:

License

MIT