PHPackages                             samushi/queryfilter - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. samushi/queryfilter

ActiveLibrary[Database &amp; ORM](/categories/database)

samushi/queryfilter
===================

This package allows you to filter, sort and include eloquent relations based on a request. The QueryFilter used in this package extends Laravel's default Eloquent builder.

v2.2.2(3mo ago)101.0k↓50%1MITPHPPHP ^8.2

Since Aug 31Pushed 3mo ago1 watchersCompare

[ Source](https://github.com/samushi/QueryFilter)[ Packagist](https://packagist.org/packages/samushi/queryfilter)[ RSS](/packages/samushi-queryfilter/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (4)Versions (10)Used By (1)

[![QueryFilter Banner](banner.svg)](banner.svg)

QueryFilter for Laravel
=======================

[](#queryfilter-for-laravel)

A powerful and flexible package for filtering, sorting, and managing Eloquent queries based on request parameters. QueryFilter seamlessly extends Laravel's Eloquent builder, preserving all your favorite methods and macros while adding robust filtering capabilities.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 10/11/12

Installation
------------

[](#installation)

Install the package via Composer:

```
composer require samushi/queryfilter
```

Table of Contents
-----------------

[](#table-of-contents)

- [Basic Usage](#basic-usage)
- [Creating Filters](#creating-filters)
- [Working with Arrays](#working-with-arrays)
- [Available Macros](#available-macros)
- [Advanced Usage](#advanced-usage)
- [Custom Filter Names](#custom-filter-names)
- [Best Practices](#best-practices)

Basic Usage
-----------

[](#basic-usage)

### Quick Start

[](#quick-start)

This package helps you filter Eloquent queries effortlessly based on request parameters.

**Step 1:** Create a `Filters` directory inside your `app` folder.

**Step 2:** Create filter classes that extend the base `Filter` class:

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class Search extends Filter
{
    /**
     * Search results using whereLike
     *
     * @param Builder $builder
     * @return Builder
     */
    protected function applyFilter(Builder $builder): Builder
    {
        return $builder->whereLike(['name', 'email'], $this->getValue());
    }
}
```

**Step 3:** Apply filters in your controller:

```
namespace App\Http\Controllers;

use App\Models\User;
use App\Filters\Search;
use Samushi\QueryFilter\Facade\QueryFilter;

class UserController extends Controller
{
    public function index()
    {
        $filters = [
            Search::class,
            // Add more filters here
        ];

        return QueryFilter::query(User::query(), $filters)->paginate(10);
    }
}
```

### Using the Model Method

[](#using-the-model-method)

You can use the `queryFilter` method directly on models for cleaner code:

```
use App\Filters\Search;
use App\Filters\Status;
use App\Models\User;

// Usage in controller
$users = User::queryFilter([
    Search::class,
    Status::class,
])->paginate(10);
```

**Example Request:**

```
GET /users?search=john&status=active

```

Creating Filters
----------------

[](#creating-filters)

### Filter Naming Convention

[](#filter-naming-convention)

**Important:** By default, filter class names are automatically converted to snake\_case to match request parameters.

Class NameRequest Parameter`Search``search``Status``status``PriceRange``price_range``CreatedDate``created_date`### Basic Filter Example

[](#basic-filter-example)

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class Status extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        return $builder->where('status', $this->getValue());
    }
}
```

**Usage:**

```
GET /users?status=active

```

Working with Arrays
-------------------

[](#working-with-arrays)

### Overview

[](#overview)

The `getValue()` method supports automatic array detection and conversion, making it easy to handle multiple values in your filters.

### Array Support Features

[](#array-support-features)

✅ **Comma-separated values**: `?status=active,pending,completed`✅ **Array query parameters**: `?status[]=active&status[]=pending`✅ **Automatic detection**: Detects arrays and converts them appropriately ✅ **Backward compatible**: Default behavior returns strings

### Using getValue() with Arrays

[](#using-getvalue-with-arrays)

#### Default Behavior (String)

[](#default-behavior-string)

```
class Search extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        // Returns string: "john doe"
        $value = $this->getValue();

        return $builder->where('name', 'like', "%{$value}%");
    }
}
```

**Request:** `GET /users?search=john doe`

#### Array Mode (Multiple Values)

[](#array-mode-multiple-values)

```
class Status extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        // Returns array: ["active", "pending", "completed"]
        $statuses = $this->getValue(true);

        return $builder->whereIn('status', $statuses);
    }
}
```

**Works with both formats:**

```
GET /users?status=active,pending,completed
GET /users?status[]=active&status[]=pending&status[]=completed

```

### Real-World Array Examples

[](#real-world-array-examples)

#### Multiple Categories Filter

[](#multiple-categories-filter)

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class Categories extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        $categories = $this->getValue(true); // Get as array

        return $builder->whereIn('category_id', $categories);
    }
}
```

**Usage:**

```
GET /products?categories=1,2,3,4
GET /products?categories[]=1&categories[]=2&categories[]=3

```

#### Multiple Tags Filter

[](#multiple-tags-filter)

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class Tags extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        $tags = $this->getValue(true); // ["laravel", "php", "vue"]

        return $builder->whereHas('tags', function ($query) use ($tags) {
            $query->whereIn('name', $tags);
        });
    }
}
```

**Usage:**

```
GET /posts?tags=laravel,php,vue
GET /posts?tags[]=laravel&tags[]=php&tags[]=vue

```

#### Case Status Filter

[](#case-status-filter)

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class Cases extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        $cases = $this->getValue(true); // ["sent", "delivered", "failed"]

        return $builder->whereIn('case_status', $cases);
    }
}
```

**Usage:**

```
GET /orders?cases=sent,delivered,failed
GET /orders?cases[]=sent&cases[]=delivered&cases[]=failed

```

### How getValue() Works

[](#how-getvalue-works)

Input Type`getValue()``getValue(true)``?status=active``"active"``["active"]``?status=active,pending``"active,pending"``["active", "pending"]``?status[]=active&status[]=pending``"active,pending"``["active", "pending"]`### Array Detection Logic

[](#array-detection-logic)

The `getValue()` method intelligently handles arrays:

1. **Detects native arrays**: Automatically recognizes `?param[]=value` format
2. **Splits comma-separated values**: Converts `?param=val1,val2` to array when requested
3. **Trims whitespace**: Automatically cleans `?param=val1, val2, val3`
4. **Maintains compatibility**: Returns string by default, array only when `$asArray = true`

Available Macros
----------------

[](#available-macros)

### whereLike

[](#wherelike)

Search across multiple columns or relationships with ease:

```
// Search in a single column
$users = User::whereLike(['name'], $searchTerm)->get();

// Search across multiple columns
$users = User::whereLike(['name', 'email'], $searchTerm)->get();

// Search in relationship columns
$users = User::whereLike(['name', 'posts.title', 'comments.body'], $searchTerm)->get();
```

**Example Filter:**

```
class Search extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        return $builder->whereLike(['name', 'email', 'phone'], $this->getValue());
    }
}
```

**Request:** `GET /users?search=john`

### whereDateBetween

[](#wheredatebetween)

Filter records between two dates with flexible formatting:

```
// Default format: d/m/Y
$users = User::whereDateBetween('created_at', '01/01/2023', '31/12/2023')->get();

// Custom date formats
$users = User::whereDateBetween('created_at', '01-01-2023', '31-12-2023', 'd-m-Y', 'Y-m-d')->get();

// Different formats for start and end dates
$users = User::whereDateBetween('created_at', '2023/01/01', '31-12-2023', 'Y/m/d', 'd-m-Y')->get();
```

**Example Filter:**

```
class DateRange extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        $dates = explode(',', $this->getValue());

        if (count($dates) === 2) {
            return $builder->whereDateBetween('created_at', $dates[0], $dates[1]);
        }

        return $builder;
    }
}
```

**Request:** `GET /users?date_range=01/01/2024,31/12/2024`

Advanced Usage
--------------

[](#advanced-usage)

### Price Range Filter

[](#price-range-filter)

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class PriceRange extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        $range = $this->getValue(true); // Get as array

        if (count($range) === 2) {
            return $builder->whereBetween('price', [$range[0], $range[1]]);
        }

        return $builder;
    }
}
```

**Usage:**

```
GET /products?price_range=10,100
GET /products?price_range[]=10&price_range[]=100

```

### Sort Filter

[](#sort-filter)

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class Sort extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        $sortBy = $this->getValue(); // e.g., "price:desc" or "name:asc"

        [$column, $direction] = array_pad(explode(':', $sortBy), 2, 'asc');

        return $builder->orderBy($column, $direction);
    }
}
```

**Usage:**

```
GET /products?sort=price:desc
GET /products?sort=name:asc

```

### Active Records Filter

[](#active-records-filter)

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class Active extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        $isActive = filter_var($this->getValue(), FILTER_VALIDATE_BOOLEAN);

        return $builder->where('is_active', $isActive);
    }
}
```

**Usage:**

```
GET /users?active=true
GET /users?active=1

```

### Relationship Filter

[](#relationship-filter)

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class HasPosts extends Filter
{
    protected function applyFilter(Builder $builder): Builder
    {
        $hasPosts = filter_var($this->getValue(), FILTER_VALIDATE_BOOLEAN);

        return $hasPosts
            ? $builder->has('posts')
            : $builder->doesntHave('posts');
    }
}
```

**Usage:**

```
GET /users?has_posts=true

```

Using Filters Outside HTTP Requests
-----------------------------------

[](#using-filters-outside-http-requests)

### Overview

[](#overview-1)

Filters can be used in **Jobs**, **Commands**, **Tests**, and other non-HTTP contexts by injecting data manually through the constructor.

### Manual Data Injection

[](#manual-data-injection)

Instead of relying on HTTP request parameters, you can pass data directly to filters:

```
namespace App\Jobs;

use App\Models\Order;
use App\Filters\Status;
use App\Filters\DateRange;

class ProcessOrdersJob
{
    public function handle()
    {
        // Manual data injection
        $orders = Order::queryFilter([
            new Status(['status' => 'pending,processing']),
            new DateRange(['date_range' => '01/01/2024,31/12/2024']),
        ])->get();

        // Process orders...
    }
}
```

### Console Commands

[](#console-commands)

```
namespace App\Console\Commands;

use App\Models\User;
use App\Filters\Status;
use App\Filters\Role;
use Illuminate\Console\Command;

class ExportUsersCommand extends Command
{
    protected $signature = 'users:export {status} {role}';

    public function handle()
    {
        $users = User::queryFilter([
            new Status(['status' => $this->argument('status')]),
            new Role(['role' => $this->argument('role')]),
        ])->get();

        // Export users...
    }
}
```

**Usage:**

```
php artisan users:export active admin
```

### Unit Tests

[](#unit-tests)

```
namespace Tests\Unit;

use App\Models\Product;
use App\Filters\PriceRange;
use App\Filters\Categories;
use Tests\TestCase;

class ProductFilterTest extends TestCase
{
    public function test_filters_products_by_price_and_category()
    {
        $products = Product::queryFilter([
            new PriceRange(['price_range' => '100,500']),
            new Categories(['categories' => '1,2,3']),
        ])->get();

        $this->assertCount(5, $products);
    }
}
```

### Mixed Usage (HTTP + Manual)

[](#mixed-usage-http--manual)

You can combine HTTP request parameters with manual data injection:

```
// In Controller
// GET /products?search=laptop

public function index()
{
    $products = Product::queryFilter([
        SearchFilter::class, // Takes 'search' from HTTP request
        new PriceRange(['price_range' => '100,1000']), // Manual data
        new Stock(['stock' => 'in_stock']), // Manual data
    ])->paginate(10);
}
```

### Queue Jobs Example

[](#queue-jobs-example)

```
namespace App\Jobs;

use App\Models\Notification;
use App\Filters\Status;
use App\Filters\Priority;

class SendNotificationsJob implements ShouldQueue
{
    public function handle()
    {
        $notifications = Notification::queryFilter([
            new Status(['status' => 'pending']),
            new Priority(['priority' => 'high,urgent']),
        ])->get();

        foreach ($notifications as $notification) {
            // Send notification...
        }
    }
}
```

### Scheduled Tasks

[](#scheduled-tasks)

```
namespace App\Console\Kernel;

use App\Models\Order;
use App\Filters\Status;
use App\Filters\DateRange;
use Carbon\Carbon;

protected function schedule(Schedule $schedule)
{
    $schedule->call(function () {
        $yesterday = Carbon::yesterday()->format('d/m/Y');
        $today = Carbon::today()->format('d/m/Y');

        $orders = Order::queryFilter([
            new Status(['status' => 'completed']),
            new DateRange(['date_range' => "$yesterday,$today"]),
        ])->get();

        // Process completed orders...
    })->daily();
}
```

### How It Works

[](#how-it-works)

The filter automatically detects the data source:

1. **HTTP Request Context**: If no data is provided, filters read from HTTP request parameters
2. **Manual Data Context**: If data is provided via constructor, filters use that data
3. **Priority**: Manual data takes precedence over HTTP request parameters

```
// HTTP Request (automatic)
StatusFilter::class → reads from request()->get('status')

// Manual Data (explicit)
new StatusFilter(['status' => 'active']) → uses provided data

// The filter name must match the array key
new Status(['status' => 'active']) → ✅ Correct
new Status(['state' => 'active'])  → ❌ Won't work (key mismatch)
```

Custom Filter Names
-------------------

[](#custom-filter-names)

Override the default snake\_case naming convention by setting a custom `$name` property:

```
namespace App\Filters;

use Samushi\QueryFilter\Filter;
use Illuminate\Database\Eloquent\Builder;

class Search extends Filter
{
    protected ?string $name = 'q'; // Use 'q' instead of 'search'

    protected function applyFilter(Builder $builder): Builder
    {
        return $builder->whereLike(['name', 'email'], $this->getValue());
    }
}
```

**Usage:**

```
GET /users?q=john  // Instead of ?search=john

// Or with manual data:
new Search(['q' => 'john']) // Must use 'q', not 'search'

```

Best Practices
--------------

[](#best-practices)

### 1. **Organize Filters by Feature**

[](#1-organize-filters-by-feature)

```
app/
├── Filters/
│   ├── User/
│   │   ├── UserSearch.php
│   │   ├── UserStatus.php
│   │   └── UserRole.php
│   ├── Product/
│   │   ├── ProductCategory.php
│   │   ├── ProductPrice.php
│   │   └── ProductStock.php

```

### 2. **Use Type Hints and Return Types**

[](#2-use-type-hints-and-return-types)

```
protected function applyFilter(Builder $builder): Builder
{
    return $builder->where('status', $this->getValue());
}
```

### 3. **Validate Input in Filters**

[](#3-validate-input-in-filters)

```
protected function applyFilter(Builder $builder): Builder
{
    $statuses = $this->getValue(true);
    $allowed = ['active', 'pending', 'completed'];

    $validated = array_intersect($statuses, $allowed);

    return $builder->whereIn('status', $validated);
}
```

### 4. **Combine Multiple Filters**

[](#4-combine-multiple-filters)

```
$users = User::queryFilter([
    Search::class,
    Status::class,
    Role::class,
    DateRange::class,
])->paginate(10);
```

**Request:**

```
GET /users?search=john&status=active,pending&role=admin&date_range=01/01/2024,31/12/2024

```

### 5. **Use Arrays for Multiple Values**

[](#5-use-arrays-for-multiple-values)

Always use `getValue(true)` when filtering by multiple values:

```
// ✅ Good
$categories = $this->getValue(true);
return $builder->whereIn('category_id', $categories);

// ❌ Bad
$categories = explode(',', $this->getValue());
return $builder->whereIn('category_id', $categories);
```

### 6. **Handle Empty Values Gracefully**

[](#6-handle-empty-values-gracefully)

The filter automatically skips when the parameter is missing or empty, but you can add custom logic:

```
protected function applyFilter(Builder $builder): Builder
{
    $value = $this->getValue();

    if (empty($value)) {
        return $builder; // Skip filter
    }

    return $builder->where('status', $value);
}
```

License
-------

[](#license)

The MIT License (MIT). Please see the [License File](LICENSE) for more information.

###  Health Score

49

—

FairBetter than 95% of packages

Maintenance79

Regular maintenance activity

Popularity25

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity69

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~201 days

Recently: every ~76 days

Total

9

Last Release

112d ago

Major Versions

v1.0.6 → v2.02025-03-26

### Community

Maintainers

![](https://www.gravatar.com/avatar/693640930be59315f6ebfcf1999895942cd129ac7d86b25ea12f721a9d976ef3?d=identicon)[samushi](/maintainers/samushi)

---

Top Contributors

[![samushi](https://avatars.githubusercontent.com/u/3842345?v=4)](https://github.com/samushi "samushi (16 commits)")

---

Tags

api-filterdatabasedatabase-queryeloquenteloquent-filterfilterfilteringlaravellaravel-filteringlaravel-ormlaravel-packagelaravel-query-builderormpaginationphpquery-builderquery-filterrequest-filtersearchsorting

### Embed Badge

![Health badge](/badges/samushi-queryfilter/health.svg)

```
[![Health](https://phpackages.com/badges/samushi-queryfilter/health.svg)](https://phpackages.com/packages/samushi-queryfilter)
```

###  Alternatives

[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k43.5M5.2k](/packages/larastan-larastan)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[jerome/filterable

Streamline dynamic Eloquent query filtering with seamless API request integration and advanced caching strategies.

19226.1k](/packages/jerome-filterable)[dragon-code/migrate-db

Easy data transfer from one database to another

15717.4k](/packages/dragon-code-migrate-db)[ntanduy/cloudflare-d1-database

Easy configuration and setup for D1 Database connections in Laravel.

215.4k](/packages/ntanduy-cloudflare-d1-database)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
