是否可以在不使用全局变量的 PHP 5.2.x 中模拟闭包?

Posted

技术标签:

【中文标题】是否可以在不使用全局变量的 PHP 5.2.x 中模拟闭包?【英文标题】:Is it possible to simulate closures in PHP 5.2.x not using globals? 【发布时间】:2011-01-13 15:19:36 【问题描述】:

我可以想到一种将所需变量作为额外参数传递给闭包的方法,但这并不是最佳实践。

有什么想法吗?

【问题讨论】:

很遗憾 php 不支持这么棒的结构。 @ChaosPandion 但它从 PHP5.3 开始 嗯,是的,但是您必须明确列出封闭变量。可以说是“拄着拐杖”。不过总比没有好。 【参考方案1】:

有趣的问题。我会说根本不可能,但让我们看看

引用IBM - What's new in PHP5.3, Part 2

闭包是在自己的环境中计算的函数,它有一个或多个绑定变量,在调用函数时可以访问这些变量。

以及进一步(强调我的)

要从外部环境导入的变量在闭包函数定义的 use 子句中指定。默认情况下,它们按值传递,这意味着如果我们更新闭包函数定义中传递的值,它不会更新外部值。

使用global 将通过引用传递,尽管可以通过在use 子句中使用& 将变量通过引用与闭包绑定,但它已经偏离了5.3 的默认行为。

$var = 'yes';
$fn  = create_function('', 'global $var; $var = "no";');
$fn();
echo $var; // outputs no

您可以复制全局变量以便按值使用它,例如

$var = 'yes';
$fn  = create_function('', 'global $var; $tmp = $var; $tmp = "no";');
$fn();
echo $var; // outputs yes

另外,全局变量的值(使用create_function时)不会在函数创建时而是在函数运行时计算(绑定)

$var = 'yes';
$fn  = create_function('', 'global $var; $tmp = $var; return $tmp;');
$var = 'maybe';
echo $fn(); // outputs maybe

$var = 'yes';
$fn  = function() use ($var)  return $var; ;
$var = 'maybe';
echo $fn(); // outputs yes

同样重要的是

当在对象中定义时,一件方便的事情是闭包可以通过 $this 变量完全访问对象,而无需显式导入它。 *虽然我认为这在最终的 PHP5.3 中被删除了

使用global 关键字是不可能的,你也不能只使用$this。使用create_function 定义函数体时,无法引用类中的属性。

class A 

    protected $prop = 'it works';

    public function test()
    
        $fn = create_function('', 'echo $this->prop;');
        return $fn;
    


$a = new A;
$fn = $a->test();
$fn();

会导致

Fatal error: Using $this when not in object context

总结一下 虽然您可以创建一个从全局范围导入变量的函数,但您不能使用来自另一个范围的变量来创建一个函数。而且由于在使用 create_function 时您在技术上没有绑定,而是在执行创建的函数时导入,我想争辩说这种限制使闭包成为 lambda


编辑:下面 Onno Marsman 提供的解决方案相当不错。它没有完全模拟闭包,但实现非常接近。

【讨论】:

没有言语可以形容这有多可怕。 (不是你的答案。) 非常好的答案!通过在 php 5.2.17 上维护一个遗留项目,全局变量绑定的细节使我从很多头痛中解脱出来。赞一个!【参考方案2】:

我的解决方案:http://techblog.triptic.nl/simulating-closures-in-php-versions-prior-to-php-5-3/

但是,它确实将对象中的变量作为第一个参数传递给闭包。

【讨论】:

非常好的努力,尽管在我看来,必须调用 call() 从技术上讲它不符合闭包的资格(嗯,至少在某种程度上)。此外,我发现必须传入 Closure 实例才能使封闭变量有点尴尬,并且 call_user_func_array 应该返回。但是,还是很不错的。你是在 5.3 的 Closure 类之后建模的吗? 我首先想到了这个解决方案,然后才意识到 php 5.3 中甚至存在一个内部闭包类。我不喜欢自己调用 call() 并且会使用 __invoke 魔术方法,除非它在 ​​php 5.3 之前不可用... 戈登:“call_user_func_array 应该返回”是什么意思?【参考方案3】:

你是说像http://en.wikipedia.org/wiki/Currying这样的Currying

然后http://zaemis.blogspot.com/2009/06/currying-in-php.html

如果没有,没关系。 :-)

【讨论】:

不完全是我的意思,但还是不错的链接:D【参考方案4】:

在一些特殊情况下你可以做到。

如果您需要按值(而不是按引用)捕获变量,并且该值是简单的值类型,如上述数字、字符串或数组(不是引用类型,如对象、资源和函数),那么您可以使用 var_export() 将其插入到函数定义中:

$var = array(1, 3);
$f = create_function('',
    '$var=' . var_export($var,true) . '; return $var;');

如果您需要通过引用捕获变量以在函数调用之间保持可变状态,但您不需要将更改反映在创建它的原始范围中(例如创建累加器,但是对 sum 的更改不需要更改创建范围内的 sum 变量),然后您可以类似地插入它,但作为静态变量:

function make_accumulator($sum) 
    $f = create_function('$x',
        'static $var=' . var_export($var,true) . '; return $var += $x;');
    return $f;

【讨论】:

以上是关于是否可以在不使用全局变量的 PHP 5.2.x 中模拟闭包?的主要内容,如果未能解决你的问题,请参考以下文章

Ruby黑魔法, 在不使用全局变量的情况下, 在方法作用域里修改局部变量的值

如何在不使用全局变量的情况下构建这个程序?

php扩展在全局变量中不存在但是打印phpinfo却存在?

如何在不使用帮助器的情况下访问 Meteor 模板中的全局变量?

如何在不使用全局变量的情况下在 bash 中返回一个数组?

PHP函数声明