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
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.
