Что такое замыкание в PHP и почему он использует идентификатор «использования»?


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);
}

в качестве одного из примеров на анонимных функций .

Кто-нибудь знает об этом? Любая документация? И это выглядит злым, стоит ли его когда-либо использовать?

Ответы:


362

Вот как PHP выражает закрытие . Это вовсе не зло, и на самом деле это довольно сильно и полезно.

По сути, это означает, что вы позволяете анонимной функции «захватывать» локальные переменные (в данном случае $taxи ссылку на них $total) вне своей области и сохранять их значения (или в случае $totalссылки на $totalсебя) как состояние внутри сама анонимная функция.


1
Так что он используется только для замыканий? Спасибо за объяснение, я не знал разницу между анонимной функцией и закрытием
SeanDowney

136
useКлючевое слово также используется для наложения спектров пространств имен . Удивительно, что спустя более 3 лет после выпуска PHP 5.3.0 синтаксис function ... useофициально не документирован, что делает замыкания недокументированными. Документ даже смешивает анонимные функции и замыкания . Единственная (бета и неофициальная) документация, которую use ()я смог найти на php.net, была RFC для закрытий .

2
Так когда же были реализованы замыкания на использование функций в PHP? Я думаю, тогда это было в PHP 5.3? Это как-то задокументировано в руководстве по PHP?
rubo77

@Mytskine Ну, согласно документу, анонимные функции используют класс Closure
Мэнни Флермонд

1
Теперь useтакже используется для включения traitв class!
CJ Деннис

477

Более простой ответ.

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

  1. Закрытие - это функция, назначенная переменной, так что вы можете передать ее
  2. Замыкание - это отдельное пространство имен, обычно вы не можете получить доступ к переменным, определенным вне этого пространства имен. Приходит ключевое слово use :
  3. Использование позволяет вам получить доступ (использовать) последующие переменные внутри замыкания.
  4. использование является ранним обязательным. Это означает, что значения переменных КОПИРОВАНЫ после ОПРЕДЕЛЕНИЯ закрытия. Таким образом, изменение$taxвнутри замыкания не имеет внешнего эффекта, если это не указатель, как у объекта.
  5. Вы можете передавать переменные в виде указателей, как в случае &$total. Таким образом, изменяя значение $totalDOES HAVE , вы меняете значение исходной переменной.
  6. Переменные, определенные внутри замыкания, также недоступны извне замыкания.
  7. Замки и функции имеют одинаковую скорость. Да, вы можете использовать их во всех ваших сценариях.

Как отметил @Mytskine , вероятно, лучшим подробным объяснением является RFC для замыканий . (Передайте ему голос за это.)


4
Ключевое слово as в операторе use дает мне синтаксическую ошибку в php 5.5: $closure = function ($value) use ($localVar as $alias) { //stuff};данная ошибка:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor

1
@KalZekdor, также подтвержденный php5.3, кажется устаревшим. Я обновил ответ, спасибо за ваши усилия.
Зупа

4
Я бы добавил к пункту 5, что изменение значения указателя подобно этому также &$totalимеет внутренний эффект. Другими словами, если вы измените значение за $total пределами замыкания после того, как оно определено, новое значение будет передано, только если оно является указателем.
Биллиноа

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.

Есть лучшие примеры из реальной жизни, чем приведенный выше. Допустим, вам нужно отсортировать многомерный массив по значению, но ключ изменится.

<?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();(или -интервала). Итак, вы передаете функцию в 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 возвращает функцию со своего рода предопределенным параметром!

честно говоря, мне больше нравится php с 5.3 и анонимные функции / замыкания. Пространства имен могут быть более важными, но они намного менее сексуальны .


4
оооо, так что использование используется для передачи дополнительных переменных, я подумал, что это было какое-то забавное задание. Спасибо!
SeanDowney

38
быть осторожен. параметры используются для передачи значений при вызове функции. замыкания используются для «передачи» значений, когда функция определена.
Stefs

В Javascript можно использовать bind () для указания начальных аргументов функций - см. Частично примененные функции .
Sᴀᴍ Onᴇᴌᴀ

17

Zupa проделал большую работу, объясняя замыкания с помощью «use» и разницы между EarlyBinding и Referencing переменных, которые «используются».

Итак, я сделал пример кода с ранним связыванием переменной (= копирование):

<?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.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.