PHPackages                             jpnut/eloquent-nested-filter - 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. jpnut/eloquent-nested-filter

AbandonedArchivedLibrary

jpnut/eloquent-nested-filter
============================

A nested filter structure for Eloquent Models

0.1.1(5y ago)415MITPHPPHP ^7.4|^8.0

Since May 25Pushed 5y ago1 watchersCompare

[ Source](https://github.com/jpnut/eloquent-nested-filter)[ Packagist](https://packagist.org/packages/jpnut/eloquent-nested-filter)[ RSS](/packages/jpnut-eloquent-nested-filter/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (2)Dependencies (6)Versions (4)Used By (0)

Eloquent Nested Filter
======================

[](#eloquent-nested-filter)

[![Version](https://camo.githubusercontent.com/a546193e33680654fe4e2532a45730062e04c0cf38ef5b1ff190a0d2de90b82d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a706e75742f656c6f7175656e742d6e65737465642d66696c7465723f7374796c653d666c61742d737175617265)](https://packagist.org/packages/jpnut/eloquent-nested-filter)[![StyleCI](https://camo.githubusercontent.com/e355d23939de0f288756c373e68d59c5beda47769edd2816cef72c690146b8c5/68747470733a2f2f6769746875622e7374796c6563692e696f2f7265706f732f3236353631333834352f736869656c643f6272616e63683d6d6173746572)](https://styleci.io/repos/265613845)[![GitHub Workflow Status](https://camo.githubusercontent.com/bcca405c441ada7d809fb0e9462168c51de9e075732aaa212c6678b2c55b24ba/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f6a706e75742f656c6f7175656e742d6e65737465642d66696c7465722f72756e2d74657374733f7374796c653d666c61742d737175617265)](https://camo.githubusercontent.com/bcca405c441ada7d809fb0e9462168c51de9e075732aaa212c6678b2c55b24ba/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f6a706e75742f656c6f7175656e742d6e65737465642d66696c7465722f72756e2d74657374733f7374796c653d666c61742d737175617265)[![License](https://camo.githubusercontent.com/9622074d7e9d4b5059ab0f57ac2d8e33aa01cf22b2be6821c7796739c61c1892/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6a706e75742f656c6f7175656e742d6e65737465642d66696c7465723f7374796c653d666c61742d737175617265)](https://camo.githubusercontent.com/9622074d7e9d4b5059ab0f57ac2d8e33aa01cf22b2be6821c7796739c61c1892/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6a706e75742f656c6f7175656e742d6e65737465642d66696c7465723f7374796c653d666c61742d737175617265)

This package provides a way to quickly and succinctly define a nested filter structure for Eloquent models. This makes it easier to filter data in more complex ways based on user input.

For example, given a filter object defined as follows:

```
...

class ProductFilter extends AbstractFilter
{
    public ?StringFilterObject $name;
}
```

we can use this to filter our `Product` query:

```
    ...

    $filter = new ProductFilter([
        'or' => [
            [
                'name' => [
                    'value' => 'foo',
                    'operator' => 'BEGINS',
                ]
            ],
            [
                'name' => [
                    'value' => 'foo',
                    'operator' => 'ENDS',
                ]
            ],
        ]
    ]);

    $query = $filter->filter(Product::query());

    ...
```

which produces an sql query:

```
SELECT * FROM `products` WHERE (`name` LIKE 'foo%' or `name` LIKE '%foo')
```

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

[](#installation)

You can install the package via composer:

```
composer require jpnut/eloquent-nested-filter
```

Usage
-----

[](#usage)

To get started, you'll need to create a filter class for each model you wish to filter. This class should extend `JPNut\EloquentNestedFilter\AbstractFilter`. In this class, you should define all the filterable properties. This package ships with built in filters defined for primary keys, strings, numbers, booleans, and datetimes; though you are free to create and use any additional filters. It is also possible to filter relationships by including a property which refers to the filter class of a related model. Let's take a look at an example filter class:

```
// app/ProductFilter.php

namespace App;

...

class ProductFilter extends AbstractFilter
{
    public ?IDFilterObject $id = null;

    public ?StringFilterObject $name = null;

    public ?NumberFilterObject $amount = null;

    public ?BooleanFilterObject $in_stock = null;

    public ?DateFilterObject $created_at = null;

    /** @var \App\CategoryFilter[]|null */
    public ?array $category = null;
}
```

First of all, note that we are defining the filter type of each property through it's type declaration. For array filters such as the `category` property, we must specify the filter type in the doc block instead since php does not currently support generics. Every instance of `AbstractFilter` also has pre-defined `and` and `or` properties which expect an array of the parent class. These properties allow us to group filters together in the expected way.

Since manually constructing these objects would be tedious, the constructor takes a single associative array and will attempt to cast the filter properties based on the type information. Of course, if an instance of the correct object is passed, that object will be used. However, in the case that an array is passed, the class will instead attempt to build the correct array/object.

Let's re-use the example we defined at the start of this readme:

```
...

$filter = new ProductFilter([
    'or' => [
        [
            'name' => [
                'value' => 'foo',
                'operator' => 'BEGINS',
            ]
        ],
        [
            'name' => [
                'value' => 'foo',
                'operator' => 'ENDS',
            ]
        ],
    ]
]);

...
```

The `or` property is composed of an array of `ProductFilter` instances. In this case, we are passing through two associative arrays - these will automatically be casted to instances of `ProductFilter` by passing their value to the constructor of `ProductFilter`. The `name` filters are constructed in a similar way.

### Typical Request Workflow

[](#typical-request-workflow)

The main use case for this library is from user-input. Typically this means that the `filter` associative array will be supplied as a JSON encoded string. The filter can then be constructed by passing in the decoded value:

```
...

$filter = $request->has('filter')
    ? new ProductFilter(json_decode($request->query('filter'), true))
    : null;

...
```

Instances of `AbstractFilter` expose a `filter` method which expect a single argument - the base eloquent query. Typically this would be `Product::query()`, though it is of course possible to scope or manipulate the filter should you wish:

```
...

$results = $filter->query(Product::query()->withTrashed())->get();

...
```

### Custom Filter Objects

[](#custom-filter-objects)

You may wish to add new filter objects. To do so, simply create a new class which extends `JPNut\EloquentNestedFilter\AbstractFilterObject` or implements `JPNut\EloquentNestedFilter\Contracts\FilterObject`. Your class *must* define two methods:

```
abstract public function filter(string $name, Builder $query): Builder;

abstract public static function fromArray(array $properties = []): self;
```

The `filter` method takes two arguments - the name of the field being filtered, and the query instance - and should return the modified query instance. Typically the filter method should handle all valid `Operator` values (e.g. `IS`, `IS_NOT` etc.), and throw an exception if an invalid operator is used. For example:

```
    public function filter(string $name, Builder $query): Builder
    {
        if ($this->operator->equals(Operator::IS())) {
            return $query->where($name, $this->value);
        }

        if ($this->operator->equals(Operator::IS_NOT())) {
            return $query->where($name, '', $this->value);
        }

        ...

        throw $this->invalidOperator($this->operator);
    }
```

The `fromArray` method takes a single argument - the array of properties - and should return a new instance of the filter object. This method is called when creating the filter object from an associative array. This is a good way to validate or cast the filter value. For example, let's take a look at the `fromArray` function from the `NumberFilterObject`:

```
    public static function fromArray(array $properties = []): self
    {
        return new static(
            is_null($properties['value'] ?? null) ? null : floatval($properties['value']),
            static::operatorFromProperties($properties)
        );
    }
```

Here you can see we permit null values, and otherwise attempt to cast the value to a float. We also get an instance of the `Operator` enum based on the supplied `operator` key-value pair.

Validation / Query Complexity
-----------------------------

[](#validation--query-complexity)

Without validating or otherwise limiting the supplied query, it is possible for users to build very expensive queries, even without malicious intent. Presently, this library does **not** attempt to validate or calculate the complexity of queries. As an alternative, it is possible to set a maximum filter depth, and a maximum filter total. The depth refers to nested instances of `AbstractFilter`: for example, each nested `and` or `or` filter increments the depth by 1. The total number of filters is determined by the number of instances of `AbstractFitler` and `AbstractFilterObject`.

Returning to the example at the top of this file, we have a depth of 1 (since there is only 1 nested `AbstractFilter` instance, generated by the `or` statement) and a total filter number of 3 (1 for the `or` statement, and 1 for each of the `name` constraints). By default, the maximum depth is set to 10, and the maximum permitted filters to 100, though this can be modified by changing the default property values:

```
...

class ProductFilter extends AbstractFilter
{
    protected ?int $max_depth = 5;

    protected ?int $max_filters = 50;
}

...
```

In order to disable these limits, simply set the property value to null.

Testing
-------

[](#testing)

```
vendor/bin/phpunit
```

License
-------

[](#license)

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

###  Health Score

25

—

LowBetter than 37% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity10

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity53

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

Every ~161 days

Total

2

Last Release

2014d ago

PHP version history (2 changes)0.0.1PHP ^7.4

0.1.1PHP ^7.4|^8.0

### Community

Maintainers

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

---

Top Contributors

[![jpnut](https://avatars.githubusercontent.com/u/13661783?v=4)](https://github.com/jpnut "jpnut (23 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/jpnut-eloquent-nested-filter/health.svg)

```
[![Health](https://phpackages.com/badges/jpnut-eloquent-nested-filter/health.svg)](https://phpackages.com/packages/jpnut-eloquent-nested-filter)
```

###  Alternatives

[owen-it/laravel-auditing

Audit changes of your Eloquent models in Laravel

3.4k33.0M95](/packages/owen-it-laravel-auditing)[fumeapp/modeltyper

Generate TypeScript interfaces from Laravel Models

196277.9k](/packages/fumeapp-modeltyper)[casbin/laravel-authz

An authorization library that supports access control models like ACL, RBAC, ABAC in Laravel.

324339.9k4](/packages/casbin-laravel-authz)[illuminatech/balance

Provides support for Balance accounting system based on debit and credit principle

16137.4k](/packages/illuminatech-balance)[flowwow/cloudpayments-php-client

cloudpayments api client

2188.2k](/packages/flowwow-cloudpayments-php-client)[glhd/special

1929.4k](/packages/glhd-special)

PHPackages © 2026

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