How to build sequences with Laravel pipelines (original) (raw)

ByLuis Güette

Recently, the Laravel team added a section in the documentation about pipelines, and I think it is a good opportunity to talk about pipelines and how useful they are.

But first, what are pipelines?

Pipelines are a design pattern that enables the creation and execution of a sequence of operations. Much like an assembly line, where each step prepares a product for the next station along the line, pipelines are a group of functions linked together, so the output of the preceding function serves as the input of the following function. Laravel employs this pattern internally, such as in middleware.

How do pipelines work?

There is a Pipeline facade which provides a couple of useful methods to pipe a given input through a series of invokable classes or closures. Here is an example:


use App\Models\User;

use Illuminate\Support\Facades\Pipeline;

$user = User::create([...]);
 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>s</mi><mi>e</mi><mi>r</mi><mo>=</mo><mi>P</mi><mi>i</mi><mi>p</mi><mi>e</mi><mi>l</mi><mi>i</mi><mi>n</mi><mi>e</mi><mo>:</mo><mo>:</mo><mi>s</mi><mi>e</mi><mi>n</mi><mi>d</mi><mo stretchy="false">(</mo></mrow><annotation encoding="application/x-tex">user = Pipeline::send(</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord mathnormal">i</span><span class="mord mathnormal">p</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">in</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">::</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">se</span><span class="mord mathnormal">n</span><span class="mord mathnormal">d</span><span class="mopen">(</span></span></span></span>user)

    ->through([

        SendWelcomeEmail::class,

        SubscribeToNewsletter::class,

        GenerateAvatar::class,

])

    ->then(fn (User <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>s</mi><mi>e</mi><mi>r</mi><mo stretchy="false">)</mo><mo>=</mo><mo>&gt;</mo></mrow><annotation encoding="application/x-tex">user) =&gt; </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=&gt;</span></span></span></span>user);

For each invokable class, two parameters are received: the input and the $next closure. Here is an example:


use Closure;

use App\Models\User;

class SendWelcomeEmail

{

    public function handle(User <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>s</mi><mi>e</mi><mi>r</mi><mo separator="true">,</mo><mi>C</mi><mi>l</mi><mi>o</mi><mi>s</mi><mi>u</mi><mi>r</mi><mi>e</mi></mrow><annotation encoding="application/x-tex">user, Closure </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">Cl</span><span class="mord mathnormal">os</span><span class="mord mathnormal">u</span><span class="mord mathnormal">re</span></span></span></span>next)

    {

        <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>s</mi><mi>e</mi><mi>r</mi><mo>−</mo><mo>&gt;</mo><mi>n</mi><mi>o</mi><mi>t</mi><mi>i</mi><mi>f</mi><mi>y</mi><mo stretchy="false">(</mo><mi>n</mi><mi>e</mi><mi>w</mi><mi>I</mi><mi>n</mi><mi>v</mi><mi>o</mi><mi>i</mi><mi>c</mi><mi>e</mi><mi>P</mi><mi>a</mi><mi>i</mi><mi>d</mi><mo stretchy="false">(</mo></mrow><annotation encoding="application/x-tex">user-&gt;notify(new InvoicePaid(</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mord">−</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&gt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">n</span><span class="mord mathnormal">o</span><span class="mord mathnormal">t</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mopen">(</span><span class="mord mathnormal">n</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span><span class="mord mathnormal">o</span><span class="mord mathnormal">i</span><span class="mord mathnormal">ce</span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord mathnormal">ai</span><span class="mord mathnormal">d</span><span class="mopen">(</span></span></span></span>invoice));

        <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi><mi>e</mi><mi>x</mi><mi>t</mi><mo stretchy="false">(</mo></mrow><annotation encoding="application/x-tex">next(</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">n</span><span class="mord mathnormal">e</span><span class="mord mathnormal">x</span><span class="mord mathnormal">t</span><span class="mopen">(</span></span></span></span>user);

    }

}

When you invoke the $next closure, the next invokable class in the pipeline will be invoked. Additionally, there other useful methods you can use to setup the pipeline:


use App\Models\User;

use Illuminate\Support\Facades\Pipeline;

$user = User::create([...]);
 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>p</mi><mi>i</mi><mi>p</mi><mi>e</mi><mi>l</mi><mi>i</mi><mi>n</mi><mi>e</mi><mo>=</mo><mi>P</mi><mi>i</mi><mi>p</mi><mi>e</mi><mi>l</mi><mi>i</mi><mi>n</mi><mi>e</mi><mo>:</mo><mo>:</mo><mi>s</mi><mi>e</mi><mi>n</mi><mi>d</mi><mo stretchy="false">(</mo></mrow><annotation encoding="application/x-tex">pipeline = Pipeline::send(</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">p</span><span class="mord mathnormal">i</span><span class="mord mathnormal">p</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">in</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord mathnormal">i</span><span class="mord mathnormal">p</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">in</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">::</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">se</span><span class="mord mathnormal">n</span><span class="mord mathnormal">d</span><span class="mopen">(</span></span></span></span>user)

    ->through([

        GenerateAvatar::class,

    ]);

if (! $user->isAdmin) {

    $pipeline->pipe([

        SendWelcomeEmail::class,

        SubscribeToNewsletter::class,

    ]);

}
 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>s</mi><mi>e</mi><mi>r</mi><mo>=</mo></mrow><annotation encoding="application/x-tex">user = </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span></span></span>pipeline->then(fn (User <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>s</mi><mi>e</mi><mi>r</mi><mo stretchy="false">)</mo><mo>=</mo><mo>&gt;</mo></mrow><annotation encoding="application/x-tex">user) =&gt; </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=&gt;</span></span></span></span>user);


// Instead of using then()
 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>s</mi><mi>e</mi><mi>r</mi><mo>=</mo><mi>P</mi><mi>i</mi><mi>p</mi><mi>e</mi><mi>l</mi><mi>i</mi><mi>n</mi><mi>e</mi><mo>:</mo><mo>:</mo><mi>s</mi><mi>e</mi><mi>n</mi><mi>d</mi><mo stretchy="false">(</mo></mrow><annotation encoding="application/x-tex">user = Pipeline::send(</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord mathnormal">i</span><span class="mord mathnormal">p</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">in</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">::</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">se</span><span class="mord mathnormal">n</span><span class="mord mathnormal">d</span><span class="mopen">(</span></span></span></span>user)

    ->through([...])

    ->then(fn (User <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>s</mi><mi>e</mi><mi>r</mi><mo stretchy="false">)</mo><mo>=</mo><mo>&gt;</mo></mrow><annotation encoding="application/x-tex">user) =&gt; </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=&gt;</span></span></span></span>user);

// You can use thenReturn()
 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>s</mi><mi>e</mi><mi>r</mi><mo>=</mo><mi>P</mi><mi>i</mi><mi>p</mi><mi>e</mi><mi>l</mi><mi>i</mi><mi>n</mi><mi>e</mi><mo>:</mo><mo>:</mo><mi>s</mi><mi>e</mi><mi>n</mi><mi>d</mi><mo stretchy="false">(</mo></mrow><annotation encoding="application/x-tex">user = Pipeline::send(</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.02778em;">ser</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord mathnormal">i</span><span class="mord mathnormal">p</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">in</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">::</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">se</span><span class="mord mathnormal">n</span><span class="mord mathnormal">d</span><span class="mopen">(</span></span></span></span>user)

    ->through([...])

    ->thenReturn();


// Using the setContainer() method

(new Pipeline)

    ->setContainer(app())

// But, you can also pass the container instance in the class constructor

(new Pipeline(app()))

Laravel pipelines are a powerful tool that allows developers to easily build and execute a sequence of operations in a clean and organized way. They are flexible and can be customized to fit any use case, making them a valuable addition to any Laravel project. By leveraging the pipeline pattern, developers can improve the scalability and maintainability of their code while also making it easier to implement complex and multi-step processes.

Laravel’s documentation on pipelines: https://laravel.com/docs/10.x/helpers#pipeline

Luis Güette

Software Developer

Author Image