Laravel: The hidden Pipeline [Part 4]

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

Italo Baeza Cabrera
5 min readOct 7, 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.

How the Pipeline works

This fourth part is for the curious ones. The practicality of the Pipeline, the Pipes and the Hub were already discussed and should be enough for anyone, but if you’re wondering how it accomplishes the pipelining, then keep reading.

Word of advice: this is very tricky to understand at first. Don’t worry if you need to re-read something.

The magic of the pipeline resides in the array_reduce() function. This function reduces an array to a single value. To do that, it executes a function on each array item. This function receives two parameters: the result from the function executed in the item before it, or an “initial value” before starting with the first, and the actual item on the array. The “reduced” value is the result of the function executed on the last item.

For example, let’s say we have a function (or callable) that sums the last result with next number, and multiplies the sum by two. We start with the number 1, and we put 0 as initial value.

The code would look like this:

$array = [1, 2, 3, 4];$callable = function ($carry, $value) {
return ($carry + $value) * 2;
}
$result = array_reduce($array, $callable, 0);echo $results; // 52

Executing the function gies us 52, which is explained in this professional-grade illustration:

Artistic illustration.

These are the basics. In the Pipeline, this basic mechanism is expanded in a tricky way.

  1. The first argument takes the pipe list and reverses its order, making the last pipe the first one, and vice versa. You will see why later.
  2. The second is the callable to use against each pipe. It basically says to use the Closure that returns the carry() method. More on that also later.
  3. The third and final argument is another Closure. This represent the “initial state” of the “passable” — the callable will be used against this Closure first.

This last Closure can be one set by the developer itself, or just a “return content” function that can be used to return the original content if the pipe is empty. This is automatically done when using thenReturn().

Now picture an onion with multiple layers.

Photo by Maria Hochgesang on Unsplash

The magic happens when the array_reduce() is executed. Every item, starting with the third parameter, will be crushed against a function, which returns another Closure that encloses the last Closure that returned last item on the array. Don’t think about processing a list, but rather, an onion.

Okay, that didn’t make much sense. Let break it down using the thenReturn() method.

  1. The initial value is a veric basic Closure that receives a value and returns the value (thanks, thenReturn()).
  2. The function receives this Closure as $stack, and the item as $pipe.
  3. In exchange, the function it returns a Closure that must receive the argument $passable, but will have access to both the $stack and $pipe internally. Since is a Closure, the code inside the brackets is inert until executed.
  4. If there is another item to process, it will call step 2, but the “carried” value will be the Closure (as $stack). If not, the returned value is this final Closure.

So, we have a Closure that has a Closure that has a Closure that has a Closure… you get the idea.

Remember that we reversed the array of pipes? Doing that makes the last pipe on the list passed to the Pipeline as the first layer of the onion, since is the first to be reduced but last to be accessed since its deep within this onion. By contrast, the most external layer becomes the first pipe to be executed.

And how we unwrap this final Closure? Well, this Closure accepts the data to be passed as argument. So the next line just executes the Closure while passing the data. The most external layer of this onion is executed: it will read the code of the pipe using the value passed, and then return the result.

Okay, we unwrapped the first Closure, but now we have to proceed with the next. Remember, the second argument of the Pipe is a Closure, commonly called $next, that you must unwrap (execute) and return using the data you have modified. Each layer does this: takes the data, modifies it, and then unwraps the next layer in direction to the core of this onion.

public function handle($passable, Closure $next)
{
return $next('I have altered the deal!');
}

And the first layer is the third argument of the array_reduce().

You could also unwrap everything in one layer, essentially making it a “after layer”.

public function handle($passable, Closure $next)
{
// Resolve all the other layers without getting out of here
$endResult = $next($passable);
return $endResult . ' and this is the end';
}

Holy shit I never thought that was possible.

--

--

Italo Baeza Cabrera

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