php
45 lines · 9 steps
Building a filtered product index in Laravel
How Eloquent's when() method conditionally chains query filters from validated request input.
Explained by
highlit
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Models\Product;
6use Illuminate\Http\Request;
7
8class ProductController extends Controller
9{
10 public function index(Request $request)
11 {
12 $filters = $request->validate([
13 'search' => ['nullable', 'string', 'max:255'],
14 'category_id' => ['nullable', 'integer', 'exists:categories,id'],
15 'min_price' => ['nullable', 'numeric', 'min:0'],
16 'max_price' => ['nullable', 'numeric', 'gte:min_price'],
17 'in_stock' => ['nullable', 'boolean'],
18 'sort' => ['nullable', 'in:newest,price_asc,price_desc'],
19 ]);
20
21 $products = Product::query()
22 ->with('category')
23 ->when($filters['search'] ?? null, function ($query, $search) {
24 $query->where(function ($q) use ($search) {
25 $q->where('name', 'like', "%{$search}%")
26 ->orWhere('sku', 'like', "%{$search}%");
27 });
28 })
29 ->when($filters['category_id'] ?? null, fn ($query, $id) => $query->where('category_id', $id))
30 ->when($filters['min_price'] ?? null, fn ($query, $min) => $query->where('price', '>=', $min))
31 ->when($filters['max_price'] ?? null, fn ($query, $max) => $query->where('price', '<=', $max))
32 ->when($request->boolean('in_stock'), fn ($query) => $query->where('stock', '>', 0))
33 ->when($filters['sort'] ?? 'newest', function ($query, $sort) {
34 match ($sort) {
35 'price_asc' => $query->orderBy('price'),
36 'price_desc' => $query->orderByDesc('price'),
37 default => $query->latest(),
38 };
39 })
40 ->paginate(24)
41 ->withQueryString();
42
43 return view('products.index', compact('products', 'filters'));
44 }
45}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Validating request input up front turns untrusted query parameters into a safe, typed array you can trust downstream.
- 2when() lets you assemble a query conditionally without a tangle of if statements, applying each filter only when its value is present.
- 3Pairing paginate() with withQueryString() keeps active filters intact as users move between pages.
Related explainers
php
<?php namespace App\Models\Scopes;
How a tenant global scope works in Laravel
multi-tenancy
global-scope
query-builder
Intermediate
5 steps
rust
use axum::{ extract::{Query, State}, http::StatusCode, Json,
Paginated, filtered product listing in Axum
pagination
query-parameters
sql-filtering
Intermediate
8 steps
ruby
class Api::V1::ArticlesController < Api::V1::BaseController def index articles = Article .published
Building a JSON:API endpoint in Rails
serialization
eager-loading
pagination
Intermediate
7 steps
php
<?php namespace App\Support;
Retry with exponential backoff in PHP
retry
exponential-backoff
error-handling
Intermediate
7 steps
php
<?php declare(strict_types=1);
Validating registration input with filter_var
validation
sanitization
filter_var
Intermediate
8 steps
python
import time from dataclasses import dataclass, field from fastapi import Depends, FastAPI, HTTPException, Request, status
Token-bucket rate limiting in FastAPI
rate-limiting
token-bucket
dependency-injection
Advanced
10 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-filtered-product-index-in-laravel-explained-php-ff18/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.