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

Laravel Filter Laravel Package

czim/laravel-filter

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Installation**:
   ```bash
   composer require czim/laravel-filter

Publish the config file (if needed):

php artisan vendor:publish --provider="Czim\LaravelFilter\LaravelFilterServiceProvider"
  1. Basic Usage: Define a filter class (e.g., ProductFilter) extending Czim\LaravelFilter\Filter:

    use Czim\LaravelFilter\Filter;
    
    class ProductFilter extends Filter
    {
        protected $query;
    
        public function __construct($query)
        {
            $this->query = $query;
        }
    
        public function apply()
        {
            if ($this->has('category')) {
                $this->query->where('category_id', $this->get('category'));
            }
    
            if ($this->has('price_min')) {
                $this->query->where('price', '>=', $this->get('price_min'));
            }
    
            return $this;
        }
    }
    
  2. First Use Case: Apply the filter in a controller:

    use App\Filters\ProductFilter;
    
    public function index(Request $request)
    {
        $query = Product::query();
        $filter = new ProductFilter($query);
        $filter->apply($request->all());
    
        return ProductResource::collection($filter->getQuery()->get());
    }
    

Implementation Patterns

Modular Filter Building

  1. Separate Filters by Domain: Create dedicated filter classes for different models (e.g., UserFilter, OrderFilter). Example:

    class UserFilter extends Filter
    {
        public function apply()
        {
            if ($this->has('role')) {
                $this->query->where('role', $this->get('role'));
            }
    
            if ($this->has('active')) {
                $this->query->where('active', $this->get('active'));
            }
    
            return $this;
        }
    }
    
  2. Reusable Filter Logic: Extend base filters for shared functionality. Example:

    class BaseProductFilter extends Filter
    {
        protected function applyPriceRange()
        {
            if ($this->has('price_min') || $this->has('price_max')) {
                $this->query->whereBetween('price', [
                    $this->get('price_min', 0),
                    $this->get('price_max', PHP_INT_MAX),
                ]);
            }
        }
    }
    
    class ProductFilter extends BaseProductFilter
    {
        public function apply()
        {
            $this->applyPriceRange();
            // Additional filters...
        }
    }
    
  3. Dynamic Filter Application: Use middleware or a base controller to apply filters automatically:

    class FilterableController extends Controller
    {
        protected function applyFilters($query, $filterClass, $request)
        {
            $filter = new $filterClass($query);
            return $filter->apply($request->all())->getQuery();
        }
    }
    
  4. Countable Filters: Extend Czim\LaravelFilter\CountableFilter for faceted navigation:

    class ProductFilter extends CountableFilter
    {
        public function apply()
        {
            // Apply filters as usual
            return $this;
        }
    
        public function getCounts()
        {
            return [
                'categories' => $this->getCategoryCounts(),
                'brands' => $this->getBrandCounts(),
            ];
        }
    
        protected function getCategoryCounts()
        {
            return Product::select('category_id', \DB::raw('COUNT(*) as count'))
                ->where($this->query->getQuery()->getBindings(), $this->query->getQuery()->getValues())
                ->groupBy('category_id')
                ->pluck('count', 'category_id');
        }
    }
    
  5. Integration with API Resources: Combine filters with Laravel API Resources for consistent responses:

    public function index(Request $request)
    {
        $query = Product::query();
        $filter = new ProductFilter($query);
        $filter->apply($request->all());
    
        return new ProductCollection($filter->getQuery()->paginate());
    }
    
  6. Form Request Integration: Validate filter inputs in a FormRequest:

    class ProductFilterRequest extends FormRequest
    {
        public function rules()
        {
            return [
                'category' => 'sometimes|integer|exists:products,category_id',
                'price_min' => 'sometimes|numeric|min:0',
                'price_max' => 'sometimes|numeric|min:0',
            ];
        }
    }
    

Gotchas and Tips

Common Pitfalls

  1. Query Binding Issues:

    • Problem: Forgetting to pass query bindings when cloning or duplicating queries for counts.
    • Fix: Use $this->query->getQuery()->getBindings() and $this->query->getQuery()->getValues() to preserve bindings:
      $baseQuery = $this->query->getQuery();
      $countQuery = Product::select(\DB::raw('COUNT(*)'))
          ->where($baseQuery->getBindings(), $baseQuery->getValues());
      
  2. Case Sensitivity in Filters:

    • Problem: String comparisons may fail due to case sensitivity.
    • Fix: Use whereRaw or modify the query:
      if ($this->has('name')) {
          $this->query->whereRaw('LOWER(name) LIKE LOWER(?)', ["%{$this->get('name')}%"]);
      }
      
  3. Performance with Large Datasets:

    • Problem: Count queries can be slow on large tables.
    • Fix: Cache counts or use database-level optimizations:
      protected function getCategoryCounts()
      {
          return Cache::remember("product_category_counts_{$this->getCacheKey()}", now()->addHours(1), function () {
              return Product::select('category_id', \DB::raw('COUNT(*) as count'))
                  ->where($this->query->getQuery()->getBindings(), $this->query->getQuery()->getValues())
                  ->groupBy('category_id')
                  ->pluck('count', 'category_id');
          });
      }
      
  4. Overwriting Query Bindings:

    • Problem: Modifying the query directly can overwrite bindings.
    • Fix: Use where methods instead of whereRaw when possible, or manually manage bindings.
  5. Filter Order Matters:

    • Problem: The order of where clauses can affect performance and results.
    • Fix: Place the most restrictive filters first:
      if ($this->has('active')) {
          $this->query->where('active', $this->get('active')); // Filter first
      }
      if ($this->has('name')) {
          $this->query->where('name', 'like', "%{$this->get('name')}%"); // Then less restrictive
      }
      

Debugging Tips

  1. Log Filtered Queries: Use Laravel's query logging to debug:

    \DB::enableQueryLog();
    $filter->apply($request->all());
    dd(\DB::getQueryLog());
    
  2. Inspect Request Data: Ensure filter parameters are being passed correctly:

    dd($request->all()); // Check incoming data
    
  3. Test Filters in Isolation: Write unit tests for filter logic:

    public function test_product_filter_applies_correctly()
    {
        $query = Product::query();
        $filter = new ProductFilter($query);
        $filter->apply(['category' => 1, 'price_min' => 10]);
    
        $expected = Product::query()->where('category_id', 1)->where('price', '>=', 10);
        $this->assertEquals($expected->toSql(), $filter->getQuery()->toSql());
    }
    

Configuration Quirks

  1. Custom Filter Classes:

    • Override the default filter class in config/laravel-filter.php:
      'filter_class' => \App\Filters\CustomFilter::class,
      
  2. Modular Filter Registration:

    • Register filters in a service provider for dependency injection:
      $this->app->bind(\App\Filters\ProductFilter::class, function ($app) {
          return new \App\Filters\ProductFilter(Product::query());
      });
      
  3. Handling Empty Filters:

    • Explicitly check for empty values to avoid SQL errors:
      if ($this->has('name') && !is_null($this->get('name'))) {
          $this->query->where('name', 'like', "%{$this->get('name')}%");
      }
      

Extension Points

  1. Custom Filter Operators: Extend the filter to support custom operators (e.g., contains, starts_with):
    class ProductFilter extends Filter
    {
        public function apply()
        {
            if ($this->has('name_contains')) {
                $this->query->where('name', 'like',
    
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.
anousss007/vigilance
supportpal/eloquent-model
ardenexal/fhir-models
laravel-at/laravel-image-sanitize
romalytar/yammi-audit-log-laravel
ardenexal/fhir-validation
arshaviras/weather-widget
laravel-chronicle/core
sunchayn/nimbus
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon