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>></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: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">=></span></span></span></span>user);
- We use
send($user)
method to inject the initial data. In this case, the new created user. - The
through([...])
method to provide the operations we want to execute with the input we are giving. - Finally, the
then()
runs the pipeline and returns the result.
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>></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->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">></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:
- The
pipe($pipes)
method enables you to add extra pipes into the pipeline. For 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>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>></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: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">=></span></span></span></span>user);
- The
via($method)
method allows you to set the method to call on the pipes. By default, it is set to handle. - The
thenReturn()
method runs the pipeline and automatically returns the result:
// 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>></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: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">=></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();
- The
setContainer()
method enables you to set the container instance, this can be useful if you want to manually instantiate thePipeline
class, for instance:
// 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