En PHP, qu'est-ce qu'une fermeture et pourquoi utilise-t-elle l'identifiant «use»?

407

Je vérifie certaines PHP 5.3.0fonctionnalités et j'ai parcouru du code sur le site qui a l'air assez drôle:

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

comme l'un des exemples de fonctions anonymes .

Quelqu'un le sait-il? Une documentation? Et il a l'air mauvais, devrait-il jamais être utilisé?

SeanDowney
la source

Réponses:

362

C'est ainsi que PHP exprime une fermeture . Ce n'est pas mal du tout et en fait c'est assez puissant et utile.

Fondamentalement, cela signifie que vous autorisez la fonction anonyme à "capturer" des variables locales (dans ce cas, $taxet une référence à $total) en dehors de sa portée et à conserver leurs valeurs (ou dans le cas de $totalla référence à $totalelle-même) comme état dans la fonction anonyme elle-même.

Andrew Hare
la source
1
Il est donc UNIQUEMENT utilisé pour les fermetures? Merci pour votre explication, je ne connaissais pas la différence entre une fonction anonyme et une fermeture
SeanDowney
136
Le usemot-clé est également utilisé pour aliaser les espaces de noms . Il est étonnant que, plus de 3 ans après la sortie de PHP 5.3.0, la syntaxe function ... usesoit toujours officiellement non documentée, ce qui fait des fermetures une fonctionnalité non documentée. Le doc confond même les fonctions anonymes et les fermetures . La seule documentation (bêta et non officielle) sur que use ()j'ai pu trouver sur php.net était le RFC pour les fermetures .
2
Alors, quand les fermetures d'utilisation des fonctions ont-elles été implémentées en PHP? Je suppose que c'était en PHP 5.3? Est-il désormais documenté dans le manuel PHP?
rubo77
@Mytskine Eh bien, selon le doc, les fonctions anonymes utilisent la classe de fermeture
Manny Fleurmond
1
Now useest également utilisé pour inclure un traitdans un class!
CJ Dennis
477

Une réponse plus simple.

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

  1. La fermeture est une fonction affectée à une variable, vous pouvez donc la faire circuler
  2. Une fermeture est un espace de noms distinct, normalement, vous ne pouvez pas accéder aux variables définies en dehors de cet espace de noms. Voici le mot-clé use :
  3. use vous permet d'accéder (utiliser) aux variables suivantes à l'intérieur de la fermeture.
  4. l'utilisation est une liaison anticipée. Cela signifie que les valeurs variables sont COPIÉES lors de la DÉFINITION de la fermeture. Ainsi, la modification$taxà l'intérieur de la fermeture n'a aucun effet externe, sauf s'il s'agit d'un pointeur, comme un objet.
  5. Vous pouvez passer des variables comme pointeurs comme dans le cas de &$total. De cette façon, en modifiant la valeur de $totalDOES ONT un effet externe, la valeur de la variable d'origine change.
  6. Les variables définies à l'intérieur de la fermeture ne sont pas non plus accessibles depuis l'extérieur de la fermeture.
  7. Les fermetures et les fonctions ont la même vitesse. Oui, vous pouvez les utiliser partout dans vos scripts.

Comme l'a souligné @Mytskine , la meilleure explication approfondie est probablement le RFC pour les fermetures . (Le voter pour cela.)

zupa
la source
4
Le mot clé as dans l'instruction use me donne une erreur de syntaxe en php 5.5: L' $closure = function ($value) use ($localVar as $alias) { //stuff};erreur donnée est:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor
1
@KalZekdor, également confirmé avec php5.3, semble obsolète. J'ai mis à jour la réponse, merci pour vos efforts.
zupa
4
J'ajouterais au point # 5 que de cette façon, la modification de la valeur d'un pointeur comme &$totala également un effet interne. En d'autres termes, si vous modifiez la valeur de l' $total extérieur de la fermeture après sa définition, la nouvelle valeur n'est transmise que s'il s'agit d'un pointeur.
billynoah
2
@ AndyD273 l'objectif est en effet très similaire, sauf qu'il globalautorise uniquement l'accès à l'espace de noms global alors qu'il useautorise l'accès aux variables dans l'espace de noms parent. Les variables globales sont généralement considérées comme mauvaises. L'accès à la portée parent est souvent le but même de créer une fermeture. Ce n'est pas "mal" car sa portée est très limitée. D'autres langages comme JS utilisent implicitement les variables de la portée parent (comme pointeur, pas comme valeur copiée).
zupa
1
Cette ligne a arrêté mes deux heures de recherche vaineYou 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

le function () use () {} comme la fermeture de PHP.

Sans use, la fonction ne peut pas accéder à la variable de portée parent

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

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

$f(); // hello

La usevaleur de la variable est à partir du moment où la fonction est définie, pas lorsqu'elle est appelée

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

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

use référence par variable avec &

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

$s = "how are you?";
$f(); // how are you?
Aile d'acier
la source
4
après avoir lu cela, je ne regrette pas un peu plus de défilement mais devinez avoir besoin d'une modification mineure pour la faute de frappe dans le troisième bloc. Il devrait y avoir $ s au lieu de $ obj.
Utilisateur de la pile du
53

les fermetures sont belles! ils résolvent beaucoup de problèmes qui viennent avec des fonctions anonymes et rendent possible un code vraiment élégant (au moins aussi longtemps que nous parlons de php).

les programmeurs javascript utilisent des fermetures tout le temps, parfois même sans le savoir, car les variables liées ne sont pas explicitement définies - c'est à cela que sert "utilisation" en php.

il y a de meilleurs exemples du monde réel que celui ci-dessus. disons que vous devez trier un tableau multidimensionnel par une sous-valeur, mais la clé change.

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

avertissement: code non testé (je n'ai pas php5.3 installé atm), mais il devrait ressembler à quelque chose comme ça.

il y a un inconvénient: beaucoup de développeurs php peuvent être un peu impuissants si vous les confrontez à des fermetures.

pour mieux comprendre la beauté des fermetures, je vais vous donner un autre exemple - cette fois en javascript. l'un des problèmes est la portée et l'asynchronité inhérente au navigateur. surtout s'il s'agit de window.setTimeout();(ou -interval). vous passez donc une fonction à setTimeout, mais vous ne pouvez pas vraiment donner de paramètres, car fournir des paramètres exécute le code!

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 renvoie une fonction avec une sorte de paramètre prédéfini!

pour être honnête, j'aime beaucoup plus PHP depuis 5.3 et les fonctions / fermetures anonymes. les espaces de noms peuvent être plus importants, mais ils sont beaucoup moins sexy .

stefs
la source
4
ohhhhhhhh, donc les utilisations sont utilisées pour passer des variables supplémentaires , je pensais que c'était une affectation amusante. Merci!
SeanDowney
38
Faites attention. les paramètres sont utilisés pour transmettre des valeurs lorsque la fonction est APPELÉE. les fermetures sont utilisées pour «transmettre» des valeurs lorsque la fonction est DEFINIE.
stefs
En Javascript, on peut utiliser bind () pour spécifier les arguments initiaux des fonctions - voir Fonctions partiellement appliquées .
Sᴀᴍ Onᴇᴌᴀ
17

Zupa a fait un excellent travail en expliquant les fermetures avec «utilisation» et la différence entre EarlyBinding et référencement des variables qui sont «utilisées».

J'ai donc fait un exemple de code avec liaison anticipée d'une variable (= copie):

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

?>

Exemple de référencement d'une variable (notez le caractère '&' avant la variable);

<?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
la source
2

Jusqu'à très récemment, PHP a défini son AST et l'interpréteur PHP a isolé l'analyseur de la partie évaluation. Pendant le temps où la fermeture est introduite, l'analyseur PHP est fortement couplé avec l'évaluation.

Par conséquent, lorsque la fermeture a été introduite pour la première fois en PHP, l'interpréteur n'a aucune méthode pour savoir quelles variables seront utilisées dans la fermeture, car elle n'est pas encore analysée. L'utilisateur doit donc satisfaire le moteur zend par importation explicite, en faisant les devoirs que devrait faire zend.

Il s'agit de la méthode dite simple en PHP.

Zhu Jinxuan
la source