Skip to main content

Sebastiaan Luca

Enabling PHP method chaining with a makeshift pipe operator

December 24, 2016
4 minute read
This blog post was written more than a year ago. Be wary of any technologies that might have changed and if no better alternatives exist at the time of viewing this post.

With the rise of active record and Laravel collections among other, many of us enjoy the possibility of fluently chaining method calls on an object. While this is being supported more and more, it requires implementation by the package or framework maintainer and is not possible for native values nor methods.

Photo by Victor Garcia on Unsplash

Update: more than a year after blogging about this, I extracted the pipe operator helper to its own package and added a bunch of new features. Check the repository readme for more up-to-date information!


An interesting RFC proposal by Sara Golemon submitted in April 2016 suggested the use of a pipe operator to enable method chaining for any value and method. Yet as of today, it's still being discussed and there's no saying if it will ever make its way into PHP. So in the meantime, here's a solution!

A simple example

Let's say we want to get the subdomain of a given URL in a proper way without using any regex. As the RFC demonstrates, such a task can be handled in a few different ways.

Intermediate variables

First up is the use of intermediate, temporary variables to split the process into multiple steps. This makes it easier to read, from top to bottom, yet pollutes our code with redundant variables. This might be ok for small tasks that take up one or two lines, but doesn't apply to our multi-line example:

$subdomain = 'https://blog.sebastiaanluca.com/';
$subdomain = parse_url($subdomain, PHP_URL_HOST);
$subdomain = explode('.', $subdomain);
$subdomain = reset($subdomain);

The confusing one-liner

The next solution is to just cram it all into one line and be done with it:

$subdomain = explode('.', parse_url('https://blog.sebastiaanluca.com/', PHP_URL_HOST))[0];

This might be the worst of all solutions, as it requires you to start reading from the center, work your way towards the outer methods, and keep switching back and forth when you get values from an array using $array[0], which come at the end of the line.

If you call a lot of methods like in our example, this can be quite hard to read and grasp at first, especially with all the parenthesis and square brackets.

Chaining it up

Using method chaining, we can skip the complicated, redundant code and write it more fluently:

$subdomain = take('https://blog.sebastiaanluca.com/')
    ->pipe('parse_url', PHP_URL_HOST)
    ->pipe('explode', '.', '$$')
    ->pipe('reset')
    ->get();

As you can see, this doesn't require any temporary variables and the methods calls can now be read from left to right. Just remember that each first pipe parameter is the function name and if you don't pass a $$ string, the first parameter that function receives will be the piped value (but more on that further down).

Using closures and calling class methods

An additional feature is that since the function name is essentially a generic callable, you can also pass a closure or [$this, 'myMethod'] as first parameter, which would call the public method myMethod on the current class (similar to user_func_call_array()):

To transform our subdomain to upper case using an anonymous function:

$subdomain = take('https://blog.sebastiaanluca.com/')
    ->pipe('parse_url', PHP_URL_HOST)
    ->pipe('explode', '.', '$$')
    ->pipe('reset')
    ->pipe(function(string $value) {
        return mb_strtoupper($value);
    })
    ->get();

Or prefix it using a class method:

public function method(string $subdomain) : string
{
    return take($subdomain)
        ->pipe('parse_url', PHP_URL_HOST)
        ->pipe('explode', '.', '$$')
        ->pipe('reset')
        ->pipe([$this, 'prefix'])
        ->get();
}

public function prefix(string $string) : string
{
    return 'This is my ' . $string;
}

A detailed walk-through

Yet this new approach can be a bit daunting at first, so let's look at each segment from our initial example separately and explain what's happening.

To initiate the chain, it needs that initial value to start with. Using take($value) and passing the value, we create a new pipe item to call methods on:

take('https://blog.sebastiaanluca.com/')

Next, we'll use PHP's native parse_url(string $url, int $component = -1) function to split the URL into different segments. Instead of passing our URL to the parse method by wrapping it, we simply pipe it through. All we need to do here is specify the method to call and that we want the host value from the resulting array.

->pipe('parse_url', PHP_URL_HOST)

At the end of that call, our URL has been transformed once and its value is now the result of parse_url, our base domain (or host) blog.sebastiaanluca.com.

With that in mind, we can take the previous value and split it on the dots into an array of strings so we get each part of the domain separately. For this, we can use explode(string $delimiter, string $string):

->pipe('explode', '.', '$$')

As you can see, the first parameter is not our piped value, but the delimiter to split the string on. To resolve this predicament, we forfeit the benefit of the pipe object auto-resolving the piped value as first parameter like in previous calls and just pass all parameters to the explode function in sequential order. In this pipe call, our method comes first (as usual), then the delimiter (.), and finally our piped value, represented as $$, similar to the original RFC.

Ultimately, we call reset(array &$array) to get the first item of our split base domain: the subdomain. The final get() call will end the chain and return the current value that went through all the previous steps.

->pipe('reset')->get()

Using it in your project

If you're keen to try it out yourself, this helper is available in the laravel-helpers package (but can be used outside of the Laravel framework too). You can start using it in your project by installing it through composer or copying the source file to your project directory:

composer require sebastiaanluca/laravel-helpers=dev-develop

Stay tuned for the next post where we'll take a look at some other helper methods from that same package. As always, feedback is appreciated!