PHPackages                             dowob/laravel-refiner - 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. dowob/laravel-refiner

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

dowob/laravel-refiner
=====================

Refine Laravel Eloquent queries from request data according to the refinement you choose.

v0.4.0(1y ago)0370MITPHPPHP ^8.0

Since Aug 20Pushed yesterday1 watchersCompare

[ Source](https://github.com/dowobdev/laravel-refiner)[ Packagist](https://packagist.org/packages/dowob/laravel-refiner)[ RSS](/packages/dowob-laravel-refiner/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (9)Versions (8)Used By (0)

Refine Laravel Eloquent queries
===============================

[](#refine-laravel-eloquent-queries)

The aim of the package is to provide simple yet flexible filtering to your Eloquent models from query parameters in the request. You create a refining class that extends `\Dowob\Refiner\Refiner` and specify the filter definitions that are applicable to that refiner. This allows refiners to be re-used throughout your application.

Please read through the [Caveats](#caveats) section to ensure this package is suitable for you. This was built to meet my needs which means it won't fit all use cases.

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

[](#installation)

Run the following to install the package with composer:

```
composer require dowob/laravel-refiner
```

Some minimal configuration options are available if you wish to publish the configuration file. The service provider will be registered automatically within Laravel.

```
php artisan vendor:publish --provider="Dowob\Refiner\RefinerServiceProvider" --tag="refiner-config"
```

Refinable Models
----------------

[](#refinable-models)

A model must use the trait `\Dowob\Refiner\Refinable` to enable refinement on the model.

```
use \Dowob\Refiner\Refinable;
use \Illuminate\Database\Eloquent\Model;

class Post extends Model {
    use Refinable;
    ...
}
```

### Refining a model

[](#refining-a-model)

You can then refine the query by calling `refine()`:

```
Post::refine()->get();
```

As `refine()` is a scope, it can be combined with any other calls on the model (scopes, query builder methods, pagination etc.).

The `refine()` method has two optional arguments:

1. `?\Dowob\Refiner\Refiner $refiner = null`
    if specified, this is the refiner instance you want to use for this refinement. If not specified, it will be automatically determined from the model name and the configured refiner namespace (defaults to `\App\Refiners`). For example, the guessed refiner `Post` would be `\App\Refiners\PostRefiner`.
2. `?\Illuminate\Http\Request $request = null`
    if specified, this will be used as the request object to retrieve query parameters from. Otherwise, we'll use the current request to retrieve the parameters.

Refiners
--------

[](#refiners)

A refiner defines what refinement is allowed (filters &amp; sorting). This is achieved by specifying **[Definitions](#definitions)** in a refiner. The refiner will be automatically used if it matches the naming convention of `ModelNameRefiner`, i.e. `PostRefiner`, unless you have specifically passed an instance of a refiner to the `refine() ` method.

A refiner is typically for a specific model, but you may want to create multiple refiners for a model too. For example, you may want to use a `UserPostRefiner` for when a user is viewing/filtering their specific posts, and you may have a `PostRefiner` that is for anyone to search any posts.

### Creating a Refiner

[](#creating-a-refiner)

Generate a refiner using the artisan command `php artisan make:refiner NameOfYourRefiner`.

Definitions
-----------

[](#definitions)

To make use of a refiner, you'll want to add one or more definitions to it in the `definitions()` method. Each definition must have a `name` (passed in the `make` method), but everything else is optional. The `name` is how the definition matches up to the request[1](#user-content-fn-1-f8041279d5f1b532cb6d02e318b8fe0f), for example a definition with a name of `email` would be triggered by a request like: `?search[email]=value`. The `name` will also be used for the query column, unless you define a different column through `column()`.

You must opt the Definition into search and/or sorting, which is done by calling one of the `search*` methods or the `sort` method.

You can specify [validation](#validation) rules for each definition which allows further complexity in how your definitions work.

Examples of definitions being defined within a refiner:

```
use \Dowob\Refiner\Definitions\Definition;
use \Dowob\Refiner\Enums\Like;
use \Illuminate\Support\Facades\DB;

...

public function definitions(): array
{
    return [
        // This allows exact (column = value) matching for `name` and enables sorting.
        // Note that any search that is a string will be trimmed by default.
        Definition::make('name')->search()->sort(),
        // You can disable trimming per-definition with trim(false)
        Definition::make('non-trimmed-name')->search()->trim(false),
        // This allows LIKE matching without sorting. By default, LIKE sorting will use Like::BOTH however,
        // you can override this by passing a Like::* value as the first parameter as shown in 2nd example.
        Definition::make('email')->searchLike(), // LIKE %value%
        Definition::make('email-match-after')->searchLike(Like::END), // LIKE value%
        // You can specify a different column name to use rather than the name used in the request parameter.
        // This also demonstrates support for WHERE column IN (...) type searches
        Definition::make('type')->column('account_type')->searchIn(),
        // If a pre-defined search option just doesn't cut it for you, you can specify a custom callback
        // that will be used for the search.
        Definition::make('full-name')->searchCustom(function (Builder $query, mixed $value) {
            $query->where(DB::raw('CONCAT(first_name, last_name)'), 'like', '%' . $value . '%');
        }),
        // It can also access any scopes etc. as you normally would be able to on the model
        Definition::make('model-scope')->searchCustom(function (Builder $query, mixed $value) {
            $query->aScopeOnTheModel($value);
        }),
        // You may want a search to always be applied, even if the search value isn't present in the query.
        // You can do this by specifying `alwaysRun` like so. Without the `alwaysRun`, this query would
        // not apply the `where('active', 1)` if there's no search for `active` present in the request!
        Definition::make('active')->alwaysRun()->searchCustom(function (Builder $query, mixed $value) {
            switch ($value) {
                case 'inactive':
                    $query->where('active', 0);
                    break;
                case 'all':
                    // No action, show both active & inactive
                    break;
                default:
                    // Reached either by no value being specified, or it being 'active'
                    // or anything that does not match the above
                    $query->where('active', 1);
            }
        }),
    ];
}
```

### Validation

[](#validation)

You can add validation rules to a definition by calling `validation($rules)`. By default, basic validation rules will be added automatically depending on how the definition is configured:

- `required` unless the definition is set to always run, in which case it would have `nullable`
- `array` if using a `searchIn()` search filter.

Validation rules use the power of Laravel's validation system:

```
use \Dowob\Refiner\Definitions\Definition;

// Three ways of specifying the validation rules that result in same validation
Definition::make('email')->search()->validation(['required', 'string', 'email']);
Definition::make('email')->search()->validation('required|string|email');
Definition::make('email')->search()->validation(['email' => ['required', 'string', 'email']);
```

If specifying a custom search handler via `searchCustom($closure)`, you can unlock more powerful use cases for search filters. For example, if you had a date filter that needed to rely on **two** fields (`start` and `end`) within one definition, you can do:

```
use \Dowob\Refiner\Definitions\Definition;
use \Illuminate\Contracts\Database\Query\Builder;

// A definition using a custom search that can handle multiple query values due to its validation rules.
Definition::make('date')
    ->validation([
        'start' => [
            'required',
            'date_format:Y-m-d',
        ],
        'end' => [
            'required',
            'date_format:Y-m-d',
        ],
    ])
    ->searchCustom(function (Builder $builder, mixed $value) {
        // $value will contain at least one of `start` or `end` if they passed validation
        if (empty($value['start'])) {
            return $query->where('created_at', '==', $value['start']);
        }

        // Value contains both 'start' and 'end'
        $query->whereBetween('created_at', $value);
    });
```

> Note: It's important to know that multiple fields will not work unless you're using `searchCustom` or `searchIn`, as array values are rejected for other search types to prevent array values being passed to methods expecting singular values (i.e. exact match search).

### Default sorts

[](#default-sorts)

You may want to apply default sorting to your query when there are no sorts specified in the request. This can be done by specifying an array within the `defaultSorts` method, containing one or more sorts to apply if no sorts are present.

```
use \Dowob\Refiner\Enums\Sort;

...

public function defaultSorts(): array
{
    return [
        // The first value in the array must match the relevant sort-enabled definition
        // The second value is the sort direction to use as default.
        ['last_name', Sort::ASC],
        ['first_name', SORT::ASC],
    ];
}
```

Refiner Registry
----------------

[](#refiner-registry)

When a refiner is created due to no refiner being passed to `refine()`, the package registers it in the registry for you to retrieve (if needed). If you don't need the refiner instance, you can ignore the registry. You may need the refiner instance if you wish to check what sorts are active in the current request (i.e. for inverting sort links based on current sort), or what searches are active &amp; the values that were used (i.e. for pre-filling forms), or using the `query()` method to retrieve the validated query parameters (i.e. for pagination `->appends()` method).

> Note: The refiner is a singleton, which means you may need to reset its state in long-running applications serving multiple requests like those running on Laravel Octane.

Refiners are added to the registry in the order they are registered via `refine()`, and when retrieving a refiner it is removed from the registry.

You can use the Facade to access the singleton: `\Dowob\Refiner\Facades\Registry`.

The below methods are the ones you'll need to know if using the registry.

```
use \Dowob\Refiner\Facades\Registry;

// Take the refiner from the end of the registry (last in)
Registry::pop();
// Take the refiner from front of the registry (first in)
Registry::shift();

// Example
User::refine();
Post::refine();
Registry::shift(); // returns the UserRefiner
Registry::shift(); // returns the PostRefiner, as we've already shifted the UserRefiner out of registry.
```

Both `pop` and `shift` accept two optional parameters, the class-name of a `Refiner` and the class-name of a `Model`. Passing either of these parameters will filter the `pop` or `shift` action to only refiners registered for that match those parameters specified.

Example of using optional parameters for registry methods:

```
// Example of registry setup in this order,
// each 'we can retrieve it by' assumes no prior models are retrieved in the example.
// 1st registered, 1st UserRefiner/User registered
User::refine();
// We can retrieve it by...
// - Registry::shift()
// - or Registry::shift(UserRefiner::class);
// - or Registry::shift(model: User::class);
//
// 2nd registered, 1st PostRefiner/Post registered
Post::refine();
// We can retrieve it by...
// - Registry::shift(PostRefiner::class)
// - or Registry::shift(model: Post::class);
//
// 3rd registered, 2nd UserRefiner/User registered
User::fine();
// We can retrieve it by...
// - Registry::pop(UserRefiner::class)
// - or Registry::pop(model: User::class);
//
// 4th registered, 2nd PostRefiner/Post registered
Post::fine();
// We can retrieve it by...
// - Registry::pop()
// - or Registry::pop(PostRefiner::class)
// - or Registry::pop(model: Post::class);
```

Caveats
-------

[](#caveats)

1. This package does **NOT** support filter combinations other than AND, unless they are within a custom search filter (but that would be AND'd with any other active filters).
2. Whilst multiple refiners in a single request is supported, if the definitions have conflicting names then you may get unexpected results (as both refiners would, if the data validates to their definitions, use them).
    1. Definition names within a refiner must be unique, this will likely be enforced in a later version.
3. If validating multiple fields in one definition, you must pass the definition name to `getSearchValue($name)`which will return an array of values for any fields that passed validation in that definition. You cannot call the fields by their name in `getSearchValue($name)` but could do something like `getSearchValue($name)[$field] ?? null`

TODO
----

[](#todo)

- Refining
    - search via basic relationship `whereHas` to reduce need for `searchCustom` callbacks that achieve the same result
        - potential grouping of same relationship queries for better performance

Footnotes
---------

1. Unless you use validation rules that cover multiple fields, then those field names will be used instead of the definition name. [↩](#user-content-fnref-1-f8041279d5f1b532cb6d02e318b8fe0f)

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance77

Regular maintenance activity

Popularity14

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity47

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 ~169 days

Recently: every ~254 days

Total

7

Last Release

388d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/82f29f7e694e963877817e2ef95424fa42cd897bf75e899109fe4a518d58ec8a?d=identicon)[dowob](/maintainers/dowob)

---

Top Contributors

[![dowobdev](https://avatars.githubusercontent.com/u/111205728?v=4)](https://github.com/dowobdev "dowobdev (9 commits)")

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/dowob-laravel-refiner/health.svg)

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

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8723.1M23](/packages/yajra-laravel-oci8)[simplestats-io/laravel-client

Analytics for Laravel. Track visitors, registrations, and payments. Discover which channels actually drive revenue, not just traffic. Server-side, GDPR compliant, ad-blocker proof.

5019.3k](/packages/simplestats-io-laravel-client)[aedart/athenaeum

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

245.2k](/packages/aedart-athenaeum)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45344.0k1](/packages/pressbooks-pressbooks)[api-platform/laravel

API Platform support for Laravel

59156.3k11](/packages/api-platform-laravel)

PHPackages © 2026

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