PHPackages                             laragear/refine - 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. laragear/refine

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

laragear/refine
===============

Filter a database query using the request query keys and matching methods.

v3.0.0(3mo ago)1012.4k↓71%MITPHPPHP ^8.3CI passing

Since Jan 9Pushed 3mo ago2 watchersCompare

[ Source](https://github.com/Laragear/Refine)[ Packagist](https://packagist.org/packages/laragear/refine)[ Fund](https://github.com/sponsors/DarkGhostHunter)[ Fund](https://paypal.me/darkghosthunter)[ RSS](/packages/laragear-refine/feed)WikiDiscussions 3.x Synced 2d ago

READMEChangelog (9)Dependencies (12)Versions (14)Used By (0)

Refiner
=======

[](#refiner)

[![Latest Version on Packagist](https://camo.githubusercontent.com/356a05eb28a2cefbd8b72db5421e36b31b2b1f6fc873b9fdad0238c56e657080/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6c617261676561722f726566696e652e737667)](https://packagist.org/packages/laragear/refine)[![Latest stable test run](https://github.com/Laragear/Refine/workflows/Tests/badge.svg)](https://github.com/Laragear/Refine/actions)[![Codecov coverage](https://camo.githubusercontent.com/486a80f8c137a923f1fc9aded5a716f724f32dedbf19e40657a0c5e79c7d0514/68747470733a2f2f636f6465636f762e696f2f67682f4c617261676561722f526566696e652f67726170682f62616467652e7376673f746f6b656e3d6c4a4d5a67356d645679)](https://codecov.io/gh/Laragear/Refine)[![Maintainability](https://camo.githubusercontent.com/ae20a9ab3766f162f9f7c4457f4e465a69b0634d0780f220f9e744c276f54ac8/68747470733a2f2f716c74792e73682f6261646765732f38383238653733332d313032392d346363372d393537382d6334353335313533383238372f6d61696e7461696e6162696c6974792e737667)](https://qlty.sh/gh/Laragear/projects/Refine)[![Sonarcloud Status](https://camo.githubusercontent.com/acee94b822678e1054125f53338f6ac7b5c7e9e2b4787259b545147590bf391e/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d4c617261676561725f526566696e65266d65747269633d616c6572745f737461747573)](https://sonarcloud.io/dashboard?id=Laragear_Refine)[![Laravel Octane Compatibility](https://camo.githubusercontent.com/70359a356da237cd29561bc5d0bb80baae775b5ff62f288ed324755382858342/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2532304f6374616e652d436f6d70617469626c652d737563636573733f7374796c653d666c6174266c6f676f3d6c61726176656c)](https://laravel.com/docs/13.x/octane#introduction)

Filter a database query using the request query keys and matching methods.

```
// https://myblog.com/posts/?author_id=10

use Illuminate\Support\Facades\Route;

Route::get('/posts', function (Request $request) {
    return Post::refineBy(PostRefiner::class)->paginate()
});

class PostRefiner
{
    public function authorId($query, $value)
    {
        $query->where('author_id', $value);
    }
}
```

Become a sponsor
----------------

[](#become-a-sponsor)

[![](.github/assets/support.png)](https://github.com/sponsors/DarkGhostHunter)

Your support allows me to keep this package free, up-to-date and maintainable. Alternatively, you can **spread the word on social media!**.

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

[](#requirements)

- PHP 8.3 or later
- Laravel 12 or later

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

[](#installation)

Require this package into your project using Composer:

```
composer require laragear/refine
```

Usage
-----

[](#usage)

This package solves the problem of refining a database query using the URL parameters by moving that logic out of the controller.

For example, imagine you want to show all the Posts made by a given Author ID. Normally, you would check that on the controller and modify the query inside.

```
use App\Models\Post;
use Illuminate\Http\Request;

public function all(Request $request)
{
    $request->validate([
        'author_id' => 'sometimes|integer'
    ]);

    $query = Post::query()->limit(10);

    if ($request->has('author_id')) {
        $query->where('author_id', $request->get('author_id'));
    }

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

While this looks inoffensive for a couple of URL parameters, it will add up as more refinements are needed: published at a given time, with a given set of tags, ordering by a given column, etc. Eventually it will clutter your controller action.

Instead, Laragear Refine moves that logic to its own "Refiner" object, which is handled by only issuing the refiner class name to the `refineBy()` method of the query builder.

```
use App\Models\Post;
use Illuminate\Http\Request;
use App\Http\Refiners\PostRefiner;

public function all(Request $request)
{
    return Post::query()->refineBy(PostRefiner::class);
}
```

The magic is simple: each refiner method will be executed as long the corresponding URL parameter key is present in the incoming request. Keys are automatically normalized to `camelCase` to match the method, so the `author_id` key will execute `authorId()` with its value.

```
GET https://myapp.com/posts?author_id=20
```

```
namespace App\Http\Refiners;

class PostRefiner
{
    public function authorId($query, $value)
    {
        $query->where('author_id', $value);
    }
}
```

Creating a Refiner
------------------

[](#creating-a-refiner)

Call the `make:refiner` with the name of the Refiner you want to create.

```
php artisan make:refiner PostRefiner
```

You will receive the refiner in the `app\Http\Refiners` directory.

```
namespace App\Http\Refiners;

use Laragear\Refine\Refiner;

class PostRefiner extends Refiner
{
    /**
     * Create a new post query filter instance.
     */
    public function __construct()
    {
        //
    }
}
```

As you can see, apart from the constructor, the class is empty. The next step is to define methods to match the request keys.

### Defining methods

[](#defining-methods)

You may define the methods you want to be executed when a URL parameter key is present by simple creating these as public, using their corresponding `camelCase` key.

```
// For `author_id=value`
public function authorId($query, mixed $value, Request $request)
{
    // ...
}
```

All methods you set in the Refiner class receive the Query Builder instance, the value from the request, and the `Illuminate\Http\Request` instance itself. Inside each method, you're free to modify the Query Builder as you see fit, or even call authorization gates or check the user permissions.

```
namespace App\Http\Refiners;

use App\Models\Post;
use Illuminate\Http\Request;
use Laragear\Refine\Refiner;

class PostRefiner extends Refiner
{
    public function authorId($query, mixed $value, Request $request)
    {
        // Only apply the filter if the user has permission to see all posts.
        if ($request->user()->can('view any', Post::class)) {
            $query->where('author_id', $value);
        }
    }
}
```

### Only some keys

[](#only-some-keys)

On rare occasions, you may have a method you don't want to be executed as part of the refinement procedure, especially if your Refiner is extending another Refiner. In that case, you may instruct which URL parameters keys should be used to match their respective methods with the `getKeys()` method.

```
use Illuminate\Http\Request;

public function getKeys(Request $request): array
{
    return [
        'author_id',
        'published_before',
        'published_after',
    ];
}
```

Alternatively, if you're using a `FormRequest`, you can always return the keys of the validated data.

```
use Illuminate\Http\Request;
use Illuminate\Foundation\Http\FormRequest;

public function getKeys(Request $request): array
{
    if ($request instanceof FormRequest) {
        return array_keys($request->validated());
    }

    return array_keys($request->keys());
}
```

### Obligatory keys

[](#obligatory-keys)

Sometimes you will want to run a method even if the key is not set in the URL parameters. For that, use the `getObligatoryKeys()` method to return the keys (and methods) that should always run.

For example, if we want to run the `orderBy()` method regardless if there is the `order_by` URL parameter, we only need to return that key.

```
public function getObligatoryKeys(): array
{
    return ['order_by'];
}
```

Then, the method should be able to receive a `null` value when the URL parameter is not set.

```
public function orderBy($query, ?string $value, Request $request)
{
    // If the value was not set, use the publishing timestamp as the column to sort.
    $value ??= 'published_at'

    $query->orderBy($value, $request->query('order') ?? 'asc');
}
```

### Dependency Injection

[](#dependency-injection)

The Refiner class is always resolved using the application container. You can type-hint any dependency in the class constructor and use it later on the matching methods.

```
namespace App\Http\Refiners;

use Illuminate\Contracts\Auth\Access\Gate;
use Laragear\Refine\Refiner;
use App\Models\Post;

class PostRefiner extends Refiner
{
    public function __construct(protected Gate $gate)
    {
        //
    }

    public function authorId($query, $value)
    {
        if ($this->gate->check('view any', Post::class)) {
            // ...
        }
    }
}
```

### Validation

[](#validation)

You may also include validation logic into your Refiner by implementing the `ValidateRefiner` interface. From there, you should set your validation rules, and optionally your messages and custom attributes if you need to.

This is great if you expect a key to always be required in the query, as the `validationRules()` is an excellent place to do it.

```
namespace App\Http\Refiners;

use Laragear\Refine\Contracts\ValidatesRefiner;
use Laragear\Refine\Refiner;

class PostRefiner extends Refiner implements ValidatesRefiner
{
    // ...

    public function validationRules(): array
    {
        return ['author_id' => 'required|integer'];
    }
}
```

Note

Validation rules will run verbatim over the request **query**, not the request input.

Applying a Refiner
------------------

[](#applying-a-refiner)

In your Builder instance, simply call `refineBy()` with the name of the Refiner class (or its alias if you registered it on the application container) to apply to the query.

```
use App\Models\Post;
use App\Http\Refiners\PostRefiner;

Post::refineBy(PostRefiner::class)->paginate();
```

The `refineBy()` is a macro registered to the Eloquent Builder and the base Query Builder, and you can use it even after your own custom refinements.

```
use App\Http\Requests\PostRequest;
use App\Http\Refiners\PostRefiner;
use Illuminate\Support\Facades\DB;

public function rawPosts(PostRequest $request)
{
    return DB::table('posts')
        ->whereNull('deleted_at')
        ->refineBy(PostRefiner::class)
        ->limit(10)
        ->get();
}
```

### Custom keys

[](#custom-keys)

You can override the keys to look for on the Request at runtime by issuing the keys as second argument. These will replace the [custom keys](#only-some-keys) you have set in the class.

```
public function all(Request $request)
{
    $validated = $request->validate([
        // ...
    ])

    return Post::query()->refineBy(PostFilter::class, ['author_id', 'order_by'])->paginate();
}
```

Model Refiner
-------------

[](#model-refiner)

You may use the included `ModelRefiner` to quickly create a refiner for a database query over a model. The Model Refiner simplifies automatically the following URL parameters:

- `query` to search by both primary key *or* text contained in predetermined columns.
- `only[]` to only retrieve certain of columns.
- `has[]` to retrieve items that have at least one related model.
- `has_not[]` to retrieve items that doesn't have a related model.
- `with[]` to retrieve items including a relation or nested relation.
- `with_count[]` to include the count of the given relations.
- `with_sum[]` to include the count of the given relation column.
- `trashed` to include trashed items in the query.
- `order_by|order_by_desc` to determine which column to use for ordering.
- `limit|per_page` to limit the number of items retrieved.

### Creating a Model Refiner

[](#creating-a-model-refiner)

Simply call the `make:refiner` with the `--model` option.

```
php artisan make:refiner ArticleRefiner --model
```

You will receive a refiner extending the base `ModelRefiner`. Here you should set the relations, columns, sums, and order the refiner should use to validate the URL parameters values. This way you can have control on which columns or relations are permitted to be set in the query.

```
namespace App\Http\Refiners;

use Laragear\Refine\ModelRefiner;

class ArticleRefiner extends ModelRefiner
{
    /**
     * Return the columns that should only be included in the query.
     *
     * @return string[]
     */
    protected function getOnlyColumns(): array
    {
        return [];
    }

    /**
     * Return the relations that should exist for the query.
     *
     * @return string[]
     */
    protected function getHasRelations(): array
    {
        return [];
    }

    /**
     * Return the relations that should be missing for the query.
     *
     * @return string[]
     */
    protected function getHasNotRelations(): array
    {
        return [];
    }

    /**
     * Return the relations that can be queried.
     *
     * @return string[]
     */
    protected function getWithRelations(): array
    {
        return [];
    }

    /**
     * Return the relations that can be counted.
     *
     * @return string[]
     */
    protected function getCountRelations(): array
    {
        return [];
    }

    /**
     * Return the relations and the columns that should be sum.
     *
     * @return string[]
     */
    protected function getWithSumRelations(): array
    {
        // Separate the relation name using hyphen (`-`). For example, `published_posts-votes`.
        return [];
    }

    /**
     * Return the columns that can be used to sort the query.
     *
     * @return string[]
     */
    protected function getOrderByColumns(): array
    {
        return [];
    }
}
```

As with a normal refiner, you may also override the validation keys and/or the keys to check in the request, and even how each query key should be *refined*.

```
namespace App\Http\Refiners;

use Illuminate\Support\Arr;
use Laragear\Refine\ModelRefiner;

class ArticleRefiner extends ModelRefiner
{
    public function validationRules(): array
    {
        return Arr::only(parent::validationRules(), ['with', 'with.*', 'order_by']);
    }

    public function getKeys(Request $request): array
    {
        return Arr::only(parent::getKeys(), ['with', 'order_by']);
    }

    public function query(Builder $query, string $search): void
    {
        $query->where('name', 'like', $this->normaliseQuery($search));
    }

    // ...
}
```

Tip

Even if you validate relations using `snake_case`, when building the query for relations, these will be automatically transformed into `camelCase`, even if these are separated by `dot.notation`. No need to change case.

### Full text search

[](#full-text-search)

By default, when receiving a string to search as "query", the Model Refiner will use an `ILIKE` operator to search inside one or many columns. This approach will work on all SQL engines.

Alternatively, you may use [PostgreSQL or MySQL full-text search capabilities](https://laravel.com/docs/11.x/queries#full-text-where-clauses) by setting `$fullTextSearch` as `true` in your Model Refiner.

```
namespace App\Http\Refiners;

use Illuminate\Support\Arr;
use Laragear\Refine\ModelRefiner;

class ArticleRefiner extends ModelRefiner
{
    protected bool $fullTextSearch = true;

    // ...
}
```

### Sum relations

[](#sum-relations)

The `ModelRefiner` supports summing relations columns using the relation name and the column separated by a hyphen. You may want to set an array of relations and possible columns to sum by returning them in the `getSumRelations()` method.

```
protected function getSumRelations(): array
{
    return [
        'user_comments-claps',
        'user_comments-down_votes',
        'user_comments-up_votes',
    ];
}
```

The above will make calls to the `userComments()` relation of the queried model.

Form Request Refiner
--------------------

[](#form-request-refiner)

For convenience, you can create a Request Refiner that will automatically validate and refine the request parameters. Failed or malformed query parameters will be silently discarded.

First, create a Refined Request through the `make:refined-request` Artisan command, with a name. For example, if we plan to refine a request to show all the payments, we can name it as `PaymentRefinedRequest`.

```
php artisan make:refined-request PostRefinedRequest
```

You will receive a class at the `App\Http\Request\Refined` directory, based on the `Laragear\Refine\Http\Requests\RefinedRequest`. Here the only requirement is to set which Refiner you want to use to *refine* the request.

Since the class extends the native `Illuminate\Foundation\Http\FormRequest` class, you can also use validation and authorization rules.

```
namespace App\Http\Requests\Refined;

use App\Http\Refiners\PostRefiner;
use App\Models\User;
use Laragear\Refine\Http\Requests\RefinedRequest;

class PostRefinedRequest extends RefinedRequest
{
    /**
     * The refiner to execute.
     */
    protected $refined = PostRefiner::class;

    public function authorize(User $user)
    {
        return $user->isCool();
    }
}
```

Then, you can refine the request using the `query()` method.

```
use Illuminate\Support\Facades\Route;
use App\Http\Requests\Refined\PostRefinedRequest;

Route::get('payments/all', function (PostRefinedRequest $request) {
    return $request->query()->paginate();
})
```

Laravel Octane compatibility
----------------------------

[](#laravel-octane-compatibility)

- There are no singletons using a stale application instance.
- There are no singletons using a stale config instance.
- There are no singletons using a stale request instance.
- A static property being written is the cache of Refiner methods that grows by every unique Refiner that runs.
- A static property being written is the cache of Abstract Refiner methods that is only written once.

The cached Refiner methods shouldn't grow uncontrollably unless you have dozens of Refiner classes being called multiple times. In any case, you can always flush the cached refiner methods using the `RefineQuery::flushCachedRefinerMethods()`.

There should be no problems using this package with Laravel Octane.

Security
--------

[](#security)

If you discover any security-related issues, please [use the online form](https://github.com/Laragear/Refine/security).

License
=======

[](#license)

This specific package version is licensed under the terms of the [MIT License](LICENSE.md), at the time of publishing.

[Laravel](https://laravel.com) is a Trademark of [Taylor Otwell](https://github.com/TaylorOtwell/). Copyright © 2011–2026 Laravel LLC.

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance78

Regular maintenance activity

Popularity31

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity65

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

Recently: every ~101 days

Total

12

Last Release

117d ago

Major Versions

v1.2.2 → 2.x-dev2025-03-26

v2.0.0 → v3.0.02026-03-08

PHP version history (3 changes)v1.0.0PHP ^8.1

2.x-devPHP ^8.2

v3.0.0PHP ^8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/5141911?v=4)[Italo](/maintainers/DarkGhostHunter)[@DarkGhostHunter](https://github.com/DarkGhostHunter)

---

Top Contributors

[![DarkGhostHunter](https://avatars.githubusercontent.com/u/5141911?v=4)](https://github.com/DarkGhostHunter "DarkGhostHunter (42 commits)")

---

Tags

composerdatabasehttplaravelpackagephpqueryrequest

### Embed Badge

![Health badge](/badges/laragear-refine/health.svg)

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

###  Alternatives

[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

77022.3M151](/packages/laravel-mcp)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M131](/packages/roots-acorn)[larastan/larastan

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

6.5k55.4M8.4k](/packages/larastan-larastan)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.6k29.9M146](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M132](/packages/laravel-pulse)

PHPackages © 2026

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