Unleashing the Power of Pipelines: A Custom PHP Implementation

Paul EdwardPaul Edward
5 min read

Do you remember the first time you saw a chain of pipes in Mario? Those mysterious and often amusing connections that could transport you between different worlds or even secret rooms? Today, we're going to talk about something similar, but in the fascinating world of PHP!

Recently, Laravel introduced its own Pipeline helper and I loved it, and it got me thinking: why not create a custom version of this helper that is framework-agnostic? That way, PHP developers all over the world could benefit from this powerful feature without having to be tied to a specific framework. So, I put on my coding hat, rolled up my sleeves, and set off on this adventure to share the joy of pipelines with everyone!

Before we dive into the code, let me give you a quick overview of what pipelines are and why you should care. In a nutshell, pipelines allow you to sequentially pass a value through a series of callable "pipes" (read: middleware, filters, or processors). Each pipe can modify the value before passing it along to the next one in the chain. This pattern is particularly useful for handling requests, processing data, or applying transformations in a clean, maintainable, and testable way.

Now, without further ado, let's take a look at our custom Pipeline class, which I lovingly call "Mario's Pipeline" in honor of our favorite plumber.

class Pipeline
{
    protected $passable;
    protected $pipes;
    protected $method = 'handle';

    public static function send($passable)
    {
        $pipeline = new static;

        $pipeline->passable = $passable;

        return $pipeline;
    }

    public function through(array $pipes)
    {
        $this->pipes = $pipes;

        return $this;
    }

    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes),
            $this->carry(),
            function ($passable) use ($destination) {
                return $destination($passable);
            }
        );

        return $pipeline($this->passable);
    }

    public function thenReturn()
    {
        return $this->then(function ($passable) {
            return $passable;
        });
    }

    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                if (is_callable($pipe)) {
                    return $pipe($passable, $stack);
                } elseif (is_object($pipe)) {
                    return $pipe->{$this->method}($passable, $stack);
                } elseif (is_string($pipe) && class_exists($pipe)) {
                    $pipeInstance = new $pipe;
                    return $pipeInstance->{$this->method}($passable, $stack);
                } else {
                    throw new \InvalidArgumentException('Invalid pipe type.');
                }
            };
        };
    }
}

Impressive, isn't it? Let's break it down step by step:

  1. send(): This static method initializes a new pipeline instance, sets the initial value of $passable, and returns the pipeline object. It's like the door that invites you into the pipeline world.

  2. through(): This method takes an array of pipes, which can be callables, objects, or class names. It's like choosing which pipes to connect in our pipeline chain.

  3. then(): The real magic happens here. This method takes a closure and applies each pipe to the passable value. It's like pushing Mario through the pipeline and watching him come out the other side with new abilities.

  4. thenReturn(): A convenient method that returns the final result of the pipeline. It's like seeing the prize at the end of the level.

  5. carry(): This protected method sets up the pipeline stack and handles different types of pipes. It's like the secret sauce that makes our pipeline work smoothly.

To use Mario's Pipeline, all you need to do is:

$result = Pipeline::send($input)
    ->through($pipes)
    ->thenReturn();

Voilà! You've successfully created and used a custom PHP pipeline, independent of any framework. Give yourself a high-five and celebrate with a slice of pizza, because you deserve it!

Let's dive into a practical example to demonstrate the power of Mario's Pipeline. Imagine we have a string that needs to be processed through a series of transformations: trim the input, capitalize the first letter of each word, and add an exclamation mark at the end. Here's how we can do it using our custom Pipeline class:

First, let's create three simple pipe classes to handle these transformations:

interface PipeInterface
{
    public function handle($passable, $next);
}


class TrimPipe implements PipeInterface
{
    public function handle($passable, $next)
    {
        $trimmed = trim($passable);
        return $next($trimmed);
    }
}

class CapitalizePipe implements PipeInterface
{
    public function handle($passable, $next)
    {
        $capitalized = ucwords(strtolower($passable));
        return $next($capitalized);
    }
}

class AddExclamationPipe implements PipeInterface
{
    public function handle($passable, $next)
    {
        $withExclamation = $passable . '!';
        return $next($withExclamation);
    }
}

class RemoveExtraSpacesPipe implements PipeInterface
{
    public function handle($passable, $next)
    {
        $withoutExtraSpaces = preg_replace('/\s+/', ' ', $passable);
        return $next($withoutExtraSpaces);
    }
}

Now that we have our pipes ready, let's pass our input string through them using Mario's Pipeline:

input = "   thE quiCk BroWn   fOx    ";
$pipes = [
    TrimPipe::class,
    CapitalizePipe::class,
    RemoveExtraSpacesPipe::class,
    AddExclamationPipe::class,
];

$result = Pipeline::send($input)
    ->through($pipes)
    ->thenReturn();

echo $result; // Output: "The Quick Brown Fox!"

And there you have it! Our input string has been successfully processed through the pipeline, transforming it into the desired output. This example demonstrates how you can use the Pipeline class to process data in a clean and maintainable way. You can easily add, remove, or reorder pipes in the $pipes array to modify the behavior of your pipeline, making it highly flexible and adaptable to your needs.

Remember, the sky's the limit when it comes to pipelines. You can create more complex pipes that handle authentication, validation, caching, or anything else your heart desires. With the power of Mario's Pipeline at your fingertips, you'll be unstoppable in your quest to conquer the PHP world!

In conclusion, pipelines offer a powerful way to organize and process data in your PHP applications. With this custom, framework-agnostic implementation, you can now enjoy the benefits of pipelines without being tied to Laravel or any other specific ecosystem. It's time to start exploring the exciting world of pipelines and unleash their full potential in your projects. Just don't forget to watch out for those pesky Piranha Plants and Banza in Super Mario bros!

Happy coding and a big credit to the Laravel Ecosystem!

11
Subscribe to my newsletter

Read articles from Paul Edward directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Paul Edward
Paul Edward