W PHP czym jest zamknięcie i dlaczego używa identyfikatora „use”?

407

Sprawdzam niektóre PHP 5.3.0funkcje i natknąłem się na kod na stronie, który wygląda dość zabawnie:

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

jako jeden z przykładów funkcji anonimowych .

Czy ktoś o tym wie? Jakaś dokumentacja? I wygląda źle, czy należy go kiedykolwiek użyć?

SeanDowney
źródło

Odpowiedzi:

362

W ten sposób PHP wyraża zamknięcie . To wcale nie jest złe, w rzeczywistości jest dość potężne i przydatne.

Zasadniczo oznacza to, że zezwalasz funkcji anonimowej na „przechwytywanie” zmiennych lokalnych (w tym przypadku $taxi odniesienie do $total) poza jej zakresem i zachowanie ich wartości (lub w przypadku $totalodwołania do $totalsiebie) jako stanu w obrębie sama anonimowa funkcja.

Andrew Hare
źródło
1
Więc jest używany TYLKO do zamknięć? Dzięki za wyjaśnienie, nie znałem różnicy między anonimową funkcją a zamknięciem
SeanDowney
136
Słowo usekluczowe służy również do aliasingu przestrzeni nazw . To niesamowite, że ponad 3 lata po wydaniu PHP 5.3.0 składnia function ... usejest nadal oficjalnie nieudokumentowana, co powoduje, że zamknięcia są nieudokumentowaną funkcją. Dokument nawet myli anonimowe funkcje i zamknięcia . Jedyną (beta i nieoficjalną) dokumentacją, use ()którą mogłem znaleźć na php.net, była RFC dla zamknięć .
2
Kiedy więc funkcja używa zamknięć zaimplementowanych w PHP? Chyba wtedy było w PHP 5.3? Czy jest to jakoś udokumentowane w podręczniku PHP?
rubo77
@Mytskine Cóż, zgodnie z dokumentem, anonimowe funkcje używają klasy Closure
Manny Fleurmond
1
Teraz usejest również używany do włączenia a traitdo class!
CJ Dennis,
477

Prostsza odpowiedź.

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

  1. Zamknięcie jest funkcją przypisaną do zmiennej, więc możesz ją przekazywać
  2. Zamknięcie to osobna przestrzeń nazw, zwykle nie można uzyskać dostępu do zmiennych zdefiniowanych poza tym obszarem nazw. Pojawia się słowo kluczowe use :
  3. use umożliwia dostęp do (używanie) kolejnych zmiennych wewnątrz zamknięcia.
  4. posługiwać się jest wcześnie wiążące. Oznacza to, że wartości zmiennych są KOPIOWANE po OKREŚLENIU zamknięcia. Zatem modyfikowanie $taxwewnątrz zamknięcia nie ma efektu zewnętrznego, chyba że jest wskaźnikiem, tak jak obiekt.
  5. Możesz przekazać zmienne jako wskaźniki, tak jak w przypadku &$total . W ten sposób, modyfikując wartość $totalDOES MAJĄ efekt zewnętrzny, zmienia się wartość oryginalnej zmiennej.
  6. Zmienne zdefiniowane wewnątrz zamknięcia również nie są dostępne z zewnątrz zamknięcia.
  7. Zamknięcia i funkcje mają tę samą prędkość. Tak, możesz używać ich w całym swoim skrypcie.

Jak zauważył @Mytskine , prawdopodobnie najlepszym dogłębnym wyjaśnieniem jest RFC dla zamknięć . (Głosuj za to.)

zupa
źródło
4
$closure = function ($value) use ($localVar as $alias) { //stuff};Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Słowo
1
@KalZekdor, potwierdzony również przez php5.3, wydaje się przestarzały. Zaktualizowałem odpowiedź, dziękuję za twój wysiłek.
zupa
4
Dodałbym do punktu # 5, że w ten sposób modyfikacja wartości wskaźnika &$totalma również efekt wewnętrzny. Innymi słowy, jeśli zmienisz wartość $total poza zamknięciem po jej zdefiniowaniu, nowa wartość zostanie przekazana tylko wtedy, gdy będzie wskaźnikiem.
billynoah,
2
@ AndyD273 cel jest rzeczywiście bardzo podobny, z wyjątkiem tego, że globalpozwala jedynie na dostęp do globalnej przestrzeni nazwuse umożliwia dostęp do zmiennych w nadrzędnej przestrzeni nazw. Zmienne globalne są ogólnie uważane za złe. Dostęp do zakresu nadrzędnego jest często właśnie celem utworzenia zamknięcia. Nie jest to „zło”, ponieważ jego zakres jest bardzo ograniczony. Inne języki, takie jak JS, domyślnie używają zmiennych zakresu nadrzędnego (jako wskaźnik, a nie jako skopiowana wartość).
zupa
1
Ta linia zatrzymała moje dwie godziny próżnego poszukiwaniaYou 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

To function () use () {}jest jak zamknięcie PHP.

Bez usefunkcji funkcja nie może uzyskać dostępu do zmiennej zasięgu nadrzędnego

$s = "hello";
$f = function () {
    echo $s;
};

$f(); // Notice: Undefined variable: s
$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$f(); // hello

Wartość usezmiennej pochodzi z momentu zdefiniowania funkcji, a nie z jej wywołania

$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$s = "how are you?";
$f(); // hello

use zmienna referencja za pomocą &

$s = "hello";
$f = function () use (&$s) {
    echo $s;
};

$s = "how are you?";
$f(); // how are you?
Steely Wing
źródło
4
po przeczytaniu tego nie żałuję trochę przewijania, ale chyba potrzebuję drobnej edycji literówki w trzecim bloku. Powinny być $ s zamiast $ obj.
Ustaw użytkownika
53

zamknięcia są piękne! rozwiązują wiele problemów związanych z funkcjami anonimowymi i umożliwiają naprawdę elegancki kod (przynajmniej tak długo, jak mówimy o php).

programiści javascript używają zamknięć przez cały czas, czasem nawet nie wiedząc o tym, ponieważ zmienne powiązane nie są wyraźnie zdefiniowane - po to właśnie jest „use” w php.

istnieją lepsze przykłady z prawdziwego świata niż powyższy. powiedzmy, że musisz posortować tablicę wielowymiarową według podwartości, ale klucz się zmienia.

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

ostrzeżenie: nieprzetestowany kod (nie mam zainstalowanego bankomatu php5.3), ale powinien on wyglądać tak.

ma jedną wadę: wielu programistów php może być nieco bezradnych, jeśli skonfrontujesz ich z zamknięciami.

aby lepiej zrozumieć wygodę zamykania, dam wam inny przykład - tym razem w javascript. jednym z problemów jest zakres i nieodłączna asynchroniczność przeglądarki. zwłaszcza jeśli chodzi o window.setTimeout();(lub -interval). więc przekazujesz funkcję do setTimeout, ale tak naprawdę nie możesz podać żadnych parametrów, ponieważ podanie parametrów powoduje wykonanie kodu!

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 zwraca funkcję z rodzajem predefiniowanego parametru!

szczerze mówiąc, bardziej lubię php od 5.3 i anonimowe funkcje / zamknięcia. przestrzenie nazw mogą być ważniejsze, ale są o wiele mniej seksowne .

stefs
źródło
4
ohhhhhhh, więc Zastosowania służą do przekazywania dodatkowych zmiennych, myślałem, że to zabawne zadanie. Dzięki!
SeanDowney
38
bądź ostrożny. parametry są używane do przekazywania wartości, gdy funkcja jest WYWOŁANA. zamknięcia służą do „przekazania” wartości, gdy funkcja jest DEFINIOWANA.
stefs
W Javascript można użyć bind () do podania początkowych argumentów funkcji - patrz Funkcje częściowo zastosowane .
Sᴀᴍ Onᴇᴌᴀ
17

Zupa wykonała świetną robotę, wyjaśniając zamknięcia za pomocą „use” i różnicy między EarlyBinding i Odwoływaniem się do zmiennych, które są „używane”.

Zrobiłem więc przykładowy kod z wczesnym wiązaniem zmiennej (= kopiowanie):

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

?>

Przykład z odwołaniem się do zmiennej (zwróć uwagę na znak „&” przed zmienną);

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

?>
joronimo
źródło
2

Do ostatnich lat PHP definiowało AST, a interpreter PHP izolował parser od części ewaluacyjnej. Podczas wprowadzania zamknięcia parser PHP jest bardzo sprzężony z oceną.

Dlatego, kiedy zamknięcie zostało po raz pierwszy wprowadzone w PHP, interpreter nie ma metody, która wiedziałaby, które zmienne zostaną użyte w zamknięciu, ponieważ nie jest jeszcze analizowane. Użytkownik musi zadowolić silnik Zend poprzez wyraźne importowanie, wykonując zadanie domowe, które powinien wykonać Zend.

Jest to tak zwany prosty sposób w PHP.

Zhu Jinxuan
źródło