Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Eloquent Filtering Laravel Package

indexzer0/eloquent-filtering

Filter Laravel Eloquent models using simple arrays and request data—no custom query spaghetti. Define allowed filters on your models, support complex search, and keep queries readable, maintainable, and easy to extend for APIs and dashboards.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require indexzer0/eloquent-filtering
    php artisan eloquent-filtering:install
    
  2. Implement IsFilterable in your model:

    use IndexZer0\EloquentFiltering\Contracts\IsFilterable;
    use IndexZer0\EloquentFiltering\Traits\Filterable;
    
    class Product extends Model implements IsFilterable
    {
        use Filterable;
    
        public function allowedFilters(): AllowedFilterList
        {
            return Filter::only(
                Filter::field('name', [FilterType::EQUAL]),
                Filter::field('price', [FilterType::GREATER_THAN, FilterType::LESS_THAN])
            );
        }
    }
    
  3. First use case:

    $products = Product::filter([
        ['type' => '$eq', 'target' => 'name', 'value' => 'TV'],
        ['type' => '$gt', 'target' => 'price', 'value' => 100]
    ])->get();
    

Key Entry Points

  • allowedFilters(): Define which filters are permitted on the model.
  • Filter::field(): Specify allowed filter types for a field (e.g., EQUAL, LIKE, GREATER_THAN).
  • Filter::only(): Restrict filters to a predefined list.
  • Filter::all(): Allow all filter types for a field.
  • Filter::custom(): Define custom filter logic.

Implementation Patterns

1. API Request Filtering

Workflow:

  • Parse incoming request data (e.g., from request()->all() or request()->query()).
  • Apply filters dynamically:
    $filters = request()->input('filters', []);
    $results = Product::filter($filters)->get();
    

Tip: Use Filter::custom() for complex logic:

Filter::custom('active', function (Builder $query, $value) {
    return $query->where('is_active', $value);
});

2. Admin Dashboard Filters

Pattern: Combine with Laravel Scout or custom search:

$users = User::filter([
    ['type' => '$like', 'target' => 'name', 'value' => 'John'],
    ['type' => '$gt', 'target' => 'created_at', 'value' => now()->subDays(30)->toDateString()]
])->paginate(10);

Tip: Use Filter::field() with modifiers for partial matches:

Filter::field('email', [FilterType::LIKE]) // Supports `$like:start` and `$like:end`

3. Nested Relationship Filters

Pattern: Filter related models via JSON paths:

Filter::field('user->role->name', [FilterType::EQUAL]) // Requires JSON column or relationship

Tip: For JSON columns, use FilterType::JSON_LENGTH:

Filter::field('metadata->tags', [FilterType::JSON_LENGTH])

4. Conditional Filters ($and, $or)

Pattern: Group filters logically:

$filters = [
    ['type' => '$or', 'filters' => [
        ['type' => '$eq', 'target' => 'status', 'value' => 'active'],
        ['type' => '$eq', 'target' => 'status', 'value' => 'pending']
    ]],
    ['type' => '$and', 'filters' => [
        ['type' => '$gt', 'target' => 'price', 'value' => 50],
        ['type' => '$lt', 'target' => 'price', 'value' => 200]
    ]]
];

Note: $and/$or are always allowed and don’t need explicit definition.


5. Validation Integration

Pattern: Validate filters before applying:

use Illuminate\Support\Facades\Validator;

$validator = Validator::make($request->all(), [
    'filters' => 'array',
    'filters.*' => 'required|array',
    'filters.*.type' => 'required|string',
    'filters.*.target' => 'required|string',
    'filters.*.value' => 'sometimes|string|numeric',
]);

6. Caching Filtered Results

Pattern: Cache filtered queries with tags:

$cacheKey = 'products_' . md5(serialize($filters));
return Cache::remember($cacheKey, now()->addHours(1), function () use ($filters) {
    return Product::filter($filters)->get();
});

Gotchas and Tips

Pitfalls

  1. SQL Injection Risk:

    • Always validate/sanitize target fields (e.g., ensure they match allowed columns).
    • Use Filter::field() to restrict targets to model attributes.
  2. Performance with Complex Filters:

    • Avoid deep nesting ($or/$and) in large datasets. Test with toSql() to review generated queries.
    • Use database indexes on filtered columns (e.g., price, created_at).
  3. JSON Path Limitations:

    • JSON path filters (e.g., user->role->name) require MySQL 5.7+ or PostgreSQL.
    • Test with toSql() to debug path syntax.
  4. Case Sensitivity:

    • LIKE filters are case-sensitive by default. Use LOWER() in custom filters for case-insensitive searches:
      Filter::custom('name', function (Builder $query, $value) {
          return $query->whereRaw('LOWER(name) LIKE LOWER(?)', ["%{$value}%"]);
      });
      
  5. Sorting (Experimental):

    • Sorting is not natively supported but can be added via custom logic:
      $results = Product::filter($filters)->orderBy('name')->get();
      

Debugging Tips

  1. Inspect Generated SQL:

    $query = Product::filter($filters);
    dd($query->toSql(), $query->getBindings());
    
  2. Check Allowed Filters:

    dd(Product::allowedFilters()); // Debug allowed filter definitions
    
  3. Handle Missing Fields:

    • Use Filter::field() with fallback logic:
      Filter::field('optional_field', [FilterType::EQUAL], function () {
          return FilterType::EQUAL; // Default if field doesn’t exist
      });
      

Extension Points

  1. Custom Filter Types:

    Filter::extend('custom_type', function (Builder $query, $target, $value) {
        return $query->where($target, 'custom_logic', $value);
    });
    
  2. Override Default Behavior:

    • Publish the config (php artisan vendor:publish --tag=eloquent-filtering-config) and modify:
      'default_filter_types' => [
          FilterType::EQUAL,
          FilterType::LIKE,
          // Add/remove types
      ],
      
  3. Integrate with Scout:

    • Use Filter::custom() to bridge with Scout’s search logic:
      Filter::custom('scout_search', function (Builder $query, $value) {
          return $query->where('name', 'LIKE', "%{$value}%");
      });
      

Configuration Quirks

  1. Case-Insensitive Targets:

    • Configure in config/eloquent-filtering.php:
      'case_insensitive_targets' => ['name', 'email'], // Auto-convert to LOWER()
      
  2. Disable Default Filters:

    'default_filter_types' => [], // Disable all default types
    
  3. JSON Path Wildcards:

    • Enable in config for dynamic JSON paths:
      'json_path_wildcards' => true,
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport