Laravel: The hidden Pipeline [Part 2]

Serialized content processing, meet your new friend: the Pipeline.

Italo Baeza Cabrera
4 min readSep 23, 2019

In this series of articles, I will talk about the Pipeline package that comes with Laravel but nobody uses it and it’s not documented. Why? Because it’s amazing.

Pipes

The Pipes are second most important thing of the Pipeline itself. As we saw in the article earlier, we can use a callable, an object instance, an invokable class, or the Class name to be instanced using the Service Container.

A Class name is super useful when your Pipe depends on Services to work. Let’s say, a Pipe needs the Database to compare something, or the Cache to store some kind of result for a while to accelerate the logic. The Pipeline will automatically resolve its dependencies when is instanced.

A basic and classic Pipe may look like this:

Handling the data

When the Pipeline calls each Pipe, it will receive two arguments: the data being passed, and a Closure.

public function handle($passable, Closure $next)
{
// Your logic
}

You can do whatever you want inside the handling method to modify the content. For example, we will just make the first character uppercase.

public function handle($passable, Closure $next)
{
$passable = ucfirst($passable);
}

Now that we have modified our data — remember, only object instances are passed by reference — the next step is to tell the Pipeline to keep going with the data we modified, otherwise everything will stop here.

For that, we call the Closure that represents the next pipe in line and pass our modified data as the argument.

public function handle($passable, Closure $next)
{
$passable = ucfirst($passable);
return $next($passable);
}

This must be done in all the pipes, even if it is the last one, otherwise it won’t advance.

Jumping to the end

One of the magics tricks of a pipe is that it can automatically “jump” to the end. This work pretty much like an “After Middleware”: we will execute our code after the pipeline reaches the end result.

public function handle($passable, Closure $next)
{
$result = $next($passable);
// Do something with the result return $result;
}

This is a great alternative when you are adding pipes dynamically instead of hardwiring them somewhere. You don’t need to explicitly add a Pipe at the end. Instead, you just put a Pipe that takes the end result and works from there, no matter where it sits in the line.

Magic of pipe arguments

One thing the Pipeline doesn’t explicitly mention, but you can guess, is how you can use arguments in each pipeline when resolving them. This won’t work when you pass an object instance or a callable (like a Closure).

For example, let’s say one of our pipes is a class that deletes words. To make it actually useful, it should receive a list of words we want to delete.

For that matter, we can use a string with the syntax 'Namespace\Class:foo,bar'. This will force the pipe to be instanced through the Service Container (even if it doesn’t use any service of it) and pass them from the third argument onwards. The parsing of the class is done automatically by the Pipeline class itself.

$result = app(\Illuminate\Pipeline\Pipeline::class)
->send(’this should be correctly formatted’)
->through([
// ...
'App\Strings\Pipes\RemoveWords:should,formatted'
])->thenReturn();
echo $result; // "this be correctly"

Then, we can use the arguments in our RemoveWords class:

/**
* Removes a list of words
*
* @param string $string
* @param Closure $next
* @param array $remove
* @return string
*/
public function handle($string, Closure $next, ...$remove)
{
return $next(str_replace($remove, '', $string));
}

If the Class was registered in the Service Container with a friendly name, like image, replacer or remover, we can use that name instead of the Class:

$result = app(\Illuminate\Pipeline\Pipeline::class)
->send('this should be correctly formatted')
->through([
// ...
'replacer:should,formatted'
]);

Sounds familiar? It’s because the Router of your application uses the Pipeline for the middlewares. It’s the same syntax:

Route::get('podcast/{podcast}', 'PodcastController@show')
->middleware('auth:api');

A word of advice with Responses

The Pipeline was originally made to handle an HTTP Response, but I gladly made a PR along with some recommendations so in Laravel 6 and onwards it should handle anything. It’s also more flexible in terms of debugging and handling exceptions.

Take into account that, as for Laravel 5.8, if you use this Pipeline class and the outgoing object is a instance of Responsable, it will be automatically transformed to a Response for the next pipe, no contest. In other words, you should avoid using an instance of Responsable when using the Pipeline:

return $response instanceof Responsable 
? $response->toResponse($this->getContainer()->make(Request::class))
: $response;

Luckly, my PR was approved, and there is no more need to avoid using the Responsable instance in the Pipeline.

That’s are the Pipes, next is the Hub.

--

--

Italo Baeza Cabrera
Italo Baeza Cabrera

Written by Italo Baeza Cabrera

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com

Responses (3)