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

时间:2009-06-30 18:21:45

标签: php closures

我正在查看一些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);
}

作为anonymous functions上的一个示例。

有人知道吗?有文件吗?如果它被使用它看起来很邪恶?

6 个答案:

答案 0 :(得分:429)

更简单的答案。

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

  1. 闭包是分配给变量的函数,因此您可以传递它
  2. 闭包是一个单独的命名空间,通常,您无法访问在此命名空间之外定义的变量。有 使用 关键字:
  3. 使用 允许您访问(使用)闭包内的后续变量。
  4. 使用 是早期绑定。这意味着在定义闭包时变量值是COPIED。因此,修改闭包内的$tax没有外部影响,除非它是一个指针,就像对象一样。
  5. 您可以将变量作为指针传递,就像&$total一样。这样,修改$total具有外部效果的值,原始变量的值会发生变化。
  6. 闭包内定义的变量也无法从闭包外部访问。
  7. 闭包和功能具有相同的速度。是的,您可以在脚本上使用它们。
  8. 正如@Mytskine pointed out可能最好的深入解释是RFC for closures。 (为此赞成他。)

答案 1 :(得分:327)

这就是PHP表达closure的方式。这根本不是邪恶的,事实上它非常强大和有用。

基本上这意味着您允许匿名函数在其范围之外“捕获”局部变量(在本例中为$tax和对$total的引用)并保留其值(或者在$total$total}本身的引用的情况下,作为匿名函数本身的状态。

答案 2 :(得分:50)

封闭是美丽的!它们解决了匿名函数带来的许多问题,并且可以实现非常优雅的代码(至少只要我们谈论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);
?>

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

有一个缺点:如果你用闭包来对付它们,很多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。命名空间可能更重要,但它们的性感要差得多

答案 3 :(得分:39)

function () use () {}是PHP的闭包,您必须使用use来包含父function的变量。

<?php
$message = "hello\n";


$example = function () {
    echo $message;
};
// Notice: Undefined variable: message
$example();


$example = function () use ($message) {
    echo $message;
};
// "hello"
$example();


// Inherited variable's value is from when the function is defined, not when called
$message = "world\n";
// "hello"
$example();


// Inherit by-reference
$message = "hello\n";
$example = function () use (&$message) {
    echo $message;
};
// "hello"
$example();
// The changed value in the parent scope is reflected inside the function call
$message = "world\n";
// "world"
$example();


// Closures can also accept regular arguments
$example = function ($arg) use ($message) {
    echo $arg . ' ' . $message;
};
// "hello world"
$example("hello");

答案 4 :(得分:13)

Zupa做了很好的工作,解释了使用&#39;&#39;以及EarlyBinding和引用已使用的变量之间的区别&#39;。

所以我做了一个代码示例,早期绑定变量(=复制):

<?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
*/

?>

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

<?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
*/

?>

答案 5 :(得分:0)

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

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

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