mohamedfathy/dynamic-filters

A Laravel package that provides dynamic filtering, sorting, and relationship-based querying for Eloquent models.

v1.0.0 2025-02-12 18:02 UTC

This package is auto-updated.

Last update: 2025-08-17 21:34:38 UTC


README

A lightweight and flexible query filtering package for Laravel, supporting standard filters, relationship filters, and custom filters via URL parameters. It enables sorting and pagination effortlessly while keeping query logic clean and maintainable. With intuitive syntax and extendability, it simplifies API request handling for scalable applications.

🚀 Basic Usage

Filter a query based on a request:

Example Request

GET /products?filters[category:eq]=electronics

SQL:

SELECT * FROM products WHERE category = 'Electronics';

Adding Filters to the Product Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use MohamedFathy\DynamicFilters\HasDynamicFilters;

class Product extends Model
{
    use HasDynamicFilters;

    /**
     * Define the fields that can be filtered dynamically.
     * Only these attributes can be used in query filters.
     */
    protected array $allowedFilters = ['name', 'price', 'category'];

    /**
     * Define the relationships that are allowed for filtering or eager loading.
     * Only these relations can be included in queries to prevent unwanted data exposure.
     */
    protected array $allowedRelations = ['provider'];

    /**
     * Define the fields that can be used for ordering results.
     * Only these attributes can be used in ORDER BY clauses to maintain query integrity.
     */
    protected array $allowedOrdering = ['id'];

    public function provider(): BelongsTo
    {
        return $this->belongsTo(Provider::class);
    }
}

Usage in Controller:

/**
 * list products.
 *
 * @param Request $request
 * @return JsonResponse
 */
public function index(Request $request): JsonResponse
{
    // The $request->all() resolves to an array like: ['filters' => ['category:eq' => 'electronics']]
    $data = Product::filter($request->all())->get();
    return response()->json($data);
}

🛠 Installation

Install via Composer:

composer require mohamedfathy/dynamic-filters

📌 List of Available Operators

Below are the available operators you can use in filtering:

Operator SQL Equivalent Description Example Usage
eq = Equal to filters[status:eq]=active
neq != Not equal to filters[status:neq]=active
gt > Greater than filters[price:gt]=100
lt < Less than filters[price:lt]=1000
gte >= Greater than or equal filters[rating:gte]=4
lte <= Less than or equal filters[discount:lte]=50
like LIKE Partial match filters[name:like]=car
nLike NOT LIKE Does not match pattern filters[name:nLike]=tablet
null IS NULL Field is null filters[deleted_at:null]
nNull IS NOT NULL Field is not null filters[updated_at:nNull]
in IN Value in list filters[status:in]=active,pending
nIn NOT IN Value not in list filters[status:nIn]=banned
between BETWEEN Value in range filters[price:between]=100,500
nBetween NOT BETWEEN Value outside range filters[age:nBetween]=18,60
regexp REGEXP Matches regex pattern filters[sku:regexp]=^[A-Z]+
nRegexp NOT REGEXP Does not match regex pattern filters[code:nRegexp]=[0-9]+

🔍 Query Examples

Standard Filtering

Request:

GET /products?filters[price:gte]=1000

SQL:

SELECT * FROM products WHERE price >= 1000;

Multiple Filters

Request:

GET /products?filters[category:eq]=Electronics&filters[price:gte]=500

SQL:

SELECT * FROM products WHERE category = 'Electronics' AND price >= 500;

Sorting

Request:

GET /products?orderBy=-price

SQL:

SELECT * FROM products ORDER BY price DESC;

Pagination

Request:

GET /products?page=2&perPage=10

SQL:

SELECT * FROM products LIMIT 10 OFFSET 10;

Filtering by Relationship

Request:

GET /products?relationFilters[provider.name:like]=mohamed

SQL:

SELECT * FROM products
WHERE EXISTS (
    SELECT * FROM providers
    WHERE products.provider_id = providers.id
        AND name = 'mohamed'
);

Custom Filter Example

Request:

GET /products?customFilters[mostExpensiveProductsByProvider]=12

SQL:

select `products`.`id`, `products`.`name`, `products`.`price`, `providers`.`name` as `provider_name` from `products` inner join `providers` on `products`.`provider_id` = `providers`.`id` where `products`.`is_published` = 1 and `products`.`provider_id` = 12 order by `products`.`price` desc;

🔧 Why Use Custom Filters?

By default, standard filters and relation filters cover most scenarios. However, in some cases, you may need more flexibility.
For example, if your filtering logic involves complex conditions, multiple values, or custom operations, it's best to create a dedicated Filter Class
for each model and handle all related filters inside it.

How to Create Custom Filters

Create a Filter Class for Each Model

Instead of writing filtering logic inside controllers or query scopes, you should create a dedicated filter class for each model.
For example, let's create a ProductFilters class that handles filtering for the Product model.

<?php

namespace App\Filters;

use Illuminate\Database\Eloquent\Builder;

class ProductFilters
{
    protected Builder $query;

    public function __construct(Builder $query)
    {
        $this->query = $query;
    }

    /**
     * get most expensive product for each provider.
     */
    public function mostExpensiveProductsByProvider($providerId): void
    {
        $this->query->select(
            'products.id',
            'products.name',
            'products.price',
            'providers.name as provider_name'
        )
            ->join('providers', 'products.provider_id', '=', 'providers.id')
            ->where('products.is_published', true)
            ->where('products.provider_id' , $providerId)
            ->orderBy('products.price', 'DESC');

    }
}

✅ Conclusion

  • Simple API for filtering, sorting, and relationship handling
  • Customizable filters for advanced use cases
  • Efficient query execution

📌 Need Help?

Feel free to contribute or raise an issue if you have suggestions! 🚀