Можно ли создать именованную функцию из замыкания?

В PHP вы можете иметь именованные функции следующим образом:

function foo()
{
   return "bar";
}

И вы можете иметь Closures следующим образом:

$foo = function() {
    return "bar";
};

Закрытие является удивительным и легким в создании, но, к сожалению, библиотеке, которую мне нужно использовать, действительно нужна именованная функция. Можно ли динамически создавать именованную функцию из замыканий? То есть не определяя все функции в коде заблаговременно, но больше похоже на register_function($name, callable $closure).

Я знаю create_function, но тот берет строку PHP как тело функции и просто eval, это не то, что я ищу.

Ответы

Ответ 1

Я не думаю, что если можно создать именованные функции из-за закрытия на лету, но используя __call() магический метод класс, вы можете найти это обходное решение полезным:

<?php

class Foo
{
    public function __call($name, $args)
    {
        if (isset($this->{$name}))
            return call_user_func_array($this->{$name}, $args);
    }
}

$foo = new Foo();
// Declaring a closure then assign it to $bar property of Foo
$foo->bar = function () { 
    echo "bar"; 
};
// Call Foo method of the same name
$foo->bar();

Ответ 2

Вы можете создать глобальный массив с обратными вызовами. Добавьте к этому глобальному массиву register_func($name, $callback) и вызовите функцию call_func($name, $parameter1, $parameter2, ...).

Без использования eval Я думаю, что невозможно создать именованную функцию из обратного вызова.

Ответ 3

Мне удалось создать одно с помощью зла eval, ReflectionClass и Библиотека SuperClosure.

Здесь мой код:

<?php

function register_function(string $name, Closure $closure)
{
    $serializedBody = (new SuperClosure\Serializer)->serialize($closure);

    $obj = unserialize($serializedBody);
    $reflection = new ReflectionClass($obj);
    $property = $reflection->getProperty('data');
    $property->setAccessible(true);
    $data = $property->getValue($obj);
    $body = preg_replace('/^function \(/', "function {$name} (", $data['code']);

    eval($body);
}

$closure = function($a = 1, $b = 2, $c = 3) {
    var_dump(compact('a', 'b', 'c'));
};

register_function('test', $closure);

var_dump(function_exists('test')); // true

test(13, 2, 3);
// array(3) {
//   ["a"]=>
//   int(13)
//   ["b"]=>
//   int(2)
//   ["c"]=>
//   int(3)
// }

Ответ 4

Мое предложение - использовать статические методы, потому что их можно вызвать как простые функции. Мы можем достичь этого с помощью магической функции __callStatic, как в этом коде

<?php

class FunctionsProvider
{
    protected static $closures = [];

    public static function addClosure($name, $closure)
    {
        if (is_callable($closure)) {
            static::$closures[$name] = $closure;
        } else {
            throw new \Exception('Closure is not callable');
        }
    }

    public static function __callStatic($name, $arguments)
    {
        if (array_key_exists($name, static::$closures)) {       
            return call_user_func_array(static::$closures[$name], $arguments);
        } else {
            throw new \Exception('Unknown method');
        }
    }
}

//Lets prepare sample closure
$foo = function() {
    return "bar";
};

FunctionsProvider::addClosure('foo', $foo);

$return = FunctionsProvider::foo();

var_dump($return);

На выходе мы получим

string (3) "bar"

Ответ 5

Короче говоря, вы не можете назвать объект \Closure в PHP.

Хотя я уверен, что для этого есть API, он не отображается на уровне PHP. Можно было бы написать C-плагин, чтобы открыть функцию php-функции User-land, чтобы назвать функцию.