Eloquent Filters

Suggest edit

Introduction

Eloquent Filters are powerful tools for creating complex queries in Laravel. They allow you to easily manage and customize your search criteria. You can use Eloquent filters to filter your product catalog based on attributes, brands, and other criteria.

To create a new Eloquent filter, you can use the php artisan orchid:filter command followed by the desired filter name. This command will generate a new filter class in the app/Orchid/Filters folder.

Here’s an example of creating an EmailFilter:

php artisan orchid:filter EmailFilter

The generated filter class will look like this:

namespace App\Http\Filters;

use Orchid\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;

class EmailFilter extends Filter
{
    /**
     * The array of matched parameters.
     *
     * @var array
     */
    public $parameters = ['email'];

    /**
     * Apply filter if the request parameters were satisfied.
     * 
     * @param Builder $builder
     *
     * @return Builder
     */
    public function run(Builder $builder): Builder
    {
        return $builder->where('email', $this->request->get('email'));
    }

    /**
     * Get the display fields.
     *
     * @return Field[]
     */
    public function display(): array
    {
        return [
            Input::make('email')
                ->type('text')
                ->value($this->request->get('email'))
                ->placeholder('Search...')
                ->title('Search')
        ];
    }
}

To use filters in your own models, you need to connect the trait Orchid\Filters\Filterable and pass an array of classes to the filtersApply function:

use App\Model;

Model::filters([EmailFilter::class])->simplePaginate();

Using Filters for Different Models

One of the great advantages of filters is that you can reuse the same filter class for different models. This allows you to define a filter once and apply it to multiple models, reducing code duplication. Simply specify the filter class when applying filters to your models:

$filters = [
    EmailFilter::class,
];

User::filters($filters)
    ->simplePaginate();

Customer::filters($filters)
    ->simplePaginate();

Running the Filter Always

By default, filters are applied only when the corresponding parameters are specified. However, if you want a filter to run on every request, you can leave the $parameters property as an empty array in your filter class. This way, the filter will be applied to all queries. For instance:

public $parameters = [];

Parameter Patterns

Filter provides the capability to define parameter patterns using a convenient syntax. This allows you to create custom patterns and perform advanced filtering based on specific patterns. For example:

/**
 * The array of matched parameters.
 *
 * @var null|array
 */
public $parameters = [
    'pattern.*',
];

In the above example, the filter will match any parameter that follows the pattern pattern.*. This allows you to handle a wide range of dynamic parameters in your filters.

Selection

The “Selection” layer provides a convenient way to group and organize filters for both displaying and applying them to a model. This layer acts as an intermediary between the user interface and the model, simplifying the process of managing filters.

To create a “Selection” layer, you can use the following command:

php artisan orchid:selection MailingSelection

This command will generate a new PHP file called MailingSelection in the App\Orchid\Layouts directory. Inside this class, you will find a single method called filters(). This method is where you should list all the filters that need to be displayed and applied.

For example, let’s say you want to display and apply two filters: a email filter and a created filter. Your MailingSelection class would look like this:

namespace App\Orchid\Layouts;

use App\Orchid\Filters\EmailFilter;
use App\Orchid\Filters\CreatedFilter;
use Orchid\Screen\Layouts\Selection;

class MailingSelection extends Selection
{
    /**
     * @return Filter[]
     */
    public function filters(): array
    {
        return [
          EmailFilter::class,
          CreatedFilter::class
        ];
    }
}

Once you have defined your filters in the MailingSelection class, you can apply them to a model by using the filters() method. For example:

Model::filters(MailingSelection::class)->simplePaginate();

By calling the filters() method on your model and passing MailingSelection::class as the argument, you can apply the filters defined in the MailingSelection class to the model.

Displaying on a Screen

The “Selection” layer can also be used to display filters on a screen. In the layout() method of your screen, you can include the MailingSelection class to display the filters on the screen. For example:

use App\Orchid\Layouts\MailingSelection;

public function layout(): array
{
    return [
        MailingSelection::class,
    ];
}

Please note that filters with empty fields will not be rendered, ensuring a clean and user-friendly interface.

Customization with Templates

The appearance of the selection layer can vary, such as being displayed as a drop-down list (the default) or as a form. You can define this using the template property. For instance:

class MailingSelection extends Selection
{
    /**
     * Template for rendering the selection.
     * 
     * You can use any Blade template. Defaults:
     * - `self::TEMPLATE_LINE` for a linear layout
     * - `self::TEMPLATE_DROP_DOWN` for a dropdown menu
     */
    public $template = self::TEMPLATE_DROP_DOWN;
    
    /**
     * @return Filter[]
     */
    public function filters(): array
    {
        return [
            //...
        ];
    }
}

Moreover, you have the flexibility to define your own Blade template within this property.

Handling HTTP Parameters

To automatically filter and sort your application’s data based on user-supplied HTTP parameters, the package provides a powerful and flexible set of tools. The key to effectively utilizing these tools is to ensure that your model includes the Filterable trait and implements a whitelist of acceptable filter and sort parameters.

To make your App\Models\Post model filterable, follow these steps:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Orchid\Filters\Filterable;
use Orchid\Filters\Types\Like;
use Orchid\Filters\Types\Where;
use Orchid\Filters\Types\WhereDate;
use Orchid\Filters\Types\WhereMaxMin;
use Orchid\Filters\Types\WhereDateStartEnd;

class Post extends Model
{
    use Filterable;
}

Filtering

As mentioned before, any filtering action can only be performed on an allowed list of filters, which is specified using the allowedFilters property as a key-value pair. The key represents the name of your columns, and the value is the handling class. Here’s an example:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Orchid\Filters\Filterable;
use Orchid\Filters\Types\Like;
use Orchid\Filters\Types\Where;
use Orchid\Filters\Types\WhereDate;
use Orchid\Filters\Types\WhereMaxMin;
use Orchid\Filters\Types\WhereDateStartEnd;

class Post extends Model
{
    use Filterable;

    /**
     * The attributes for which you can use filters in url.
     *
     * @var array
     */
    protected $allowedFilters = [
        'id'            => Where::class,
        'user_id'       => WhereIn::class,
        'rating'        => WhereMaxMin::class,
        'content'       => Like::class,
        'publish_at'    => WhereDate::class,
        'created_at'    => WhereDateStartEnd::class,
        'deleted_at'    => WhereDateStartEnd::class,
    ];
}

Once you have specified the list, using automatic filtering is straightforward. Simply call the filters() method, for example:

Post::filters()->paginate();

By leveraging this approach, you can easily apply filters to your query and paginate the results.

This code will automatically apply any filters or sorting rules that were included in the user’s HTTP request.

Query Examples

In order to use this feature effectively, it’s important to have a solid understanding of how the HTTP parameters are translated into database queries. For example:

http://example.com/demo?filter[id]=1
$model->where('id', '=', 1)

This query will apply a where clause to the id column of your model, filtering out any records that don’t match the value provided by the user.

http://example.com/demo?filter[name]=A
$model->where('name', 'like', '%A%')

This query will apply a like clause to the name column of your model, searching for any records that contain the letter “A” in their name.

http://example.com/demo?filter[id]=1,2,3,4,5
$model->whereIn('id', [1,2,3,4,5]);

This query will apply a wherein clause to the id column of your model, filtering for any records that match one of the specified IDs.

http://example.com/demo?filter[id][min]=1&filter[id][max]=5
$model->whereBetween('id', [1,5]);

This query will apply a wherebetween clause to the id column of your model, filtering for records where the ID is between 1 and 5.

http://example.com/demo?filter[id][]=1&filter[id][]=2&filter[id][]=3
$model->whereIn('id', [1,2,3]);

This query will apply a whereIn clause to the id column of your model, filtering for records where the ID is one of the specified values.

http://example.com/demo?filter[rating][min]=1&filter[rating][max]=5
$model->where('rating', '>=', 1)
    ->where('rating', '<=', 5);

This query will apply two separate where clauses to the rating column of your model, filtering for records where the rating is between 1 and 5.

http://example.com/demo?filter[rating][min]=1
$model->where('rating', '>=', 1);

This query will apply a single where clause to the rating column of your model, filtering for records where the rating is greater than or equal to 1.

http://example.com/demo?filter[publish_at]=2023-02-02
$model->where('publish_at', '2023-02-02')

This query will apply a single where clause to the publish_at column of your model, filtering for records where the publish_at date is exactly equal to February 2, 2023.

http://example.com/demo?filter[created_at][start]=2023-01-01&filter[created_at][end]=2023-02-02
$model->whereDate('created_at', '>=', '2023-01-01')
    ->whereDate('created_at', '<=', '2023-02-02');

This query will apply two separate whereDate clauses to the created_at column of your model, filtering for records where the created_at date falls within the specified range.

http://example.com/demo?filter[created_at][start]=2023-01-01
$model->whereDate('created_at', '>=', '2023-01-01')

This query will apply a single whereDate clause to the created_at column of your model, filtering for records where the created_at date is greater than or equal to January 1, 2023.

HTTP filters or sorting do not have separate display templates. You can see an example of this use in the table headers.

Sorting

To enable sorting functionality, you need to specify a list of columns in the allowedSorts property. This list represents the columns in your database table that can be used for sorting. Here’s an example:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Orchid\Filters\Filterable;

class Post extends Model
{
    use Filterable;

    /**
     * The attributes for which can use sort in url.
     *
     * @var array
     */
    protected $allowedSorts = [
        'id',
        'user_id',
        'rating',
        'publish_at',
        'created_at',
        'deleted_at',
    ];
}

Once you have defined the allowed sortable columns, you can easily enable sorting by calling the filters() method. This method will handle the sorting based on the provided HTTP parameter. For instance:

Post::filters()->paginate();

Now, when you make an HTTP request, the sorting behavior will be as follows:

http://example.com/demo?sort=created_at
$model->orderBy('created_at', 'asc');

http://example.com/demo?sort=-created_at
$model->orderBy('created_at', 'desc');

Default Sorting

If you want to specify a default sorting order for your data, you can use the defaultSort method. This method allows you to set a default column for sorting when no specific sorting parameter is provided via the HTTP request. For example:

Post::filters()->defaultSort('id')->paginate();

You can also specify the sort direction as a second parameter. For instance:

Post::filters()->defaultSort('id', 'desc')->paginate();

Automatic sorting and filtering of HTTP requests will not work with fields of models obtained through relationships. If you need sorting or filtering based on such fields, you can use third-party packages, such as Eloquent Power Joins. This package can help you solve this issue:

User::orderByPowerJoins('profile.city');
User::orderByPowerJoins('profile.city', 'desc');

However, you will need to manually handle the HTTP parameters sort and filter, as the package does not automatically recognize that the - sign before the field name indicates descending order for sorting, and also how to apply filters. You can do this using a “Filter.” Additionally, you should only use the package methods for sorting or filtering based on fields accessible through relationships.

Our Friends