Aug 17, 2017 | laravel, collections

Laravel Collections’ higher order messaging and "when" method in Laravel 5.4

Series

This is a series of posts on New Features in Laravel 5.4.

!
Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

It seems like it was just last year that collection pipelines took over the Laravel world. Taylor had introduced collections to Laravel a while back but they sat somewhat under-appreciated until Adam Wathan wrote his book Refactoring to Collections about how they can transform the way you write a lot of your PHP code.

In Laravel 5.4, collections got a few boosts. Let’s take a look at a few.

Higher Order Messaging

The biggest-name change is “higher order messaging”, an object-oriented design pattern described first in 2005 (“Higher Order Messaging”) and later implemented in Ruby (Mistaeks I Hav Made: Higher Order Messaging in Ruby) by Nat Pryce, co-author of GOOS.

What is Higher Order Messaging?

The best way to understand Higher Order Messaging is to walk through an example. I’m going to take the idea and shape of the code directly from Nat Pryce’s article, but adapt them for PHP and make them a little easier to follow. Thanks to Nat for his original writing.

Nat’s Claimants

We have a collection of Claimants who are receiving benefits from the government. A claimant has a name, a gender, an age, and an integer benefits that represents the total of their weekly monetary benefits.

Let’s say we want to add $50 a week to the benefits total for every claimant who is retired. We’re using the receiveBenefits() method to increase the benefits value.

First, we can iterate over it procedurally;

foreach ($claimants as $claimant) {
    if ($claimant->is_retired) {
        $claimant->receiveBenefit(50);
    }
}

The revolution that hit the Laravel world over the last year introduces the idea of higher-order functions, or functions which take a Closure, and how they can be used in collection pipelines. Here’s the same call using a collection pipeline:

collect($claimants)->filter(function ($claimant) {
    return $claimant->is_retired;
})->each(function ($claimant) {
    $claimant->receiveBenefit(50);
});

Great. If you’ve read Adam’s book or watched his course this isn’t news. But let’s take a look at the next step—not higher-order functions, but what is called higher-order messages:

collect($claimants)->filter->is_retired->each->receiveBenefit(50);

As Nat defines them:

A higher order message is a message that takes another message as an "argument". It defines how that message is forwarded on to one or more objects and how the responses are collated and returned to the sender. They fit well with collections; a single higher order message can perform a query or update of all the objects in a collection.

If all this talk of messages seems foreign, it would be worth reading up a bit on the idea of OOP as “message passing”. In short, when Nat is talking about messages here he’s (sort of) referring to method calls as “messages” which you assemble together in a sort of language — “claimants (filter is retired) each receive benefits” isn’t a perfect English sentence, but it’s definitely a series of messages sent to the claimants collection, not a bunch of implementation details.

I think Nat’s post does the best job of explaining the benefit we’re getting by converting this code sample to use higher order messaging:

[T]he code using higher order messages most succinctly expresses the business rule being executed. It expresses what is being performed and hides the details of how.

Using Higher Order Messaging with Laravel’s collection pipelines

You can already get a taste of how it works from my examples above. Essentially, instead of calling a collection method like filter() and giving it a Closure that returns the property from each object, you call each method (message) one after another and the Higher Order Messaging collection pipeline reads your intent and makes it work.

It’s a little hard to describe perfectly—how are both "filter" and "is_retired" messages? Essentially, when you call collection methods like filter using their higher order messaging syntax ($collection->filter instead of $collection->filter(...)) they’re now set to expect the next string in the call stack to be a “message” passed to them. If I’m filter being called in a higher order messaging context, I expect the next string down the call stack to be a property or method that I’ll call on each item for my filter truthiness test.

An example

class Person
{
    public $isAdmin;

    public function __construct($isAdmin)
    {
        $this->isAdmin = $isAdmin;
    }

    public function isAdmin()
    {
        return (bool) $this->isAdmin;
    }
}

$people = collect([new Person(false), new Person(true)]);

// Filter against a prop
$people->filter->isAdmin;

// ... same as:
$people->filter(function ($person) {
    return $person->isAdmin;
});

// Filter against a method
$people->filter->isAdmin();

// ... same as:
$people->filter(function ($person) {
    return $person->isAdmin();
});

So, practically, higher order messaging in Laravel collections simplifies a few extremely common syntaxes for passing properties or methods into collection methods like filter and each. These changes make the code simpler and more expressive and everyone wins.

Collection@when

If you’re not familiar with the already-existing pipe() method in Laravel’s collections, here’s how it works: the pipe() method’s Closure is passed the entire current collection as a parameter, and whatever you return from that Closure will replace the collection.

return collect($peopleArray)
    ->sort('age')
    ->pipe(function ($people) {
        // Final collection is run through the transformer
        // and then the output of that is returned
        return app('peopleTransformer')->transform($people);
    });

The new when() method is the same, except it’s conditional. To understand the when() method, just take the pipe() method and (in your head) modify it to it accept a first parameter; if that parameter is truthy, run the second parameter Closure as a pipe() method. If it’s falsey, ignore the entire when() call and keep moving. That’s the when() method.

return collect($peopleArray)
    ->sort('age')
    ->when(request()->wantsJson(), function ($people) {
        // Final collection is run through the transformer
        // and then the output of that is returned
        return app('peopleTransformer')->transform($people);
    });

Collection@end

That’s it! More ways collection pipelines can make your code cleaner, terser, more expressive, and more elegant.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  collections


This is part of a series of posts on New Features in Laravel 5.4:

  1. Feb 3, 2017 | laravel 5.4, Dusk
  2. Feb 8, 2017 | laravel, laravel 5.4, mix
  3. Aug 22, 2017 | laravel, laravel 5.4, Facades

Subscribe

For quick links to fresh content, and for more thoughts that don't make it to the blog.