twitter rss github stackoverflow
Joseph Silber's Avatar

Joseph Silber

The new Closure::fromCallable() in PHP 7.1

With PHP 5.5 going EOL earlier this week and the PHP 7.1 beta expected later this month, now sounds like a good time to look into a neat little feature coming in 7.1: easily converting any callable into a proper Closure using the new Closure::fromCallable() method.

A short refresher on closures in PHP

If you're already familiar with closures in PHP, you can skip right ahead.

PHP has had closures since version 5.3. Think of it as similar to an anonymous function in Javascript; they're usually passed off to another method, which either calls it directly or stores it for later use.

As an example, let's look at Laravel's collection class. It has a reject method, which you can use to filter out values you don't want in the collection:

$ages = collect([3, 7.5, 12, 19.5, 23]);

$adults = $ages->reject(function ($age) {
    return $age < 18;
});

Behind the scenes, the reject method will call our closure callback for every item in the collection. If the age is less than 18, it will be rejected from our collection.

When you need to convert a callable to a Closure

Now imagine you want to reject every value in the collection that is not a whole number. Basically, reject all floats. There's a built-in is_float function, so why don't we pass that in directly?

$ages = collect([3, 7.5, 12, 19.5, 23]);

$integers = $ages->reject('is_float');

Unexpectedly, this will not work. Why? In addition to a closure, Laravel also allows you to pass in a single value to reject, which will remove that value from the collection. Since we've passed it a string, Laravel thinks we want to remove the 'is_float' item from the collection. Whoops!

The way around this is to actually pass in a closure and call is_float from within our closure callback:

$ages = collect([3, 7.5, 12, 19.5, 23]);

$integers = $ages->reject(function ($age) {
    return is_float($age);
});

Introducing Closure::fromCallable()

In PHP 7.1, the Closure class has a new static fromCallable method, which automatically creates a Closure from any callbable you pass it. Using this new method, we can clean up our code a bit:

$ages = collect([3, 7.5, 12, 19.5, 23]);

$integers = $ages->reject(Closure::fromCallable('is_float'));

Pretty neat!

Creating a closure that wraps a private method

Another scenario where you'd want a closure is if you want to pass in an array-callable of a private method. As mentioned in the PHP docs, you can create a callable to a method on an object by using an array with this format: [$object, 'method']. This can be quite convenient when using a method on your class that has the logic for the callback:

$due = $orders->filter([$this, 'isDue']);

This works well, with the only caveat being that the isDue method now has to be made public - otherwise the collection class won't be able to access it.

This is where Closure::fromCallable() once again comes to the rescue. When used from within a class, the resulting closure will automatically be bound to that class, allowing it to access private methods:

$due = $orders->filter(Closure::fromCallable([$this, 'isDue']));

The isDue method can now stay private or protected, but the collection will still be able to access it through the wrapping closure!

None of this is really earth shattering, but it's a neat little addition that can come in handy every so often.


Questions? Comments? Complaints? Ping me on Twitter.

Recent posts

  1. Releasing Bouncer: Roles and Permissions for Laravel apps

    A personal post with some musings about my journey through the years - from inception to final release

    Read more ➺

  2. Lazy Collections in Laravel

    A deep dive into Laravel's Lazy Collections, and how to process enormous amounts of data with very little memory.

    Read more ➺

  3. How to rid your database of PHP class names in Eloquent's Polymorphic tables

    Using Relation::morphMap() to instruct Eloquent to store more generic values for polymorphic types.

    Read more ➺

  4. Getting the current user (or other session data) in a Laravel controller constructor

    Session data is not available directly in controller constructors. Let's see how we can work around that.

    Read more ➺

  5. Gate and authorization improvements in Laravel 5.3

    With Laravel 5.2 we got a nice authorization system right out of the box. In 5.3 this got a lot of improvements and refinements. Let's take a look.

    Read more ➺