Laravel Collections’ higher order messaging and "when" method in Laravel 5.4
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 Claimant
s 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:
-
Feb 3, 2017 | laravel 5.4, Dusk
-
Feb 8, 2017 | laravel, laravel 5.4, mix
-
Aug 17, 2017 | laravel, collections
-
Aug 22, 2017 | laravel, laravel 5.4, Facades