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

363

Я просматриваю некоторые функции 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);
}

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

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

Теги:
closures

6 ответов

290
Лучший ответ

Так PHP выражает closure. Это вовсе не зло, а на самом деле оно достаточно мощное и полезное.

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

  • 1
    Так он используется только для замыканий? Спасибо за объяснение, я не знал разницу между анонимной функцией и закрытием
  • 120
    Ключевое слово use также используется для псевдонимов пространств имен . Удивительно, что более чем через 3 года после выпуска PHP 5.3.0 синтаксическая function ... use все еще официально не документирована, что делает замыкания недокументированными. Документ даже смешивает анонимные функции и замыкания . Единственная (бета и неофициальная) документация по use () я смог найти на php.net, была RFC для замыканий .
Показать ещё 3 комментария
424

Простой ответ.

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

  • Закрытие - это функция, назначенная переменной, поэтому вы можете передать ее
  • Закрытие представляет собой отдельное пространство имен, обычно вы не можете обращаться к переменным, определенным за пределами этого пространства имен. Приходит ключевое слово use:
  • использовать позволяет вам использовать (использовать) следующие переменные внутри закрытия.
  • использовать - раннее связывание. Это означает, что значения переменных COPIED устанавливаются при определении закрытия. Поэтому модификация $tax внутри замыкания не имеет внешнего эффекта, если только это не указатель, как объект.
  • Вы можете передавать переменные как указатели, например, в случае &$total. Таким образом, изменение значения $total имеет внешний эффект, изменяется исходное значение переменной.
  • Переменные, определенные внутри закрытия, также недоступны из-за закрытия.
  • Замыкания и функции имеют одинаковую скорость. Да, вы можете использовать их во всех своих сценариях.

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

  • 3
    Ключевое слово as в операторе use дает мне синтаксическую ошибку в php 5.5: $closure = function ($value) use ($localVar as $alias) { //stuff}; Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')' ошибка: Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
  • 0
    @KalZekdor, также подтвержденный php5.3, кажется устаревшим. Я обновил ответ, спасибо за ваши усилия.
Показать ещё 6 комментариев
47

закрытия прекрасны! они решают множество проблем, связанных с анонимными функциями, и делают действительно элегантный код возможным (по крайней мере, пока мы говорим о 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(); (или -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 возвращает функцию с определенным предопределенным параметром!

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

  • 4
    оооо, так что Uses используется для передачи дополнительных переменных, я подумал, что это было какое-то забавное задание. Спасибо!
  • 31
    быть осторожен. параметры используются для передачи значений при вызове функции. замыкания используются для «передачи» значений, когда функция определена.
Показать ещё 1 комментарий
34

function () use () {} является закрытием для PHP, вы должны использовать use для включения переменной parent function.

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


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


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


// Inherited variable 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");
  • 1
    Спасибо за гораздо более простое объяснение.
11

Zupa проделал отличную работу, объясняя закрытие с помощью "use" и разницу между 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
*/

?>
0

Объем $tax и $total находится внутри функции getTotal(). Вы вызываете функцию обратного вызова внутри него. Поэтому нет необходимости называть use.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню