I’m posting this to get some initial feedback on this idea before I officially submit an RFC.
Background
Even with PHP’s growing object-oriented and functional programming features, the callback remains widely-used and useful. However, forcing authors to create callbacks via strings and arrays presents difficulties:
- Most IDEs do not recognize callbacks as such, and so cannot offer autocompletion, rename refactoring, and other benefits of code comprehension.
- Authors can misspell identifiers inside strings.
- Within namespaced code, authors can forget to prepend the namespace, since function calls within the namespace do not require it.
- Where
use
statements change the identifier for a class, authors can specify the local classname instead of the fully resolved name.
Proposal
PHP would provide an expression that evaluates to a string or array callback value. This expression would be composed of the usual function call syntax, but replacing the parameter list and surrounding parenthesis with ::function
. For example, to create the callback "Foo::bar"
, one would use the expression Foo::bar::function
.
At runtime, the following expressions (within a namespace NS
and a class Foo
) would evaluate as true:
func::function === 'NS\\func'
\func::function === 'func'
CN::meth::function === 'NS\\CN::meth'
\CN::meth::function === 'CN::meth'
self::meth::function === 'NS\\Foo::meth'
$obj->meth::function === [$obj, 'meth']
parent::{'meth'}::function === ['NS\\Foo', 'parent::meth']
As function
is a reserved word, it should not be misinterpreted as a constant or method (this idea borrowed from Ralph Schindler’s class name resolution RFC).
Any other use of ::function
would throw a parse error. Although it may be reasonable to suggest supporting syntaxes like ($callable)::function
(evaluating to $callable
), this would not provide real benefit and would complicate the spec.
As it is permitted between a function name and its argument list, for consistency and flexibility whitespace would be permitted before the ::function
tokens. It’s not clear whether whitespace would improve or worsen readability of the expression in practice; this will likely depend on how syntax highlighters display the ::function
tokens.
Example code
namespace NS1; use NS2\Bee as B; class A { static function init() { $inst = new self; var_export($inst->bar::function); $inst->bar(); } function bar() { var_export(B::baz::function); } } var_export(var_export ::function); var_export(A::init ::function); A::init();
At runtime, these values would be sent to var_export
:
'var_export'
'NS1\\A::init'
[$inst, 'bar']
'NS2\\Bee::baz'
Summary
::function
would give authors a straightforward and reliable way to specify correctly-formed callbacks without being tripped up by namespaces and use
statements. Callback expressions would also be more clearly recognized as such by the reader, and by IDEs and static analysis tools.
Hey Steve. This looks like a cool idea but I think the prefix is not necessary or desirable.
Also, doesn’t PHP already have a callable type hinting? Why can’t IDEs use this info to deduce that certain strings or arrays are references to functions?
Thanks, Evan. Without a prefix token, I don’t know how the tokenizer could differentiate between constants and function names, but, yes, ideally it would not be necessary. The internals team will certainly know.
The callable hint certainly helps, but a) it can’t help existing functions written without the hint (almost all existing framework code today), b) even with the hint I think treating a string as having special properties would be very difficult for IDE vendors, and c) function names in strings tend to be longer when namespaces come into play (
'Vendor\\Namespace\\Class_Name::Foo'
vs.callbackfor self::Foo
), and d) existing callback syntax is just visually distracting (“remember this string isn’t really a string”).I confess I know nothing about the php parser, but it seems like you could just change the define resolution to add a step that checks for functions in the scope/namespace first, and then falls back to checking for existing defines. People probably shouldn’t be giving the same names to defines and to functions anyways.
Note: all previous comments were based on the previous post revision.