PHPackages                             architools/laravel-sieve - 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. architools/laravel-sieve

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

architools/laravel-sieve
========================

Laravel Sieve is a lightweight yet powerful filtering and sorting engine for Laravel applications. It transforms HTTP query parameters into clean, maintainable Eloquent or Query Builder queries with zero boilerplate. Built to follow SOLID and DRY principles, this modular package supports dynamic filtering, custom sorting, relationship constraints, and JSON field queries. Ideal for RESTful APIs, admin panels, and data-intensive applications, it keeps your controllers slim and your queries expressive — all while embracing Laravel best practices.

v1.0.0(12mo ago)1466MITPHPPHP &gt;=8.2

Since May 10Pushed 12mo ago1 watchersCompare

[ Source](https://github.com/MuhammadReda97/Laravel-Sieve)[ Packagist](https://packagist.org/packages/architools/laravel-sieve)[ RSS](/packages/architools-laravel-sieve/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (4)Versions (2)Used By (0)

Introduction
============

[](#introduction)

**Laravel Sieve** is a **modular**, **scalable** filtering engine designed to keep your codebase clean, maintainable, and easy to extend. It eliminates **bloated controllers** and **scattered if statements** with a structured, composable approach to dynamic, request-driven query filtering. By isolating filter logic into well-defined, **single-responsibility** classes, it enforces true **separation of concerns** while staying fully aligned with **SOLID** and **DRY** principles.

Whether you're building RESTful APIs, admin panels, or data-intensive applications, **Laravel Sieve** fits right in — offering the flexibility and structure needed for modern Laravel projects:

🧱 Decouple business logic with focused, single-responsibility filter classes.

🔄 Easily extend and customize filters to meet your unique requirements.

🧠 Compose complex query conditions effortlessly — even across nested relationships

↕️ Apply multiple sorts dynamically and cleanly, directly from request input

⚙️ Seamlessly integrate into your existing Laravel codebase with minimal setup

Designed for scalability, and developer satisfaction.

Requirements
============

[](#requirements)

- PHP &gt;= 8.2
- Laravel &gt;= 11.0

Installation
============

[](#installation)

1. Install the package via Composer:

```
composer require architools/laravel-sieve
```

Table of Contents
=================

[](#table-of-contents)

- [Getting Started](#getting-started)
    - [Creating a Custom Utilities Service](#create-a-custom-utilities-service)
    - [Defining Filters](#defining-filters)
    - [Reusable Filter Classes](#reusable-filter-classes)
    - [Defining Sorts](#defining-sorts)
- [Using the Utilities Service](#using-the-utilities-service)
- [Building the Query](#building-the-query)
- [Components](#components)
    - [Utilities Service](#utilities-service)
    - [Criteria](#criteria)
    - [Conditions](#condition)
    - [Joins](#joins)
    - [Sorts](#sorts)
- [Contact &amp; Feedback](#-contact-and-feedback)

---

Getting Started
===============

[](#getting-started)

Create a Custom Utilities Service
---------------------------------

[](#create-a-custom-utilities-service)

To begin, define your own service class that extends the base `UtilitiesService` class:

```
namespace App\Utilities;

use ArchiTools\LaravelSieve\UtilitiesService;

class MyUtilitiesService extends UtilitiesService
{
}
```

---

Defining Filters
----------------

[](#defining-filters)

Define available filters inside the `filters()` method of your service class. Each filter is a `key-value` pair where:

- The **key** represents the query parameter name.
- The **value** is either a method name to handle the filter implementation or a `Filter` instance.

```
namesapce App\Utilities;

use ArchiTools\LaravelSieve\UtilitiesService;
use ArchiTools\LaravelSieve\Criteria;
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\Condition;

class ProductUtilitiesService extends UtilitiesService
{
    public function filters(): array
    {
        return [
            'product_name' => 'productNameFilter',
        ];
    }

    public function productNameFilter(Criteria $criteria, mixed $value): void
    {
        $criteria->appendCondition(new Condition('products.name', 'like', "%$value%"));
    }
}
```

Each filter method receives two arguments:

`Criteria $criteria`: The criteria instance for appending conditions &amp; joins.

`mixed $value`: The filter value from the request.

🔎 For more on how Criteria works, [see the Criteria section](#criteria)

🔒 **Tip:**Always validate filter values using Form Requests, or another approach you prefer.

📘 [**Explore Available Conditions →**](#Conditions)

### Applying Joins in Filters

[](#applying-joins-in-filters)

Need to use Join's in filters? No problem — just append them inside your filter method:

```
use ArchiTools\LaravelSieve\Filters\Joins\Concretes\Join;
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\Condition;

$criteria->appendJoin(
    new Join('product_categories', 'categories.id', '=', 'products.category_id')
);

$criteria->appendCondition(
    new Condition('product_categories.name', 'like', "%$value%")
);
```

You can even attach conditions directly to the join:

```
use ArchiTools\LaravelSieve\Filters\Joins\Concretes\Join;
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\Condition;

$criteria->appendJoin(
    (new Join('product_categories', 'categories.id', '=', 'products.category_id'))
        ->appendCondition(new Condition('product_categories.is_active', '=', 1))
        ->appendCondition(...)
);
```

✅ **Best Practice**: To avoid overwriting joins when reusing the same one in multiple filters, always check if it already exists:

```
use ArchiTools\LaravelSieve\Filters\Joins\Concretes\Join;

if (!$criteria->joinExists('product_categories')) {
    $criteria->appendJoin(new Join('product_categories', 'categories.id', '=', 'products.category_id'));
}
```

🔒 **Important:**Appending a join with an existing name will overwrite the previous one.

#### Example with Multiple Filters Using Joins

[](#example-with-multiple-filters-using-joins)

```
namespace App\Utilities;

use ArchiTools\LaravelSieve\Filters\Joins\Concretes\Join;
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\{Condition, AggregationCondition};
use ArchiTools\LaravelSieve\UtilitiesService;
use ArchiTools\LaravelSieve\Criteria;

class MyUtilitiesService extends UtilitiesService
{
    public function filters(): array
    {
        return [
            'category_name' => 'categoryNameFilter',
            'has_multiple_categories' => 'multipleCategoriesFilter',
        ];
    }

    public function categoryNameFilter(Criteria $criteria, mixed $value)
    {
        if (!$criteria->joinExists('product_categories')) {
            $criteria->appendJoin(new Join('product_categories', 'categories.id', '=', 'products.category_id'));
        }

        $criteria->appendCondition(new Condition('product_categories.name', 'like', "%$value%"));
    }

    public function multipleCategoriesFilter(Criteria $criteria, mixed $value)
    {
        if (!$value) return;

        if (!$criteria->joinExists('product_categories')) {
            $criteria->appendJoin(new Join('product_categories', 'categories.id', '=', 'products.category_id'));
        }

        $criteria->appendCondition(new AggregationCondition('COUNT(product_categories.id)', '>', 1));
    }
}
```

📘 [**Explore Available Joins →**](#joins)

---

Reusable Filter Classes
-----------------------

[](#reusable-filter-classes)

You can reuse common filters across services by implementing the `Filter` interface:

```
namespace App\Utilities\Filters;

use ArchiTools\LaravelSieve\Filters\Contracts\Filter;
use ArchiTools\LaravelSieve\Criteria;
use ArchiTools\LaravelSieve\Filters\Joins\Concretes\Join;
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\Condition;

class ProductCategoryNameFilter implements Filter
{
    public function apply(Criteria $criteria, mixed $value)
    {
        if (!$criteria->joinExists('product_categories')) {
            $criteria->appendJoin(new Join('product_categories', 'categories.id', '=', 'products.category_id'));
        }
        $criteria->appendCondition(new Condition('product_categories.name', 'like', "%$value%"));
    }
}
```

To apply a reusable filter class in your service, simply register it in the `filters()` method:

```
namespace App\Utilities;

use ArchiTools\LaravelSieve\UtilitiesService;
use App\Utilities\Filters\ProductCategoryNameFilter;

class ProductUtilitiesService extends UtilitiesService
{
    public function filters(): array
    {
        return [
            'category_name' => new ProductCategoryNameFilter(),
        ];
    }
}
```

📌 **Note:** If you need to reuse the same filter logic but apply it to `different columns`, consider modifying the filter class to accept a column name and use it dynamically in the `apply()` method.

You can reuse it in other services too:

```
namespace App\Utilities;

use ArchiTools\LaravelSieve\UtilitiesService;
use App\Utilities\Filters\ProductCategoryNameFilter;

class DashboardUtilitiesService extends UtilitiesService
{
    public function filters(): array
    {
        return [
            'category' => new ProductCategoryNameFilter(),
        ];
    }
}
```

🔒 **Important:**📌 Make sure your filter class implements the `Filter` interface and its `apply()` method.
---------------------------------------------------------------------------------------------------------

[](#-important-make-sure-your-filter-class-implements-the-filter-interface-and-its-apply-method)

Defining Sorts
--------------

[](#defining-sorts)

Define available sorts inside the `sorts()` method of your service class. Each sort is a `key-value` pair where:

- The **key** represents the sort name.
- The value is either:
    - The name of a `method` that returns a `BaseSort` instance.
    - A string representing a `column name` or `alias`, which will be used directly for sorting

```
namespace App\Utilities;

use ArchiTools\LaravelSieve\UtilitiesService;
use ArchiTools\LaravelSieve\Sorts\Concretes\RawSort;
use ArchiTools\LaravelSieve\Sorts\Contracts\BaseSort;

class OrderUtilitiesService extends UtilitiesService
{
    public function sorts(): array
    {
        return [
            'name' => 'customNameSort',
            'created_at' => 'orders.created_at',
        ];
    }

    public function customNameSort(string $direction): BaseSort
    {
        return new RawSort('LENGTH(products.name)', $direction);
    }
}
```

🔒 **Note:**Sorts query parameters are expected in the following format:

```
/products?sorts[0][field]=name&sorts[1][field]=created_at&sorts[1][direction]=desc

```

- The `direction` key is optional (`ASC` is default).
- The sorts key can be customized by overriding the `$sortsKey` property:

```
namespace App\Utilities;

use ArchiTools\LaravelSieve\UtilitiesService;

class MyUtilitiesService extends UtilitiesService
{
    protected string $sortsKey = 'your_custom_sort_key';
    ...
}
```

📘 [**Explore Available Sorts →**](#sorts)

---

Using the Utilities Service
===========================

[](#using-the-utilities-service)

**Inject the service into your controller and apply filters and sorts:**

```
use App\Utilities\ProductUtilitiesService;

class ProductController extends Controller
{
    public function index(Request $request, ProductUtilitiesService $utilitiesService)
    {
        $criteria = $utilitiesService
            ->applyFilters()
            ->applySorts()
            ->getCriteria();

        // Use the criteria in your repository or query builder
    }
}

```

---

Building the Query
==================

[](#building-the-query)

**Utilize the `Criteria` object to modify the`builder` instance**

```
use ArchiTools\LaravelSieve\Criteria;

class ProductRepository
{
    public function getProducts(Criteria $criteria)
    {
        $query = Product::query();
        $criteria->applyOnBuilder($query);

        return $query->get();
    }
}
```

> ✅ `applyOnBuilder()` can be used anywhere you're building queries—not just limited repositories.

Components
==========

[](#components)

Utilities Service
-----------------

[](#utilities-service)

The `UtilitiesService` class is an abstract base class that provides a structured way to handle filtering and sorting in your Laravel applications. It acts as a bridge between HTTP requests and the Criteria class, making it easy to implement filtering and sorting functionality in your services.

### Purpose

[](#purpose)

The `UtilitiesService` class serves as a foundation for building filterable and sortable services. It allows you to:

- Automatically process filter and sort parameters from HTTP requests.
- Define available filters and sorts for your service.
- Apply filters and sorts to your queries in a consistent way.

### Available Methods

[](#available-methods)

#### `getCriteria(): Criteria`

[](#getcriteria-criteria)

- **Returns**: The current Criteria instance

#### `fresh(): UtilitiesService`

[](#fresh-utilitiesservice)

Initializes a fresh `Criteria` instance and resets the internal state of the service.

- **Returns**: The current service instance

#### `applyFilters(): UtilitiesService`

[](#applyfilters-utilitiesservice)

Applies all valid filters from the request to the Criteria instance.

- **Returns**: The current service instance

#### `applySorts(): UtilitiesService`

[](#applysorts-utilitiesservice)

Applies all valid sorts from the request to the Criteria instance.

- **Returns**: The current service instance

### Protected Methods to Override

[](#protected-methods-to-override)

#### `filters(): array`

[](#filters-array)

Define the available filters for your service.

- **Returns**: An associative array where each `key` is the filter name, and the `value` is either a `filter instance`or the name of a `method name` that implements the filter.

```
use App\Utilities\Filters\ProductCategoryNameFilter;

protected function filters(): array
{
    return [
        'category_name' => new ProductCategoryNameFilter(),
        'date_range' => 'applyDateRangeFilter',
    ];
}
```

#### `sorts(): array`

[](#sorts-array)

Define the available sorts for your service.

- **Returns**: An associative array where each key is the sort name used in requests, and the value is either:
    - `field/column` name to sort by directly.
    - `method name` that returns a BaseSort instance for custom logic.

```
protected function sorts(): array
{
    return [
        'created_at' => 'created_at',
        'name' => 'users.name',
        'custom_sort' => 'applyCustomSort'
    ];
}
```

### Usage Examples

[](#usage-examples)

#### Basic Implementation

[](#basic-implementation)

```
use ArchiTools\LaravelSieve\UtilitiesService;
use ArchiTools\LaravelSieve\Criteria;
use Illuminate\Http\Request;
use App\Utilities\Filters\{StatusFilter, RoleFilter};
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\Condition;

class UserService extends UtilitiesService
{
    protected string $defaultSortDirection = 'DESC';

    protected function filters(): array
    {
        return [
            'status' => new StatusFilter(),
            'role' => new RoleFilter(),
            'search' => 'applySearchFilter'
        ];
    }

    protected function sorts(): array
    {
        return [
            'name' => 'users.name',
            'email' => 'users.email',
            'created_at' => 'users.created_at'
        ];
    }

    protected function applySearchFilter(Criteria $criteria, string $value): void
    {
        $criteria->appendCondition(new Condition('name', 'like', "%{$value}%"));
    }
}
```

#### Complex Implementation

[](#complex-implementation)

```
use ArchiTools\LaravelSieve\UtilitiesService;
use ArchiTools\LaravelSieve\Criteria;
use Illuminate\Http\Request;
use App\Utilities\Filters\{
    StatusFilter,
    CategoryFilter,
    TagsFilter};
use ArchiTools\LaravelSieve\Filters\Joins\Concretes\Join;
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\{
    Condition,
    BetweenCondition
};
use ArchiTools\LaravelSieve\Filters\Sorts\Concretes\RawSort;

class PostService extends UtilitiesService
{
    protected string $defaultSortDirection = 'DESC';
    protected string $sortsKey = 'order_by'; // Customize the sort parameter key

    protected function filters(): array
    {
        return [
            'status' => new StatusFilter,
            'category' => new CategoryFilter,
            'date_range' => 'applyDateRangeFilter',
            'author' => 'applyAuthorFilter',
            'tags' => new TagsFilter
        ];
    }

    protected function sorts(): array
    {
        return [
            'created_at' => 'posts.created_at',
            'title' => 'posts.title',
            'views' => 'applyViewsSort',
            'popularity' => 'applyPopularitySort'
        ];
    }

    protected function applyDateRangeFilter(Criteria $criteria, array $value): void
    {
        $criteria->appendCondition(new BetweenCondition(
            'created_at',
            [$value['start'], $value['end']]
        ));
    }

    protected function applyAuthorFilter(Criteria $criteria, int $authorId): void
    {
        $criteria->appendJoin(new Join('users', 'users.id', '=', 'posts.user_id', 'inner'));
        $criteria->appendCondition(new Condition('user_id', '=', $authorId));
    }

    protected function applyViewsSort(string $direction): BaseSort
    {
        return new RawSort('views_count + likes_count', $direction);
    }

    protected function applyPopularitySort(string $direction): BaseSort
    {
        return new RawSort('(views_count * ?) + (likes_count * ?)', $direction, [
            0.5, // Weight for views
            1.5  // Weight for likes
        ]););
    }
}
```

### Best Practices

[](#best-practices)

- Create dedicated filter classes for complex or frequently reused filters.
- Always validate filter and sort parameters to ensure data integrity.
- Be mindful of security risks when handling user-provided input.
- Keep filtering and sorting logic clean, focused, and limited to a single responsibility.

---

Criteria
--------

[](#criteria)

The `Criteria` class is the main orchestrator of the Laravel Sieve package. It manages and applies joins, conditions, and sorts to your query builder in a structured and organized way.

### Purpose

[](#purpose-1)

The Criteria class serves as a container and manager for all query modifications. It allows you to:

- Build complex queries by combining multiple conditions, joins, and sorts
- Maintain a clean and organized query building process
- Apply all modifications to your query builder in the correct order
- Manage the relationships between different query components

### Available Methods

[](#available-methods-1)

#### `appendJoin(BaseJoin $join, int $sort = 100): Criteria`

[](#appendjoinbasejoin-join-int-sort--100-criteria)

Adds a join to the criteria with an optional sort order.

- `$join`: The join instance to add
- `$sort`: The order in which the join should be applied (lower numbers are applied first). Defaults to 100
- **Returns:** same `Criteria` instance

#### `appendSort(BaseSort $sort): Criteria`

[](#appendsortbasesort-sort-criteria)

Adds a sort to the criteria.

- `$sort`: The sort instance to add
- **Returns:** same `Criteria` instance

#### `removeJoinIfExists(string $joinName): Criteria`

[](#removejoinifexistsstring-joinname-criteria)

Removes a join from the criteria if it exists.

- `$joinName`: The name of the join to remove
- **Returns:** same `Criteria` instance

#### `joinExists(string $joinName): bool`

[](#joinexistsstring-joinname-bool)

Checks if a join exists in the criteria.

- `$joinName`: The name of the join to check
- **Returns:** `true` if the join exists, `false` otherwise

#### `appendCondition(BaseCondition $condition): Criteria`

[](#appendconditionbasecondition-condition-criteria)

Adds a condition to the criteria.

- `$condition`: The condition instance to add
- **Returns:** same `Criteria` instance

#### `applyOnBuilder(Builder $builder): Builder`

[](#applyonbuilderbuilder-builder-builder)

Applies all joins, conditions, and sorts to the query builder in the correct order.

- `$builder`: The query builder instance to modify
- **Returns:** The modified query builder

### Example

[](#example)

```
use ArchiTools\LaravelSieve\Criteria;
use ArchiTools\LaravelSieve\Joins\Concretes\Join;
use ArchiTools\LaravelSieve\Conditions\Concretes\{
    Condition,
    BetweenCondition,
    InCondition,
    GroupConditions
};
use ArchiTools\LaravelSieve\Sorts\Concretes\Sort;

$criteria = new Criteria();

// Add multiple joins with different priorities
$criteria->appendJoin(new Join('users', 'users.id', '=', 'posts.user_id', 'inner'), 100);
$criteria->appendJoin(new Join('categories', 'categories.id', '=', 'posts.category_id', 'left'), 200);

// Add complex conditions
$criteria->appendCondition(new Condition('status', '=', 'active'));
$criteria->appendCondition(new BetweenCondition('created_at', ['2023-01-01', '2023-12-31']));
$criteria->appendCondition(new InCondition('category_id', [1, 2, 3]));

// Add grouped conditions
$groupConditions = new GroupConditions([
    new Condition('views', '>', 1000),
    new Condition('likes', '>', 100, 'or')
]);
$criteria->appendCondition($groupConditions);

// Add multiple sorts
$criteria->appendSort(new Sort('created_at', 'DESC'));
$criteria->appendSort(new Sort('views', 'DESC'));

// Apply to a query builder
$query = DB::table('posts');
$query = $criteria->applyOnBuilder($query);

// The resulting query will be equivalent to:
// SELECT * FROM posts
// INNER JOIN users ON users.id = posts.user_id
// LEFT JOIN categories ON categories.id = posts.category_id
// WHERE status = 'active'
//   AND created_at BETWEEN '2023-01-01' AND '2023-12-31'
//   AND category_id IN (1, 2, 3)
//   AND (views > 1000 OR likes > 100)
// ORDER BY created_at DESC, views DESC
```

---

Conditions
----------

[](#conditions)

- [Basic Conditions](#basic-conditions)
- [JSON Conditions](#json-conditions)
- [Aggregation Conditions](#aggregation-conditions)
- [Group Conditions](#group-conditions)
- [Special Conditions](#special-conditions)

Basic Conditions
----------------

[](#basic-conditions)

### Condition

[](#condition)

**Purpose**: The fundamental building block for creating WHERE clauses in your queries. It handles basic comparison operations between a `field` and a `value`.

#### Parameters:

[](#parameters)

- `string $field`: The database column name to apply the condition on
- `string $operator`: The comparison operator (`=`, `!=`, ``, `>`, `=`, ``, `=`, `languages contains 'en'
$condition = new JsonContainCondition('preferences->languages', 'en');

// Exclude users whose roles contain 'admin'
$condition = new JsonContainCondition('roles', 'admin', not: true);
```

### JsonContainsKeyCondition

[](#jsoncontainskeycondition)

**Purpose**: Verifies if a JSON object contains a specific key. Useful for checking the existence of properties in JSON data.

#### Parameters:

[](#parameters-4)

- `string $field`: The database column name to apply the condition on
- `string $boolean`: The logical operator (`AND` or `OR`) to use when combining with other conditions. Defaults to `AND`.
- `bool $not`: Indicates whether to negate the condition (`true` for NOT logic). Defaults to `false`

**Example**:

```
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\JsonContainsKeyCondition;

// Check if preferences has 'notifications' key
$condition = new JsonContainsKeyCondition('preferences->notifications');
```

### JsonLengthCondition

[](#jsonlengthcondition)

**Purpose**: Compares the length of a JSON array. Useful for filtering based on array size.

#### Parameters:

[](#parameters-5)

- `string $field`: The database column name to apply the condition on
- `string $operator`: The comparison operator (`=`, `!=`, ``, `>`, `=`, `languages has exactly 3 items
$condition = new JsonLengthCondition('preferences->languages', '=', 3);

// Exclude rows where 'settings' JSON object has an 'experimental' key
$condition = new JsonContainsKeyCondition('settings->experimental', not: true);
```

### JsonOverlapCondition

[](#jsonoverlapcondition)

**Purpose**: Checks if two JSON arrays have any elements in common. Useful for finding records with matching array elements.

#### Parameters:

[](#parameters-6)

- `string $field`: The database column name to apply the condition on
- `mixed $value`: The value to compare against
- `string $boolean`: The logical operator (`AND` or `OR`) to use when combining with other conditions. Defaults to `AND.
- `bool $not`: Indicates whether to negate the condition (`true` for NOT logic). Defaults to `false`

**Example**:

```
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\JsonOverlapCondition;

// Check if tags overlap with ['php', 'laravel']
$condition = new JsonOverlapCondition('tags', ['php', 'laravel']);

// Check if skills overlap with required skills
$condition = new JsonOverlapCondition('skills', ['php', 'mysql', 'redis']);

// Exclude rows where the roles array overlaps with ['admin', 'editor']
$condition = new JsonOverlapCondition('roles', ['admin', 'editor'], not: true);
```

Aggregation Conditions
----------------------

[](#aggregation-conditions)

### AggregationCondition

[](#aggregationcondition)

**Purpose**: Applies conditions on aggregated values (COUNT, SUM, AVG, etc.). Essential for filtering based on grouped data.

#### Parameters:

[](#parameters-7)

- `string $field`: The database column name to apply the condition on
- `string $operator`: The comparison operator (`=`, `!=`, ``, `>`, `=`, `subDays(30)),
        ], 'or'),
    ], 'and'),
    new GroupConditions([
        new Condition('email_verified', '=', true),
        new Condition('phone_verified', '=', true, 'or')
    ], 'and'),
], 'and');

// previous group will generate a query like this:
where (
        ("country" = 'US' or ("subscription_plan" = 'premium' and "last_login" >= '2025-01-01'))
        and
        ("email_verified" = 1 or "phone_verified" = 1)
      );
```

> **Note**
>
> The `GroupConditions` class does **not** support **mixing** different condition types. Specifically, you cannot combine `AggregationCondition` instances with standard (non-aggregation) `Condition` instances in the same group.
>
> If mixed condition types are provided, a `MixedGroupConditionException` will be thrown to enforce consistency and prevent ambiguous behavior.

Special Conditions
------------------

[](#special-conditions)

### BetweenCondition

[](#betweencondition)

**Purpose**: Creates a `BETWEEN` clause for range queries. Useful for filtering values within a specific range.

#### Parameters:

[](#parameters-9)

- `string $field`: The database column name to apply the condition on
- `array $values`: An array containing exactly two elements, representing the lower and upper bounds.
- `string $boolean`: The logical operator (`AND` or `OR`) to use when combining with other conditions. Defaults to `AND`.
- `bool $not`: Indicates whether to negate the condition (`true` for NOT logic). Defaults to `false`.

**Example**:

```
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\BetweenCondition;

// Filter products with price between 10 and 100
$condition = new BetweenCondition('price', [10,100]);

// Filter users with age between 18 and 65
$condition = new BetweenCondition('age', [18, 65]);

// Exclude orders with total between 500 and 1000
$condition = new BetweenCondition('total', [500, 1000], not: true);
```

> **Note**
>
> The `BetweenCondition` requires the value to be an **array containing exactly two elements** — representing the lower and upper bounds of the range for a `WHERE BETWEEN` comparison.
>
> If the provided array does not contain exactly two elements, an `InvalidArgumentException` will be thrown to ensure proper condition formatting.

### DateCondition

[](#datecondition)

**Purpose**: Specialized condition for date comparisons. Handles date formatting and comparison.

#### Parameters:

[](#parameters-10)

- `string $field`: The database column name to apply the condition on
- `string $operator`: The comparison operator (`=`, `!=`, ``, `>`, `=`, ``, `=`, `appendCondition(new Condition('categories.is_active', '=', true))
   ->appendCondition(new Condition('categories.type', '=', 'main'));
```

`apply(Builder $builder): void`

- Applies the join to the query builder
- Internal method used by the package

```
 $join->apply($queryBuilder);
```

**Example**:

```
use ArchiTools\LaravelSieve\Filters\Joins\Concretes\Join;
use ArchiTools\LaravelSieve\Filters\Conditions\Concretes\Condition;

// Basic join.
$join = new Join(
    'categories',
    'products.category_id',
    '=',
    'categories.id'
);

// Join with additional conditions.
$join = (new Join(
    'categories',
    'products.category_id',
    '=',
    'categories.id',
    'left'
))->appendCondition(
    new Condition('categories.is_active', '=', true)
);

// Join with custom name.
$join = new Join(
    'categories',
    'products.category_id',
    '=',
    'categories.id',
    'inner',
    'product_categories'
);

// Chaining multiple conditions.
$join = (new Join('categories', 'products.category_id', '=', 'categories.id'))
    ->appendCondition(new Condition('categories.is_active', '=', true))
    ->appendCondition(new Condition('categories.type', '=', 'main'))
    ->appendCondition(new Condition('categories.created_at', '>', '2024-01-01'));
```

### ClosureJoin

[](#closurejoin)

**Purpose**: Creates a join using a closure, allowing for more complex join conditions and logic.

Useful when you need to build dynamic or complex join conditions that can't be expressed with simple column comparisons.

#### Parameters:

[](#parameters-16)

- `string $table`: The table to join with
- `Closure $closure`: A closure that receives the join query builder and defines the join conditions
- `string $type`: The type of join to perform (`inner`, `left`, `right`). Defaults to `inner`.
- `string|null $name`: Optional name for the join. If not provided, the table name is used

**Public Methods**:

1. `apply(Builder $builder): void`

- Applies the join to the query builder
- Internal method used by the package

```
$join->apply($queryBuilder);
```

**Example**:

```
use ArchiTools\LaravelSieve\Filters\Joins\Concretes\ClosureJoin;

// Complex join with multiple conditions
$join = new ClosureJoin(
    'orders',
    function ($query) {
        $query->on('users.id', '=', 'orders.user_id')
              ->where('orders.status', '=', 'completed')
              ->where('orders.created_at', '>', now()->subDays(30));
    },
    'left'
);

// Join with custom name and type
$join = new ClosureJoin(
    'user_preferences',
    function ($query) {
        $query->on('users.id', '=', 'user_preferences.user_id')
              ->where('user_preferences.is_active', '=', true);
    },
    'left',
    'active_preferences'
);

// Complex join with multiple conditions and subqueries
$join = new ClosureJoin(
    'order_summary',
    function ($query) {
        $query->on('users.id', '=', 'order_summary.user_id')
              ->where('order_summary.total_orders', '>', function ($subquery) {
                  $subquery->selectRaw('AVG(total_orders)')
                          ->from('order_summary');
              })
              ->whereExists(function ($subquery) {
                  $subquery->select('id')
                          ->from('recent_orders')
                          ->whereColumn('user_id', 'users.id')
                          ->where('created_at', '>', now()->subDays(7));
              });
    }
);
```

> **Note**
>
> An `InvalidJoinTypeException` will be thrown if an unsupported join type is provided.

Best Practices for Joins
------------------------

[](#best-practices-for-joins)

**Join Naming and Execution Order**

When working with joins, it's important to assign clear and descriptive names—especially when joining the same table multiple times or referencing joins later in your logic.

**Named Joins**: If a join with the same name already exists, it will be overwritten.

**Execution Order**: You can control the order in which joins are applied using the `appendJoin(BaseJoin $join, int $ order = 100)` method. Joins with lower order values are executed first.

Using named and ordered joins helps maintain predictable and maintainable query structures, particularly in complex filtering scenarios.

---

Sorts
-----

[](#sorts)

The Laravel Sieve package provides two types of sorts to help you order your query results.

### Sort

[](#sort)

**Purpose**: Creates a standard `ORDER BY` clause for sorting query results. This is the most commonly used sort type, allowing you to sort by a specific column in ascending or descending order.

#### Parameters:

[](#parameters-17)

- `string $field`: The column name to sort by
- `string $direction`: The sort direction (`ASC` or `DESC`). Defaults to `ASC`.

**Available Methods**:

`apply(Builder $builder): void`

- Applies the sort to the query builder
- Internal method used by the package

```
$sort->apply($queryBuilder);
```

**Example**:

```
use ArchiTools\LaravelSieve\Sorts\Concretes\Sort;

// Sort by name in ascending order
$sort = new Sort('name', 'ASC');

// Sort by created_at in descending order
$sort = new Sort('created_at', 'desc');

// Sort by multiple fields
$sorts = [
    new Sort('status', 'asc'),
    new Sort('created_at', 'desc')
];
```

### RawSort

[](#rawsort)

**Purpose**: Creates a raw `ORDER BY` clause for complex sorting requirements. Useful when you need to use SQL expressions, functions, or complex sorting logic that can't be achieved with simple column sorting.

#### Parameters:

[](#parameters-18)

- `string $expression`: The raw SQL expression for sorting
- `string $direction`: The sort direction (`ASC` or `DESC`). Defaults to `ASC`.
- `array $bindings`: Array of values to bind to the expression. Defaults to empty array

**Available Methods**:

`apply(Builder $builder): void`

- Applies the raw sort to the query builder
- Internal method used by the package

```
$sort->apply($queryBuilder);
```

**Example**:

```
use ArchiTools\LaravelSieve\Sorts\Concretes\RawSort;

// Sort by a calculated field
$sort = new RawSort('(price * quantity)', 'DESC');

// Sort using a SQL function
$sort = new RawSort('LENGTH(name)', 'ASC');

// Sort with bindings
$sort = new RawSort('FIELD(status, ?, ?, ?)', 'ASC', ['active', 'pending', 'inactive']);

// Complex sorting with bindings
$sort = new RawSort('
    CASE
        WHEN status = ? THEN 1
        WHEN status = ? THEN 2
        ELSE 3
    END', 'ASC', ['active', 'pending']);
```

> 🔒**Important**: Always use parameter binding with `RawSort`

Best Practices for Sorts
------------------------

[](#best-practices-for-sorts)

**Raw Sorts Usage**:

- Custom SQL expressions
- Database functions in sorting
- Complex conditional sorting

---

### 📬 Contact and Feedback

[](#-contact-and-feedback)

We welcome your feedback and contributions!

- 🐛 **Bug Reports**: [Open an issue](https://github.com/MuhammadReda97/Laravel-Sieve/issues)
- 🌟 **Feature Requests**: [Submit a feature request](mailto:muhammadreda97@gmail.com?subject=Laravel%20Sieve%20Feature%20Request)
- 💬 **General Feedback or Questions**: Feel free to reach out via

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance52

Moderate activity, may be stable

Popularity16

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 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

Unknown

Total

1

Last Release

364d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/cf46c3e7061db6d931739f57c15388e8bc746bb879ec486763bd879c7b974583?d=identicon)[afghany](/maintainers/afghany)

---

Top Contributors

[![MuhammadReda97](https://avatars.githubusercontent.com/u/20626195?v=4)](https://github.com/MuhammadReda97 "MuhammadReda97 (39 commits)")

---

Tags

laravel-filters-laravel-sorting-clean-code-solidapilaraveleloquentlaravel-packagedashboardquery builderrestfulfiltersmodulardrysortingadmin-panellaravel-filtersdynamic-filterseloquent-filteringeloquent-filtersquery-optimizationquery parameterssoliditydatabase-querieslaravel sievedynamic filters laravelcustom sortingjson querieslaravel filter query parameterssort and filter laravel packagefilter eloquent relations dynamicallyfilterable Laravel modelsfilterable eloquent models

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/architools-laravel-sieve/health.svg)

```
[![Health](https://phpackages.com/badges/architools-laravel-sieve/health.svg)](https://phpackages.com/packages/architools-laravel-sieve)
```

###  Alternatives

[mehdi-fathi/eloquent-filter

Eloquent Filter adds custom filters automatically to your Eloquent Models in Laravel.It's easy to use and fully dynamic, just with sending the Query Strings to it.

450191.6k1](/packages/mehdi-fathi-eloquent-filter)[indexzer0/eloquent-filtering

Powerful eloquent filtering

22425.9k3](/packages/indexzer0-eloquent-filtering)[cerbero/query-filters

Filter Laravel Eloquent models based on query parameters.

85282.6k](/packages/cerbero-query-filters)

PHPackages © 2026

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