У 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 Ну, за словами док., Анонімні функції використовують клас закриття
Манні Флермонд

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

477

Простіша відповідь.

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

  1. Закриття - це функція, призначена змінній, тому ви можете передавати її навколо
  2. Закриття - це окремий простір імен, як правило, ви не можете отримати доступ до змінних, визначених поза цим простором імен. Існує ключове слово використання :
  3. використання дозволяє отримати доступ (використання) наступних змінних всередині закриття.
  4. використання - це раннє зв'язування. Це означає, що значення змінних КОПІЮються після ВИЗНАЧЕННЯ закриття. Отже, модифікація$taxвсередині закриття не має зовнішнього ефекту, якщо тільки вона не є вказівником, як об'єкт.
  5. Ви можете передавати змінні як покажчики, як у випадку &$total. Таким чином, змінюючи значення $totalДОБАЄ зовнішній ефект, змінюється значення вихідної змінної.
  6. Змінні, визначені всередині закриття, також недоступні ззовні закриття.
  7. Замикання та функції мають однакову швидкість. Так, ви можете використовувати їх у всіх сценаріях.

Як зазначав @Mytskine , мабуть, найкращим поглибленим поясненням є RFC для закриття . (Просимо його за це.)


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

1
@KalZekdor, підтверджений також php5.3, здається застарілим. Я оновив відповідь, дякую за ваші зусилля.
zupa

4
Я б додав до пункту №5, що таким чином, зміна значення, як вказівник, &$totalмає і внутрішній ефект. Іншими словами, якщо ви зміните значення $total зовнішньої частини закриття після того, як воно визначено, нове значення передається лише у тому випадку, якщо воно є вказівником.
billynoah

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
прочитавши це, я не шкодую про трохи більше прокрутки, але, мабуть, потрібна незначна редакція для друку в третьому блоці. Замість $ obj повинно бути $ s.
Стек користувача

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
будь обережний. параметри використовуються для передачі значень, коли функція CALLED. закриття використовуються для "передачі" значень, коли функція визначена.
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.