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

361

Я проверяю некоторые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);
}

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

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

Ваш Ответ

5   ответов
319

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

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

Так что он используется только для замыканий? Спасибо за объяснение, я не знал разницу между анонимной функцией и закрытием SeanDowney
Так Когда в PHP реализованы замыкания использования функций? Наверное, тогда это было в PHP 5.3? Это как-то задокументировано в руководстве по PHP? rubo77
@ Mytskine Ну, согласно документу, анонимные функции используют класс Closure Manny Fleurmond
Theuse ключевое слово также используется для Лизинг пространств имен. Удивительно, что через 3 года после выхода PHP 5.3.0 синтаксисfunction ... use до сих пор официально недокументирован, что делает замыкания недокументированными. Документ даже вводит в заблуждение анонимные функции и замыкания. Единственная (бета и неофициальная) документация наuse () Я мог найти на php.net был RFC для закрытий. user699082
422

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

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

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

Ключевое слово as в операторе use приводит к синтаксической ошибке в php 5.5:$closure = function ($value) use ($localVar as $alias) { //stuff}; Ошибка дана:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')' Kal Zekdor
@ KalZekdor, также подтвержденный php5.3, кажется устаревшим. Я обновил ответ, спасибо за ваши усилия. zupa
Я бы добавил к пункту 5, что таким образом, изменив значение указатель, как&$total имеет и внутренний эффект. Другими словами, если вы измените значение$total за пределам закрытияпосл определено, новое значение передается, только если это указатель. billynoah
Очень поздний вопрос; Так что просто для пояснения, использование () похоже на то, как глобально работает функция, позволяя вам использовать переменные, которые объявлены вне функции? AndyD273
@ AndyD273 цель действительно очень похожа, кромеglobal разрешает доступ только к глобальному пространству имен, тогда какuse позволяет получить доступ к переменным в родительском пространстве имен. Глобальные переменные обычно считаются злыми. Доступ к родительской области часто является самой целью создания замыкания. Это не «зло», поскольку его возможности очень ограничены. Другие языки, такие как JS неявно Использование переменные родительской области видимости (как указатель, а не как скопированное значение). zupa
50

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

warning: непроверенный код (у меня не установлен 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 и анонимные функции / замыкания. пространства имен могут быть важнее, но они намного менее сексуальны.

ohhhhhhhh, поэтому использование используется для передачи в Дополнительные переменные, я думал, что это было какое-то забавное задание. Благодарность SeanDowney
быть осторожен. параметры используются для передачи значений при вызове функции. замыкания используются для «передачи» значений, когда функция определена. stefs
В Javascript можно использоватьbind () чтобы указать начальные аргументы для функций - см. Частично примененные функции. Sᴀᴍ Onᴇᴌᴀ
38

Thefunction () 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");
Спасибо за более простое объяснение. Tuhin Paul
12

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

?>

Похожие вопросы