The pipeline pattern is one way to refactor your code to separate concerns. Here is an example of un-refactored code that uses individual if statements to process some data. I’ll keep the details out for clarity.

<?php

class Processor
{
    public function process(int $typeCode)
    {
        if ($typeCode === 1) {
            $this->processA();
        }

        if ($typeCode === 2) {
            $this->processB();
        }

        if ($typeCode === 3) {
            $this->processC();
        }
    }

    private function processA(): void
    {
        // Logic here...
    }

    private function processB(): void
    {
        // Logic here...
    }

    private function processC(): void
    {
        // Logic here...
    }
}

This code seems simple enough, right? But if you begin to fill in the details in your mind, you can imagine that each of the private methods could contain a lot of code, and even calls to other private methods. The file would quickly begin to grow in size, but the real reason we want to refactor is Separation of Concerns.

Here is a diagram that shows what this architecture looks like:

Notice three things. First, taking a high level look, you’ll notice that there is a lot going on here, which makes it harder to follow. Second, there is duplication across the three branches. Finally, notice the colors in each branch. I chose those to show that the code in each group does not care about the other branches. This is a clear indicator that we need to break up this party.

What we want should look more like this:

The part on the right shows what goes on with the pipeline. Each of the white blocks within is its own class, containing the logic for each “filter” in the pipe. We pass the typeCode into the pipe, which passes it to each filter in turn. (We define the filters in an array, and can control the order in which they process.) In this design, each filter looks at the typeCode and determines if it cares about it. If it does, it runs its code then exits. If it does not care, it simply exits early. Then the next filter does the same, and so on…

By writing the code this way, we have simplified the Processor class, and introduced three new classes, each of which cares about one and only one thing, and knows nothing about the other two. We have separated the concerns.

Here is what the Processor class will look like now:

<?php

use Illuminate\Pipeline\Pipeline;

class Processor
{
    /**
     * @var Pipeline
     */
    private $pipeline;

    public function __construct(Pipeline $pipeline)
    {
        $this->pipeline = $pipeline;
    }
        
    public function process(int $typeCode)
    {
        $filters = [
            ProcessA::class,
            ProcessB::class,
            ProcessC::class,
        ];

        $this->pipeline
            ->send($typeCode)
            ->through($filters)->thenReturn();
    }
}

We can immediately see how much simpler this class became. I put the $filters array in the process method here, but of course that could be passed in or declared in a config file somewhere.

Let’s take a look at one of the filter classes:

<?php

use Closure;

class ProcessorA
{
    public function handle(int $typeCode, Closure $next)
    {
        // If this is a type code we don't care about, exit early
        if ($typeCode !== 1) {
            return $next($typeCode);
        }
        
        // Do whatever processing is needed here


        return $next($typeCode);
    }
}

The basic idea is that if you don’t care about the type code you were just handed, you pass control back to the pipeline, passing the typeCode along. We use the variable $next to capture the idea that it is passing to the next filter in the pipe…

If it is a code you care about, you do the work needed. I didn’t show any details here, but the same engineering principles as usual apply here as well, meaning this class will use injection, and separation of concerns too.

You will eventually end up with a more complex, but clear and easy to follow diagram like this:

The Pipeline Pattern