Eloquent Filter Documentation

repository·master·Indexed 23 days ago

https://github.com/tucker-eric/eloquentfilter

A tool for Laravel and Lumen that provides a structured, reusable way to filter Eloquent models and their relationships based on request parameters. It allows developers to encapsulate complex query logic within dedicated Filter classes, offering features like dynamic filtering, relationship constraints via the $relations array, and specialized pagination methods such as paginateFilter().

Tokens
4.6K
Snippets
16
Records
20
Agent score
83%

What's inside Eloquent Filter

  1. How Eloquent Filter works (Concept)

    master

    Eloquent Filter provides a clean way to apply complex query logic based on request parameters. Instead of manually writing multiple if ($request->has(...)) blocks and complex whereHas closures in your controller, you can use the filter() method on your Eloquent model.

    Standard Approach (Manual):

    $query = User::where('company_id', $request->input('company_id'));
    if ($request->has('name')) {
        $query->where(function ($q) use ($request) {
            return $q->where('first_name', 'LIKE', $request->input('name') . '%')
                     ->orWhere('last_name', 'LIKE', '%' . $request->input('name') . '%');
        });
    }
    return $query->get();

    Eloquent Filter Approach:

    return User::filter($request->all())->get();

    The logic for how each parameter affects the query is encapsulated within a dedicated Filter class.

  2. Configure Eloquent Filter in Lumen

    master

    To use the Artisan command model:filter in Lumen, register the service provider in bootstrap/app.php:

    $app->register(EloquentFilter\LumenServiceProvider::class);

    To change the default namespace in Lumen, use the config helper in bootstrap/app.php:

    config(['eloquentfilter.namespace' => "App\\Models\\ModelFilters\\"]);
  3. Define filter logic in a ModelFilter class

    master

    To define how specific input keys affect your database queries, create a class that extends EloquentFilter\ModelFilter.

    Key Behaviors:

    • Method Mapping: Input keys are converted to camelCase to match method names. For example, mobile_phone maps to mobilePhone().
    • ID Dropping: By default, _id is dropped from the end of input keys. user_id will trigger the user() method. To disable this, set protected $drop_id = false;.
    • Camel Case Mapping: To prevent automatic camel-casing of input keys (e.g., to allow mobile_phone() instead of mobilePhone()), set protected $camel_cased_methods = false;.
    • Empty Values: Empty strings and null are ignored by default. To allow them, set protected $allowedEmptyFilters = false;.
    • Setup Method: If you define a setup() method, it is executed once before any specific filter methods are called.
    • Context: Inside filter methods, you have access to all Eloquent Builder methods via $this. You can also access the full input array via $this->input() or specific values via $this->input($key).
    use EloquentFilter\ModelFilter;
    
    class UserFilter extends ModelFilter
    {
        // This will filter 'company_id' OR 'company'
        public function company($id)
        {
            return $this->where('company_id', $id);
        }
    
        public function name($name)
        {
            return $this->where(function($q) use ($name) {
                return $q->where('first_name', 'LIKE', "%$name%")
                    ->orWhere('last_name', 'LIKE', "%$name%");
            });
        }
    
        public function mobilePhone($phone)
        {
            return $this->where('mobile_phone', 'LIKE', "$phone%");
        }
    
        public function setup()
        {
            $this->onlyShowDeletedForAdmins();
        }
    
        public function onlyShowDeletedForAdmins()
        {
            if(Auth::user()->isAdmin())
            {
                $this->withTrashed();
            }
        }
    }
  4. Apply filters to an Eloquent model

    master

    To enable filtering on a model, implement the EloquentFilter\Filterable trait. Once implemented, you can call the filter() method on the model, passing an array of input (e.g., from a Request object).

    <?php
    
    namespace App;
    
    use EloquentFilter\Filterable;
    use Illuminate\Database\Eloquent\Model;
    
    class User extends Model
    {
        use Filterable;
    
        //User Class
    }
    
    // Usage in a Controller:
    class UserController extends Controller
    {
        public function index(Request $request)
        {
            return User::filter($request->all())->get();
        }
    }
  5. Configure Eloquent Filter in Laravel

    master

    To use the Artisan command model:filter and publish the configuration file, register the service provider in config/app.php:

    'providers' => [
        // Other service providers...
    
        EloquentFilter\ServiceProvider::class,
    ],

    After registering, publish the configuration file using:

    php artisan vendor:publish --provider="EloquentFilter\ServiceProvider"

    You can then customize the filter namespace in config/eloquentfilter.php:

    'namespace' => "App\\ModelFilters\\",
  6. Publish the Eloquent Filter configuration

    master

    To customize the package settings, publish the configuration file to your Laravel application's config directory using the following Artisan command:

    php artisan vendor:publish --tag=eloquentfilter-config

    Note: While the ServiceProvider defines the publishing logic for config/eloquentfilter.php, you should use the standard Laravel vendor:publish command to move it to config_path('eloquentfilter.php').

  7. Define a custom default filter for a Model

    master

    By default, Eloquent Filter looks for a class named {$ModelName}Filter in the App\ModelFilters\ namespace. If you want to use a different filter class for a specific model, implement a public modelFilter() method in your model that returns the filter class using $this->provideFilter().

    Note: You must use the EloquentFilter\Filterable trait in your model.

    namespace App;
    
    use EloquentFilter\Filterable;
    use Illuminate\Database\Eloquent\Model;
    
    class User extends Model
    {
        use Filterable;
    
        public function modelFilter()
        {
            return $this->provideFilter(\App\ModelFilters\CustomFilters\CustomUserFilter::class);
        }
    }
  8. Paginate filtered queries

    master

    To paginate a filtered query while preserving the URL query string (without manually appending inputs to the pagination links), use paginateFilter() or simplePaginateFilter(). These methods accept the same arguments as Laravel's native paginators.

    In your Blade view, calling $users->render() will automatically include the original query string in the pagination links, ignoring empty inputs (unless protected $allowedEmptyFilters = false is set on the filter).

    class UserController extends Controller
    {
        public function index(Request $request)
        {
            // Returns a paginator with query string preserved
            $users = User::filter($request->all())->paginateFilter();
    
            return view('users.index', compact('users'));
        }
    
        public function simpleIndex(Request $request)
        {
            // Returns a simple paginator
            $users = User::filter($request->all())->simplePaginateFilter();
    
            return view('users.index', compact('users'));
        }
    }
  9. Apply a filter dynamically

    master

    You can bypass the model's default filter by passing a specific filter class as the second argument to the filter() method. Dynamic filters take precedence over any default filters defined on the model.

    use App\User;
    use App\ModelFilters\Admin\UserFilter as AdminFilter;
    use App\ModelFilters\User\UserFilter as BasicUserFilter;
    
    $userFilter = Auth::user()->isAdmin() ? AdminFilter::class : BasicUserFilter::class;
    return User::filter($request->all(), $userFilter)->get();
    return User::filter($request->all(), $userFilter)->get();
  10. Filter related models using the $relations array

    master

    If the related model has its own ModelFilter, you can use the $relations array in the parent filter to delegate filtering logic. This is efficient for querying multiple columns on a related table as it avoids multiple whereHas() calls.

    Requirements:

    • The related model MUST have a ModelFilter associated with it.
    • The keys in the $relations array must match the relationship names on the model.
    • The values in the array are the input keys that will be passed to the related model's filter.

    Mapping Logic: The input keys are mapped to camelCased methods on the related model's filter. For example, an input key industry_type will trigger the industryType() method on the related filter.

    // Parent Filter
    class UserFilter extends ModelFilter
    {
        public $relations = [
            'clients' => ['industry', 'potential_volume'],
        ];
    }
    
    // Related Filter
    class ClientFilter extends ModelFilter
    {
        public $relations = [];
    
        public function industry($id)
        {
            return $this->where('industry_id', $id);
        }
        
        public function potentialVolume($volume)
        {
            return $this->where('potential_volume', '>=', $volume);
        }
    }
  11. Blacklist and Whitelist filter methods

    master

    You can prevent certain methods from being called by the filter engine using a blacklist. This is useful for internal logic methods that should not be triggered directly by user input.

    • Static Blacklist: Define protected $blacklist = ['methodName']; in your filter class.
    • Dynamic Whitelisting: Use $this->whitelistMethod('methodName') (typically within the setup() method) to allow a blacklisted method to be called under specific conditions.

    Example of dynamic whitelisting:

    public function setup()
    {
        if(Auth::user()->isAdmin())
        {
            $this->whitelistMethod('secretMethod');
        }
    }