Write Better PHP Code with Anonymous Functions

Connor Forsyth
6 min readJun 30, 2017

Closures are my guilty pleasure. Maybe it was the fact that my first language at university was a functional one (good ol’ Haskell), but their simplicity and power both sing to me. I’m convinced in many ways that they’re one of the most underused but most powerful language features available. The technique is that of wielding a sharp knife, but one that can really fast-track us to head chef status if used responsibly.

In a previous post I briefly talked about these closures and that I use them to clean up unwieldy code, reduce dependencies and prettify my work.
There are a number of ways in which I use them, and I’ll outline those here.

Transformation

Borrowing techniques from the functional programming world, PHP has functions like array_map or array_filter. These allow you to pass an array of anything and get back some other data structure, or a subset of the array.

In order to understand why I think closures are a beautiful solution (that PHP have rightly used), I will demonstrate what the alternative would be, and then how we can clean up the implementation with closures.

Imagine we have an array of arrays (we’re keeping it simple here by not imagining how this applies to object instances yet), and we want to transform these into another structure. Without array_map and closures, we would end up with something like this:

function firstNames() {
$users = [
['id' => 1, 'name' => 'Harry Johnston'],
['id' => 2, 'name' => 'Patricia Smith'],
//...
];

$output = [];

foreach ($users as $user) {
$nameParts = explode(' ', $user['name']);
$firstName = $nameParts[0];
$lastName = $nameParts[1];

$output[] = [
'first_name' => $firstName,
'last_name' => $lastName,
];
}

return $output;
}

Since we are unlikely to reuse this code later, reaching out to create a full class seems cumbersome. Having said that, the underlying transformation that happens to each element in our $users array is the same, and reusable on a local scope. We can hit the sweet spot between DRY code and simplicity by using a quick function instead of a new Class to represent the data. This gives us clean, pretty code while restricting ourselves to a paddle instead of a sledgehammer.

/* Define this once and use forever. 
PHP has you covered by default,
but this is a naive implementation for the sake of explanation.
*/
function array_map($closure, $elements) {
$output = [];

foreach($elements as $element) {
$output[] = $closure($element);
}

return $output;
}

function firstNames() {
$users = [
['id' => 1, 'name' => 'Harry Johnston'],
['id' => 2, 'name' => 'Patricia Smith'],
//...
];

// We can use a closure to explain what we want to do to one
// element and let array_map worry about handling the iteration throughout
return array_map(function($user) {
$nameParts = explode(' ', $user['name']);
$firstName = $nameParts[0];
$lastName = $nameParts[1];

return [
'first_name' => $firstName,
'last_name' => $lastName
];
}, $users);
}

The results are more impressive when transforming collections or arrays of object instances that you know. You can even enforce type-hinting if you so desire, so that your IDE can help you build up the transformation, and crash out if you get unexpected data during the program’s execution.

function firstNames() {
$users = [
new User('Harry Johnston'),
new User('Patricia Smith'),
];

return array_map(function(User $user) {
return [
'first_name' => $user->getFirstName(),
'last_name' => $user->getLastName(),
];
}, $users);
}

By using your classes within an anonymous function, you benefit from reusable transformations, type-hinting to protect you from unexpected data (if you choose), and object-orientated access to really clean up your code. This example using an object is a far-cry from the first array example and I can only think of ways in which it has improved.

More readable, less lines of code, and less prone to error.

Lazy Loading

This is perhaps my favourite benefit and use-case of closures, since (as far as I’m aware), there’s no real alternative if you do want to lazy-load your classes and data.

By creating classes that expect closures to use as generator functions, we can use the given closure(s) and store them in memory. As soon as you need to get access to the real instance of a class, you call the closure and it returns one to you. Closures therefore provide an easy way to mask whether objects are singletons or not. The benefit of this technique is that you are not loading every class in your application into memory just to boot up.

Most dependency injection frameworks work in this way so that you can keep your application lean without restricting future growth.

Imagine you have a class that you give an array of Notification Drivers. This class is a Notifier service for domain-level events. If you only need to notify a user via slack in this request cycle, why would you want the Email and SMS drivers to be created and then discarded straight away?

You wouldn’t. Let’s look at an example.

Before

class Notifier {

protected $drivers;

public function __construct(array $drivers) {
// array consists of drivers such as: ['slack' => new SlackDriver()]
$this->drivers = $drivers;
}

public function notify(User $user, Event $event) {
$this->drivers[$user->preferedNotificationMethod() = 'slack']->notify($event);
}
}

After

class Notifier {

protected $drivers;

public function __construct(array $drivers) {
/*
array consists of closures such as:
[
'slack' => function() {
return new SlackDriver();
}
]
*/
$this->drivers = $drivers;
}

public function notify(User $user, Event $event) {
$driver = $this->drivers[$user->preferedNotificationMethod() = 'slack'];

// Only now does it create the slack driver and notify the user
$driver()->notify($event);
}
}

When creating services that use multiple drivers or that delegate to strategy classes, I recommend using lazy-loading like this religiously. Over time, consistent implementation will help keep your RAM footprint low and reduce response times to users.

Hiding Nasty Dependencies

Say, for example, you’re using Laravel. You want to fire an event, but only after you’ve modified the input a little. You want to let your listeners know, but you don’t want to depend on any queue implementation. Especially not anything laravel-specific, because you use this reusable package in multiple projects. You also don’t want to pollute your entities with framework globals such as event() because that’s half the reason you moved to Doctrine in the first place, right?

Well where there is a will, there is a way!

// Hook in to Doctrine2 events and perform custom logic on post-persist event
class DoctrinePostPersistLifecycleSubscriber implements EventSubscriber {
public function __construct(array $entityTypesMap) {
$this->map = $entityTypesMap;
/* = [
User::class => function(User $user) {
$this->app->make('event')->fire(new UserCreated($user));
}
]
*/
}

public function getSubscribedEvents(){
return ['postPersist'];
}

public function postPersist(LifecycleEventArgs $args) {
$entity = $args->getEntity();

$handler = $this->map[get_class($entity)];
$handler($entity); // fires a UserCreated event in Laravel with no knowledge of the laravel framework
}
}

// AppServiceProvider.php
public function register() {
$this->app->bind(DoctrinePostPersistLifecycleSubscriber::class, function() {
new DoctrinePostPersistLifecycleSubscriber([
// just change here to add more event handlers or extract this to a config file
User::class => function(User $user) {
$this->app->make('event')->fire(new UserCreated($user));
}
]);
});

$this->app->resolving(EntityManager::class, function($entityManager) {
$entityManager->getEventManager()->addEventSubscriber($this->app->make(DoctrineLifecycleSubscriber::class));
});
}

This might seem convoluted, and I’ll agree with you for small tasks.

However, if you can envisage firing events for many other entities in your project then as it grows, you will be able to simply just add another closure to the array. Magic!

This does harp back to my previous blog post a little — exercise restraint! Use this where it makes sense. On a throwaway project, don’t bother!

Do it only once the complexity of not doing it is a bigger issue.

Let me know if you want more blog posts like this straight to your inbox. No spam — just my attempts at good quality code.

Originally published at connorimrie.com on June 30, 2017.

--

--