DEV Community

Cover image for How I Simplified My Laravel Filters Using the Pipeline Pattern (With Real Examples)
Jahid Hassan Shovon
Jahid Hassan Shovon

Posted on • Originally published at jahidhassan.hashnode.dev

How I Simplified My Laravel Filters Using the Pipeline Pattern (With Real Examples)

Today I learned a new topic in Laravel — Laravel Pipeline.
Let me first explain the problem I faced. In each of my repository files, I had to perform filtering in the index method. I noticed that in almost every filter, I was filtering by things like name, email, status, word, or similar fields — only the data source was different. I was doing the filtering like this:

Product::when(isset($request->status), function ($query) use ($request) {
    $query->where('status', $request->status);
})
->when(isset($request->search), function ($query) use ($request) {
    $query->where('name', 'like', '%' . $request->search . '%');
})
->when(isset($request->verify_status), function ($query) use ($request) {
    $query->where('verify_status', $request->verify_status);
})->get();

Enter fullscreen mode Exit fullscreen mode
Note: Similarly filter apply for more columns

The thing to notice here is that my data sources are different, but the rest of the processes are the same. After realizing this, I started looking for a way to optimize it and found an amazing concept called the Pipeline Pattern. By using this approach, I now need to write much less code in each index method, and the code has become more reusable.
Now I’ll show the state of the code after adding the Pipeline Pattern.

First step we need to create filter classes for each filter option.

Create a Filters folder under app directory. Remember all filter option file create here.
The filter classes will be created in the project/app/Filters directory.

Filter by Name -

project/app/Filters/ByName.php

<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;

class ByName
{
    public function __construct(
        protected Request $request
    ){}
    public function handle(Builder $builder, \Closure $next)
    {
        return $next($builder)->when(
            $this->request->has('name'), fn($query) => $query->where('name', 'like', '%'.$this->request->name.'%')
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Filter by Status -

project/app/Filters/ByStatus.php

<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class ByStatus
{
    public function __construct(
        protected Request $request
    ){}
    public function handle(Builder $builder, \Closure $next)
    {
        return $next($builder)->when(
            $this->request->has('status'), fn($query) => $query->where('status', $this->request->status)
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Filter by Address -

project/app/Filters/ByAddress.php

<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class ByAddress
{
    public function __construct(
        protected Request $request
    ){}
    public function handle(Builder $builder, \Closure $next)
    {
        return $next($builder)->when(
            $this->request->has('address'), fn($query) => $query->where('address', 'like', '%'.$this->request->address.'%')
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Second Step: How to use pipeline in controller or repositories file


<?php
namespace App\Repositories;
use Illuminate\Support\Facades\Pipeline;
use App\Filters\ByName;
use App\Filters\ByStatus;
use App\Models\Product;

class ProductRepository
{
    public function index($data)
    {
        $pipeline = [
            ByName::class,
            ByStatus::class,
            ByAddress::class,
        ];

        $product = Product::query();

        return Pipeline::send($product)
            ->through($pipeline)
            ->thenReturn()
            ->paginate($data['limit'] ?? 50);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • We use send($product) method to inject the initial data. In this case, the new created product.
  • The through($pipeline) method to provide the operations we want to execute with the input we are giving.
  • Finally, the then() runs the pipeline and returns the result.

Now I can reuse the filter options in other places as well. I just need to add the filter classes to the pipeline, and that’s it. The code has also become much cleaner. You should try it too — you’ll love it.

Now we will learn more about the pipeline pattern.

What is the Pipeline Design Pattern?
The Pipeline Design Pattern is a behavioral design pattern that passes data through a sequence of stages (called pipes). Each stage modifies or processes the data and then passes it along to the next one.

You can visualize it like this:

Input → [Pipe 1] → [Pipe 2] → [Pipe 3] → Output

Why Use the Pipeline Pattern?

  • Clean Code: It removes nested logic and long “if/else” chains, leading to more readable code.
  • Separation of Concerns: Each pipe does one thing (Single Responsibility Principle).
  • Reusability: Each pipe is independent and can be reused in different pipelines or projects.
  • Maintainability: Adding or removing a step in the process doesn’t break other parts — you just plug or unplug a pipe.
  • Testability: Each pipe can be tested individually, making unit testing easier.

How It Works

In Laravel, the Pipeline class (found in Illuminate\Pipeline\Pipeline) makes it easy to send data through a pipeline.

A pipeline has:

  • Data/Input – the initial object or request to process
  • Pipes – classes or closures that process the data
  • Destination/Closure – the final output step

Real-world Use Cases

  • Data transformation before saving
  • Building flexible API request handlers
  • Processing uploaded files
  • Business rule validation chains
  • Complex workflow processing (e.g., order processing pipeline)
  • Custom request handling before controller logic

More resources

Top comments (0)