Skip to content

Instantly share code, notes, and snippets.

@brzuchal
Last active July 27, 2017 12:36
Show Gist options
  • Select an option

  • Save brzuchal/fcfba46c8e20b41a6a341f499c2b679d to your computer and use it in GitHub Desktop.

Select an option

Save brzuchal/fcfba46c8e20b41a6a341f499c2b679d to your computer and use it in GitHub Desktop.
Callable Dereference

Introduction

Callables syntactically have few ways of passing them around. This RFC is supposed to introduce unified short way of creating closures from callables and reduce callable type hint checks at runtime by using closures.

Callable syntax

Currently there are few ways of declaring callables and they differ while dereferencing function or method. Assuming given example we'll gonan discuss all available 6 ways passing callable.

namespace Util {
    function writeln(string $message) : void {
        echo $message . \PHP_EOL;
    }

    class Terminal {
        public static function println(string $message) : void {
            echo $message . \PHP_EOL;
        }
        public function writeln(string $message) : void {
            echo $message . \PHP_EOL;
        }
    }
    
    class Console extends Terminal {
        public function __invoke(string $message) : void {
            echo $message . \PHP_EOL;
        }
    }
}

namespace {
    function helloWorld(callable $callable) : void {
        $callable('Hello World!');
    }
}

Simple callback

Simple callback are possible to pass using their name.

helloWorld('Util\\writeln');
call_user_func('Util\writeln', 'Hello World!');

Static class method call

Static class method call first way is quite similar and requires method name after '::'.

helloWorld('Util\\Terminal::println');
call_user_func('Util\\Terminal::println'), 'Hello World!');

Static class method call using array

Static class method call can be also passed using array construct with class name at 0 index and method name at 1 index.

helloWorld([Util\Terminal::class, 'println']);
call_user_func([Util\\Terminal::class, 'println']), 'Hello World!');

Object method call

Object method call is passed using array construct with two params - which is object instance variable and method name.

$terminal = new \Util\Terminal();
helloWorld([$terminal, 'writeln']);
call_user_func([$terminal, 'writeln'], 'Hello World!');

Relative static class method call

Relative static class method call is passed using array withtwo params - which is class name and method name with base class name delimited by '::'.

$console = new Util\Console();
call_user_func([Util\Console::class, 'parent::writeln'], 'Hello World!');

Objects implementing __invoke

Objects implementing __invoke can be used as callables using instance variable.

$console = new Util\Console();
helloWorld($console);
call_user_func($console, 'Hello World!');

Drawbacks of current solution

  1. Syntax is based on passing function names as a string or array with instance and method name as a string etc. which is very hard to refactor using for eg. IDE or detecting using regular expressions.
  2. Passed callables are different type variables, like: 'string', 'array' or specified object instance.
  3. Passed callables may not be valid callables which is validated at call time and can pass 'callable' type hint without warning.

Proposed solution

The way to unify passing callables is creating closures using same syntax as invoking functions without their argument list and parenthesis around braces and call them all the same way, which benefits are short closure from callable syntax which shortens creation of closures for most of cases.

Simple callback

Short closure from callable function would look like:

$writeln = {Util\writeln};
// is a simplification for
$writeln = Closure::fromCallable('Util\writeln');

Static class method call

Static class method short closure that would look like:

$println = {Util\Terminal::println};
// instead of
$println = Closure::fromCallable([Util\Terminal::class, 'writeln']);
// and
$println = Closure::fromCallable('Util\Terminal::writeln');

Object method call

Object instance method short closure that would look like:

$writeln = {$terminal->writeln};
// instead of
$writeln = Closure::fromCallable([$terminal, 'writeln']);

Objects implementing __invoke

Invokable objects short closure that would be:

$console = new Util\Console();
$callback = {$console};
// instead of
$callback = Closure::fromCallable($console);

Benefits

  1. Every callable closure is valid for all the time when it's passed around.
  2. No need to validate callable typehint against given argument every time.
  3. Common syntax for most of callable types.
  4. Syntax similar to function/method call.
  5. No need to pass function/method/class names as strings which is IDE friendly for refactor.
  6. Easy to parse by static analysis tools because of consistent syntax.
@brzuchal
Copy link
Author

@coatesap thanks for your comment.
Unfortunately, proposed syntax is same as object property so there is no way to resolve an ambiguity. It would lead to more ambiguities.

@JayPHP
Copy link

JayPHP commented Jul 27, 2017

@brzuchal unless we enforce better naming ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment