PHP 3 dots in method syntax: Understanding the First-class callable syntax

Syed Sirajul Islam Anik
3 min readOct 11, 2022

--

Generated using: https://carbon.now.sh/

In PHP, methods/functions are first-class citizens. That means, the functions can be assigned to a variable, passed as a function argument, and also return as a value from a function. When a function is received as an argument or returned from a function, you can type-hint with either callable or \Closure . But what is the difference?

Callable vs Closure

The main difference between callable and Closure is that callable is a type whereas Closure is a class. An anonymous function is a Closure. Closure can be passed whenever a callable is expected but not the other way around because the closure class implements the __invoke method.

Check the following examples

<?php// Skiping callable type-hint, as invalid data cannot be passed
function checkType($c): array {
return [
'is_callable' => is_callable($c),
'is_closure' => $c instanceof Closure
];
}
function namedFunction() {}

$anonymous = function () {};
$invokableClass = new class {
public function __invoke() {}
};
$classMethod = new class {
public function method()
{
}
};
class StaticCallable
{
public static function method() {}
}
echo json_encode(checkType('invalidNamedFunction'));
// {"is_callable":false,"is_closure":false}
echo json_encode(checkType('namedFunction'));
// {"is_callable":true,"is_closure":false}
echo json_encode(checkType($anonymous));
// {"is_callable":true,"is_closure":true}
echo json_encode(checkType($invokableClass));
// {"is_callable":true,"is_closure":false}
echo json_encode(checkType([$classMethod, 'method']));
// {"is_callable":true,"is_closure":false}
echo json_encode(checkType([StaticCallable::class, 'method']));
// {"is_callable":true,"is_closure":false}

So, only the anonymous functions are closures and all the closures are callable.

Closures also provide flexibility to execute a method from a class context which means if a method is declared outside the class, you can bind an object to the closure which will allow the closure to execute in the class context.

Check the following example

<?phpclass MyClass
{
public function __construct(private string $value)
{
}
}

echo Closure::fromCallable(fn () => $this->value)
->call(new MyClass('THIS IS A PRIVATE VALUE'));
// OUTPUTS
// THIS IS A PRIVATE VALUE

The anonymous function can retrieve the private variable’s value even if the function is not in the class body.

Converting a non-anonymous function to Closure

To convert a non-anonymous function to closure, we can use Closure::fromCallable since PHP 7.1

Check the following examples

<?php// ONLY PICKED THE FUNCTIONS THOSE WERE NOT CLOSUREecho json_encode(checkType(Closure::fromCallable('namedFunction')));
// {"is_callable":true,"is_closure":true}
echo json_encode(checkType(Closure::fromCallable(new $invokableClass)));
// {"is_callable":true,"is_closure":true}
echo json_encode(checkType(Closure::fromCallable([$classMethod, 'method'])));
// {"is_callable":true,"is_closure":true}
echo json_encode(checkType(Closure::fromCallable([StaticCallable::class, 'method'])));
// {"is_callable":true,"is_closure":true}

A new way of doing the same since PHP 8.1

There is another way to convert any function to a closure. All you have to do is to use MethodExpression(...) . The first braces and those three dots after the method expression will convert any function to a closure.

Check the following examples

<?php// FROM THE FIRST SNIPPET$methodName = 'method';
echo json_encode(checkType(namedFunction(...)));
echo json_encode(checkType($anonymous(...)));
echo json_encode(checkType($invokableClass(...)));
echo json_encode(checkType([$classMethod, 'method'](...)));
echo json_encode(checkType($classMethod->method(...)));
echo json_encode(checkType($classMethod->{$methodName}(...)));
echo json_encode(checkType([StaticCallable::class, 'method'](...)));
echo json_encode(checkType(StaticCallable::method(...)));
echo json_encode(checkType(StaticCallable::{$methodName}(...)));
// all the lines above will output
// {"is_callable":true,"is_closure":true}

So, the snippet for getting the private value of a class can be rewritten like the following

<?phpclass MyClass
{
public function __construct(private string $value)
{
}
}
echo (fn () => $this->value)(...)
->call(new MyClass('THIS IS A PRIVATE VALUE'));
// The first line before the call method converts it to a closure
// OUTPUTS
// THIS IS A PRIVATE VALUE

Real-life implementation

Symfony uses this syntax a lot throughout the codebase. Here are a few

https://github.com/symfony/console/blob/7fa3b9cf17363468795e539231a5c91b02b608fc/Question/Question.php#L185

That’s all for the article. Happy coding. ❤

PHP 3 dots in the method. PHP triple dots in the method. PHP dots in the method signature. PHP method signature having dots. 3 dots in PHP methods.

--

--

Syed Sirajul Islam Anik

software engineer with "Senior" tag | procrastinator | programmer | !polyglot | What else 🙄 — Open to Remote