在PHP中,什么是闭包?为什么要使用“ use”标识符?


407

我正在检查一些PHP 5.3.0功能,并在网站上运行了一些看起来很有趣的代码:

public function getTotal($tax)
{
    $total = 0.00;

    $callback =
        /* This line here: */
        function ($quantity, $product) use ($tax, &$total)
        {
            $pricePerItem = constant(__CLASS__ . "::PRICE_" .
                strtoupper($product));
            $total += ($pricePerItem * $quantity) * ($tax + 1.0);
        };

    array_walk($this->products, $callback);
    return round($total, 2);
}

作为匿名函数的例子之一。

有人知道吗?有文件吗?而且看起来很邪恶,应该使用它吗?

Answers:


362

这就是PHP表示闭包的方式。这根本不是邪恶的,实际上它是非常强大和有用的。

基本上,这意味着您要允许匿名函数在其作用域之外“捕获”局部变量(在这种情况下$tax$total),并将其值(或在$total引用其$total自身的情况下)保留为内部状态匿名函数本身。


1
因此,它仅用于闭包吗?感谢您的解释,我不知道匿名函数和闭包之间的区别
SeanDowney 2009年

136
use关键字还用于命名空间别名。令人惊讶的是,在PHP 5.3.0发布之后三年多的时间里,该语法function ... use仍未被正式记录,这使得闭包成为一个未记录的功能。该文档甚至混淆了匿名函数和闭包use ()我可以在php.net 上找到的唯一(测试版和非官方的)文档是RFC闭包

2
那么什么时候使用PHP来实现函数使用闭包呢?我猜那是在PHP 5.3中吗?它现在以某种方式记录在PHP手册中吗?
rubo77

@Mytskine好吧,根据文档,匿名函数使用Closure类
Manny Fleurmond

1
现在use也用于包含trait一个class
CJ丹尼斯

477

一个简单的答案。

function ($quantity) use ($tax, &$total) { .. };

  1. 闭包是分配给变量的函数,因此您可以传递它
  2. 闭包是一个单独的命名空间,通常,您不能访问此命名空间之外定义的变量。有自带的使用关键字:
  3. use允许您访问(使用)闭包内部的后续变量。
  4. 使用是早期绑定。这意味着在定义闭包时将复制变量值。因此$tax,在闭包内部进行修改不会产生外部影响,除非它像对象一样是指针。
  5. 您可以像的情况一样将变量作为指针传递&$total。这样,修改$totalDOES 的值会产生外部影响,原始变量的值会更改。
  6. 在闭包内部定义的变量也不能从闭包外部访问。
  7. 闭合和功能具有相同的速度。是的,您可以在所有脚本中使用它们。

正如@Mytskine 指出的那样,最好的深入解释可能是针对闭包RFC。(为此投票给他。)


4
use语句中的as关键字在php 5.5中给了我语法错误:$closure = function ($value) use ($localVar as $alias) { //stuff};给出的错误是:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor 2014年

1
@KalZekdor(也已通过php5.3确认)似乎已被弃用。我更新了答案,谢谢您的努力。
zupa 2014年

4
我会在第5点中这样说,修改指针之类的值&$total也具有内部作用。换句话说,如果定义闭包更改了$total 外部值,则只有在它是指针的情况下,新值才会被传入。
billynoah 2015年

2
@ AndyD273的目的确实非常相似,除了global只允许访问全局名称空间而use允许访问父名称空间中的变量。全局变量通常被认为是邪恶的。访问父作用域通常是创建闭包的目的。它不是“邪恶的”,因为它的范围非常有限。其他语言(例如JS)隐式使用父范围的变量(作为指针,而不作为复制的值)。
祖帕

1
这条线停止了我两个小时的徒劳搜寻You can pass in variables as pointers like in case of &$total. This way, modifying the value of $total DOES HAVE an external effect, the original variable's value changes.
BlackPearl

69

function () use () {}是封样的PHP。

没有use,函数将无法访问父作用域变量

$s = "hello";
$f = function () {
    echo $s;
};

$f(); // Notice: Undefined variable: s
$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$f(); // hello

use变量的值是在函数定义的时候,不叫的时候

$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$s = "how are you?";
$f(); // hello

use 变量按引用 &

$s = "hello";
$f = function () use (&$s) {
    echo $s;
};

$s = "how are you?";
$f(); // how are you?

4
读完这篇文章后,我不后悔滚动了一点,但是猜测第三行的错别字需要做一些小的编辑。应该有$ s而不是$ obj。
堆叠用户

53

封口很漂亮!它们解决了匿名函数带来的许多问题,并使真正优雅的代码成为可能(至少只要我们谈论php)。

javascript程序员一直在使用闭包,有时甚至不知道闭包,因为绑定变量没有明确定义-这就是php中“ use”的含义。

有比以上示例更好的真实示例。假设您必须按子值对多维数组进行排序,但是键会发生变化。

<?php
    function generateComparisonFunctionForKey($key) {
        return function ($left, $right) use ($key) {
            if ($left[$key] == $right[$key])
                return 0;
            else
                return ($left[$key] < $right[$key]) ? -1 : 1;
        };
    }

    $myArray = array(
        array('name' => 'Alex', 'age' => 70),
        array('name' => 'Enrico', 'age' => 25)
    );

    $sortByName = generateComparisonFunctionForKey('name');
    $sortByAge  = generateComparisonFunctionForKey('age');

    usort($myArray, $sortByName);

    usort($myArray, $sortByAge);
?>

警告:未经测试的代码(我没有在atm上安装php5.3),但它看起来应该像这样。

有一个缺点:如果您面对闭包,许多php开发人员可能会有些无助。

为了进一步了解闭包的好处,我再举一个例子-这次是用javascript。问题之一是范围和浏览器固有的异步性。特别是涉及到window.setTimeout();(或-interval)。因此,您将一个函数传递给setTimeout,但实际上不能给出任何参数,因为提供参数会执行代码!

function getFunctionTextInASecond(value) {
    return function () {
        document.getElementsByName('body')[0].innerHTML = value; // "value" is the bound variable!
    }
}

var textToDisplay = prompt('text to show in a second', 'foo bar');

// this returns a function that sets the bodys innerHTML to the prompted value
var myFunction = getFunctionTextInASecond(textToDisplay);

window.setTimeout(myFunction, 1000);

myFunction返回带有某种预定义参数的函数!

老实说,自5.3起,我更喜欢php和匿名函数/闭包。命名空间可能更重要,但它们却不那么性感


4
ohhhhhhhh,因此Uses用于传递额外的变量,我认为这是一些有趣的任务。谢谢!
SeanDowney

38
小心。当该函数被调用时,参数用于传递值。函数定义时,闭包用于“传递”值。
Stefs

在Javascript中,可以使用bind()指定函数的初始参数-请参阅部分应用的函数
SᴀᴍOnᴇᴌᴀ

17

Zupa很好地解释了“使用”的闭包以及EarlyBinding与引用“已使用”变量之间的区别。

因此,我制作了一个带有早期绑定变量(=复制)的代码示例:

<?php

$a = 1;
$b = 2;

$closureExampleEarlyBinding = function() use ($a, $b){
    $a++;
    $b++;
    echo "Inside \$closureExampleEarlyBinding() \$a = ".$a."<br />";
    echo "Inside \$closureExampleEarlyBinding() \$b = ".$b."<br />";    
};

echo "Before executing \$closureExampleEarlyBinding() \$a = ".$a."<br />";
echo "Before executing \$closureExampleEarlyBinding() \$b = ".$b."<br />";  

$closureExampleEarlyBinding();

echo "After executing \$closureExampleEarlyBinding() \$a = ".$a."<br />";
echo "After executing \$closureExampleEarlyBinding() \$b = ".$b."<br />";

/* this will output:
Before executing $closureExampleEarlyBinding() $a = 1
Before executing $closureExampleEarlyBinding() $b = 2
Inside $closureExampleEarlyBinding() $a = 2
Inside $closureExampleEarlyBinding() $b = 3
After executing $closureExampleEarlyBinding() $a = 1
After executing $closureExampleEarlyBinding() $b = 2
*/

?>

引用变量的示例(注意变量前的'&'字符);

<?php

$a = 1;
$b = 2;

$closureExampleReferencing = function() use (&$a, &$b){
    $a++;
    $b++;
    echo "Inside \$closureExampleReferencing() \$a = ".$a."<br />";
    echo "Inside \$closureExampleReferencing() \$b = ".$b."<br />"; 
};

echo "Before executing \$closureExampleReferencing() \$a = ".$a."<br />";
echo "Before executing \$closureExampleReferencing() \$b = ".$b."<br />";   

$closureExampleReferencing();

echo "After executing \$closureExampleReferencing() \$a = ".$a."<br />";
echo "After executing \$closureExampleReferencing() \$b = ".$b."<br />";    

/* this will output:
Before executing $closureExampleReferencing() $a = 1
Before executing $closureExampleReferencing() $b = 2
Inside $closureExampleReferencing() $a = 2
Inside $closureExampleReferencing() $b = 3
After executing $closureExampleReferencing() $a = 2
After executing $closureExampleReferencing() $b = 3
*/

?>

2

直到最近几年,PHP才定义了AST,PHP解释器将解析器与评估部分隔离开来。在引入闭包期间,PHP的解析器与评估高度结合。

因此,当闭包首次引入PHP时,解释器没有方法知道闭包中将使用哪些变量,因为尚未对其进行解析。因此,用户必须通过显式导入来使zend引擎满意,并完成zend应该做的作业。

这是PHP中所谓的简单方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.