PHP 3 dots in method syntax: Understanding the First-class callable syntax
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
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.