Mastering Laravel Pipelines for Clean, Reusable Code

Overview

The pipeline pattern is a design strategy that allows you to pass an initial object through a series of discrete stages, or "pipes." Each stage performs a specific operation on the object and then passes it to the next. This matters because it transforms complex, nested logic—like a long chain of if statements for database filtering—into a flat, readable array of actions. It promotes the Single Responsibility Principle, ensuring each class does exactly one thing.

Prerequisites

To get the most out of this tutorial, you should be comfortable with

and the
Laravel
framework. Specifically, you should understand
Eloquent
query builders, HTTP requests, and the concept of
Middlewares
, as the pipeline syntax is almost identical to how Laravel handles web requests.

Key Libraries & Tools

  • Laravel Pipeline Facade
    : The core utility used to send an object through a stack of classes.
  • Tinkerwell
    : A code runner for PHP that allows for quick experimentation with snippets.
  • Eloquent
    : The object typically being modified in our filtering examples.

Code Walkthrough

1. The Basic Closure Pipeline

You can start simple by passing a string through closures to sanitize data.

use Illuminate\Support\Facades\Pipeline;

$result = Pipeline::send("  Hey you :)  ")
    ->through([
        function ($content, $next) {
            return $next(trim($content));
        },
        function ($content, $next) {
            return $next(str_replace(":)", " "), $content));
        }
    ])
    ->thenReturn();

Here, send() sets the initial data, through() defines the stages, and thenReturn() executes the chain. Each stage must call $next($content) to keep the flow moving.

2. Implementation via Dedicated Filter Classes

For production apps, moving logic into classes is best practice. Let's create a SearchFilter to clean up a controller.

class SearchFilter
{
    public function __construct(protected ?string $value) {}

    public function handle($query, $next)
    {
        if (!$this->value) {
            return $next($query);
        }

        $query->whereLike('name', $this->value);
        
        return $next($query);
    }
}

In your controller, you can now swap messy conditionals for a clean pipeline:

$items = Pipeline::send(Item::query())
    ->through([
        new Filters\SearchFilter($request->search),
        new Filters\CategoryFilter($request->category),
        new Filters\BestDealFilter($request->best_deal),
    ])
    ->thenReturn()
    ->get();

Syntax Notes

Laravel pipelines support both handle() methods and invocable classes (__invoke). The signature must always accept the pass-through object and the next closure. Note the use of thenReturn(), which is a shorthand for then(fn($object) => $object).

Practical Examples

Beyond database filtering, pipelines are perfect for order processing (validating stock, calculating tax, applying coupons) or content transformation (converting Markdown to HTML, then syntax highlighting, then minifying).

Tips & Gotchas

Always include an early return in your filter classes. If a filter value is null and you don't check for it, you might accidentally modify your query in unintended ways. Also, ensure you are importing the Illuminate\Support\Facades\Pipeline facade and not the internal routing pipeline, as their signatures can differ slightly.

3 min read