In PHP, che cos'è una chiusura e perché utilizza l'identificatore "use"?


407

Sto verificando alcune PHP 5.3.0funzionalità e ho incontrato un po 'di codice sul sito che sembra abbastanza divertente:

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

come uno degli esempi di funzioni anonime .

Qualcuno lo sa? Qualche documentazione? E sembra malvagio, dovrebbe mai essere usato?

Risposte:


362

Ecco come PHP esprime una chiusura . Questo non è affatto male e in effetti è abbastanza potente e utile.

Fondamentalmente ciò significa che stai permettendo alla funzione anonima di "catturare" variabili locali (in questo caso, $taxe un riferimento a $total) al di fuori del suo ambito di applicazione e di preservarne i valori (o nel caso del $totalriferimento a $totalse stesso) come stato all'interno la stessa funzione anonima.


1
Quindi viene utilizzato SOLO per chiusure? Grazie per la tua spiegazione, non sapevo la differenza tra la funzione anonima e una chiusura
SeanDowney,

136
La useparola chiave viene utilizzata anche per aliasare gli spazi dei nomi . È sorprendente che, a più di 3 anni dal rilascio di PHP 5.3.0, la sintassi function ... usesia ancora ufficialmente non documentata, il che rende le chiusure una caratteristica non documentata. Il documento confonde persino funzioni e chiusure anonime . L'unica documentazione (beta e non ufficiale) che use ()ho trovato su php.net era l' RFC per le chiusure .

2
Quindi quando sono state implementate le chiusure delle funzioni in PHP? Immagino che fosse in PHP 5.3? È documentato nel manuale di PHP ora in qualche modo?
rubo77,

@Mytskine Bene, secondo il doc, le funzioni anonime usano la classe Closure
Manny Fleurmond

1
Ora useè anche usato per includere a traitin a class!
CJ Dennis,

477

Una risposta più semplice

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

  1. La chiusura è una funzione assegnata a una variabile, quindi puoi passarla in giro
  2. Una chiusura è uno spazio dei nomi separato, normalmente non è possibile accedere alle variabili definite al di fuori di questo spazio dei nomi. Arriva la parola chiave use :
  3. use consente di accedere (utilizzare) alle variabili successive all'interno della chiusura.
  4. l'uso è un legame precoce. Ciò significa che i valori delle variabili vengono COPIATI al momento della DEFINIZIONE della chiusura. Quindi la modifica$taxall'interno della chiusura non ha alcun effetto esterno, a meno che non sia un puntatore, come un oggetto.
  5. Puoi passare le variabili come puntatori come nel caso di &$total. In questo modo, modificando il valore di$total DOES HAVE un effetto esterno, il valore della variabile originale cambia.
  6. Nemmeno le variabili definite all'interno della chiusura sono accessibili dall'esterno della chiusura.
  7. Le chiusure e le funzioni hanno la stessa velocità. Sì, puoi usarli in tutti i tuoi script.

Come ha sottolineato @Mytskine , probabilmente la migliore spiegazione approfondita è la RFC per chiusure . (Votalo per questo.)


4
La parola chiave as $closure = function ($value) use ($localVar as $alias) { //stuff};Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
nell'istruzione use

1
@KalZekdor, confermato anche con php5.3, sembra deprecato. Ho aggiornato la risposta, grazie per il tuo impegno.
Zupa,

4
Vorrei aggiungere al punto 5 che in questo modo, modificando il valore di un puntatore &$totalha anche un effetto interno. In altre parole, se si modifica il valore di $total fuori della chiusura dopo che è stato definito, il nuovo valore viene passato solo se si tratta di un puntatore.
Billynoah,

2
@ AndyD273 lo scopo è davvero molto simile, tranne per il fatto globalche consente solo l'accesso allo spazio dei nomi globale, mentre useconsente l'accesso alle variabili nello spazio dei nomi padre. Le variabili globali sono generalmente considerate malvagie. L'accesso all'ambito padre è spesso lo scopo stesso di creare una chiusura. Non è "malvagio" in quanto la sua portata è molto limitata. Altre lingue come JS usano implicitamente le variabili dell'ambito padre (come puntatore, non come valore copiato).
Zupa

1
Questa linea ha interrotto la mia vana ricerca di due oreYou 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

L' function () use () {}è come chiusura per PHP.

Senza use, la funzione non può accedere alla variabile dell'ambito padre

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

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

$f(); // hello

Il usevalore della variabile proviene da quando viene definita la funzione, non quando viene chiamata

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

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

use variabile per riferimento con &

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

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

4
dopo aver letto questo non mi pento un po 'più di scorrimento ma suppongo che abbia bisogno di una modifica minore per l'errore di battitura nel terzo blocco. Dovrebbero esserci $ s invece di $ obj.
Stack utente

53

le chiusure sono bellissime! risolvono molti problemi associati a funzioni anonime e rendono possibile un codice davvero elegante (almeno finché parliamo di php).

i programmatori javascript usano le chiusure in ogni momento, a volte anche senza saperlo, perché le variabili associate non sono esplicitamente definite - ecco a cosa serve "usare" in php.

ci sono esempi del mondo reale migliori di quello sopra. supponiamo che devi ordinare un array multidimensionale per un sotto-valore, ma la chiave cambia.

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

attenzione: codice non testato (non ho php5.3 installato atm), ma dovrebbe assomigliare a qualcosa del genere.

c'è un aspetto negativo: molti sviluppatori php potrebbero essere un po 'impotenti se li si confronta con le chiusure.

per capire di più la simpatia delle chiusure, ti faccio un altro esempio: questa volta in JavaScript. uno dei problemi è l'asincronia inerente all'ambito e al browser. soprattutto, se si tratta di window.setTimeout();(o -intervallo). quindi, si passa una funzione a setTimeout, ma non si può davvero dare alcun parametro, perché fornire parametri esegue il codice!

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 restituisce una funzione con un tipo di parametro predefinito!

ad essere sincero, mi piace molto di più il php dalla 5.3 e le funzioni / chiusure anonime. gli spazi dei nomi possono essere più importanti, ma sono molto meno sexy .


4
ohhhhhhhh, quindi gli Usi sono usati per passare variabili extra , ho pensato che fosse un compito divertente. Grazie!
SeanDowney,

38
stai attento. i parametri vengono utilizzati per passare valori quando la funzione è CHIAMATA. le chiusure vengono utilizzate per "passare" i valori quando la funzione è DEFINITA.
stefs,

In Javascript, si può usare bind () per specificare argomenti iniziali per le funzioni - vedere Funzioni parzialmente applicate .
Sᴀᴍ Onᴇᴌᴀ

17

Zupa ha fatto un ottimo lavoro spiegando le chiusure con "uso" e la differenza tra EarlyBinding e riferimento alle variabili che vengono "utilizzate".

Quindi ho fatto un esempio di codice con l'associazione anticipata di una variabile (= copia):

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

?>

Esempio con riferimento a una variabile (notare il carattere '&' prima della variabile);

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

Fino a anni molto recenti, PHP ha definito il suo interprete AST e PHP ha isolato il parser dalla parte di valutazione. Durante il periodo in cui viene introdotta la chiusura, il parser di PHP è fortemente associato alla valutazione.

Pertanto, quando la chiusura è stata inizialmente introdotta in PHP, l'interprete non ha alcun metodo per sapere quali variabili verranno utilizzate nella chiusura, perché non è ancora stata analizzata. Quindi l'utente deve soddisfare il motore zend con un'importazione esplicita, facendo i compiti che zend dovrebbe fare.

Questo è il cosiddetto modo semplice in PHP.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.