PHP RFC Preview: Dynamic Callback Expressions

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:

  1. Most IDEs do not recognize callbacks as such, and so cannot offer autocompletion, rename refactoring, and other benefits of code comprehension.
  2. Authors can misspell identifiers inside strings.
  3. Within namespaced code, authors can forget to prepend the namespace, since function calls within the namespace do not require it.
  4. 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.

4 thoughts on “PHP RFC Preview: Dynamic Callback Expressions

  1. Evan Winslow says:

    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?

  2. says:

    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”).

  3. Evan Winslow says:

    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.

Leave a Reply