web-dev-qa-db-fra.com

En PHP, qu'est-ce qu'une fermeture et pourquoi utilise-t-il l'identifiant "use"?

Je vérifie quelques-unes des fonctionnalités de PHP 5.3.0 et rencontre du code sur le site qui a l'air assez amusant:

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 sur fonctions anonymes .

Est-ce que quelqu'un sait à ce sujet? Toute documentation? Et ça a l'air mauvais, faut-il jamais l'utiliser?

376
SeanDowney

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

En gros, cela signifie que vous autorisez la fonction anonyme à "capturer" des variables locales (dans ce cas, $tax et une référence à $total) en dehors de son étendue et à conserver leurs valeurs (ou cas de $total la référence à $total lui-même) en tant qu'état dans la fonction anonyme elle-même.

338
Andrew Hare

Une réponse plus simple.

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

  1. La fermeture est une fonction assignée à une variable, vous pouvez donc la faire circuler
  2. Une fermeture est un espace de noms séparé. Normalement, vous ne pouvez pas accéder aux variables définies en dehors de cet espace de noms. Vient ensuite le mot-clé : :
  3. utiliser vous permet d'accéder (utiliser) les variables suivantes à l'intérieur de la fermeture.
  4. use est une liaison anticipée. Cela signifie que les valeurs des variables sont COPIÉES lors de la définition de la fermeture. Ainsi, modifier $tax à l'intérieur de la fermeture n'a aucun effet externe, à moins qu'il ne s'agisse d'un pointeur, comme le serait un objet.
  5. Vous pouvez passer des variables en tant que pointeurs, comme dans le cas de &$total. Ainsi, si vous modifiez la valeur de $total _ AVOIS 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 accessibles de l'extérieur de la fermeture non plus.
  7. Les fermetures et les fonctions ont la même vitesse. Oui, vous pouvez les utiliser dans tous vos scripts.

Comme @Mytskine a souligné , la meilleure explication détaillée est probablement le RFC pour les fermetures . (Upvote lui pour cela.)

443
zupa

les fermetures sont belles! ils résolvent beaucoup de problèmes liés aux fonctions anonymes et rendent le code vraiment élégant (du moins tant 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 existe 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 installé atm php5.3), 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 gentillesse 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). alors, vous passez 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 un type de paramètre prédéfini!

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

51
stefs

La function () use () {} est comme une fermeture pour 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 valeur de la variable use date de la définition de la fonction et non de son appel.

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

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

use variable par référence avec &

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

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

Zupa a fait un excellent travail en expliquant les fermetures avec "utilisation" et la différence entre EarlyBinding et Referencing les variables "utilisées".

J'ai donc créé un exemple de code avec la 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
*/

?>
14
joronimo

Jusque très récemment, PHP a défini son interprète AST et PHP a isolé l'analyseur de la partie évaluation. Au moment où la fermeture est introduite, l'analyseur syntaxique de PHP est fortement associé à 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 celle-ci n'est pas encore analysée. Ainsi, l'utilisateur doit satisfaire le moteur zend par une importation explicite, en faisant le devoir que zend devrait faire.

C'est la méthode dite simple en PHP.

1
Zhu Jinxuan