【问题标题】:Is it possible to create a named function from a closure?是否可以从闭包创建命名函数?
【发布时间】:2016-08-19 15:11:45
【问题描述】:

在 PHP 中,你可以有这样的命名函数:

function foo()
{
   return "bar";
}

你可以有这样的闭包:

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

闭包很棒且易于创建,但不幸的是,我需要使用的库确实需要一个命名函数。是否可以从闭包动态创建命名函数? IE。没有提前在代码中定义所有函数,而更像是register_function($name, callable $closure)

我知道create_function,但它采用 PHP 字符串作为函数体,只是 evals,这不是我想要的。

【问题讨论】:

  • 你是什么意思,“想要一个命名函数”?你必须做library_function('my_func'),然后它做function library_function($func) { $func(); }
  • @MarcB:我希望它做到了。不,库是 Smarty,用例是注册插件。它会根据你给它的可调用类型来对你的模板进行特定的编译,并且目前不支持闭包。它可以是实例方法、静态方法或命名函数。
  • 那么 smarty 想要一个命名函数但你不想命名它?
  • 我不能,因为它们确实是运行时定义的闭包。将其视为foreach ($things as $thing) { $closure = getClosureForThing($thing); registerClosure($closure); }。无论如何,对于问题的范围来说,确切的用途有点太多了。我只是想知道这是否可能。如果没有,我将不得不等到相关问题得到解决。

标签: php


【解决方案1】:

您可以使用回调创建全局数组。通过register_func($name, $callback) 添加到这个全局数组并通过call_func($name, $parameter1, $parameter2, ...) 调用函数。

如果不使用eval,我认为这是不可能从回调创建命名函数的。

【讨论】:

  • 应避免使用全局变量和命名空间作为一般的良好做法。如果您必须制作一个“上帝”数据对象,至少要为其提供供应商命名空间。
  • @Pheagey 这很简单。我知道更好的解决方案是——例如——单例回调容器类。但我只想展示一个草稿,而不是一个完整的解决方案。
  • 啊,是的,那很好。只是不想让 OP 养成不良做法的习惯。继续:)
  • @Pheagey 我不明白 - 如果您有更好的解决方案,请继续!写下来! ;)
【解决方案2】:

我设法使用邪恶的 evalReflectionClassSuperClosure 库创建了一个。

这是我的代码:

<?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)
// }

【讨论】:

  • @Skysplit 我希望你这样做只是为了锻炼你的 PHP 知识,我们永远不会在任何实际项目中看到这一点。
  • @d3jn 我绝对不打算在任何项目中使用它 :) 不过检查起来很有趣(当时)!
【解决方案3】:

我认为如果使用类的__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();

【讨论】:

  • 这似乎确实不可能,我最终用简单地调用适当闭包的对象来实现一些东西。不过,您的解决方案更好,因为它不需要为每个闭包实例化一个额外的对象。
【解决方案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);

在输出中我们会得到

字符串(3)“酒吧”

【讨论】:

    【解决方案5】:

    简而言之,您不能在 PHP 中命名 \Closure 对象。

    虽然我确信有一个 API 可以执行此操作,但它并未在 PHP 级别公开。可以编写一个 C 插件来公开一个 User-land php 函数来命名该函数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-24
      • 2016-08-09
      • 2012-06-26
      相关资源
      最近更新 更多