En PHP, ¿qué es un cierre y por qué usa el identificador de “uso”?

Estoy revisando algunas características de PHP 5.3.0 y encontré un código en el sitio que parece bastante divertido:

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

como uno de los ejemplos de funciones anónimas .

¿Alguien sabe de esto? ¿Alguna documentación? Y parece malvado, ¿debería usarse alguna vez?

Así es como PHP expresa un cierre . Esto no es malo en absoluto y, de hecho, es bastante poderoso y útil.

Básicamente, lo que esto significa es que está permitiendo que la función anónima “capture” variables locales (en este caso, $tax y una referencia a $total ) fuera de su scope y preserve sus valores (o en el caso de $total la referencia to $total ) como estado dentro de la función anónima misma.

Una respuesta más simple.

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

  1. El cierre es una función asignada a una variable, por lo que puede pasarla por alto
  2. Un cierre es un espacio de nombres separado, normalmente no se puede acceder a las variables definidas fuera de este espacio de nombres. Ahí viene la palabra clave de uso :
  3. el uso le permite acceder (usar) las variables sucesivas dentro del cierre.
  4. el uso es vinculante temprano. Eso significa que los valores de las variables se COPIARÁN al DEFINIR el cierre. Por lo tanto, modificar $tax dentro del cierre no tiene ningún efecto externo, a menos que sea un puntero, como lo es un objeto.
  5. Puede pasar variables como punteros en caso de &$total . De esta forma, modificar el valor de $total TIENE UN efecto externo, el valor de la variable original cambia.
  6. Las variables definidas dentro del cierre tampoco son accesibles desde fuera del cierre.
  7. Los cierres y las funciones tienen la misma velocidad. Sí, puedes usarlos en todos tus scripts.

Como @Mytskine señaló probablemente la mejor explicación en profundidad es el RFC para cierres . (Levántalo por esto)

los cierres son hermosos! resuelven muchos problemas que vienen con funciones anónimas y hacen posible un código realmente elegante (al menos mientras hablemos de php).

Los progtwigdores de Javascript usan cierres todo el tiempo, a veces incluso sin saberlo, porque las variables vinculadas no están definidas explícitamente, eso es para lo que el “uso” es para php.

hay mejores ejemplos del mundo real que el anterior. digamos que tiene que ordenar una matriz multidimensional por un subvalor, pero la clave cambia.

  'Alex', 'age' => 70), array('name' => 'Enrico', 'age' => 25) ); $sortByName = generateComparisonFunctionForKey('name'); $sortByAge = generateComparisonFunctionForKey('age'); usort($myArray, $sortByName); usort($myArray, $sortByAge); ?> 

advertencia: código no probado (no tengo php5.3 atm instalado), pero debería parecer algo así.

hay una desventaja: muchos desarrolladores de php pueden estar un poco indefensos si los confrontas con cierres.

para comprender mejor la bondad de los cierres, les daré otro ejemplo, esta vez en javascript. uno de los problemas es el scope y la asincronía inherente del navegador. especialmente, si se trata de window.setTimeout(); (o -intervalo). por lo tanto, pasas una función a setTimeout, pero realmente no puedes dar ningún parámetro, ¡porque proporcionando parámetros ejecuta el código!

 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 devuelve una función con un tipo de parámetro predefinido!

para ser sincero, me gusta mucho más php desde 5.3 y funciones / cierres anónimos. espacios de nombres pueden ser más importantes, pero son mucho menos sexy .

La function () use () {} es cierre para PHP, debe usar use para incluir la variable de la function padre.

  

Zupa hizo un gran trabajo al explicar los cierres con ‘uso’ y la diferencia entre la vinculación temprana y la referencia a las variables que se ‘usan’.

Así que hice un ejemplo de código con el enlace anticipado de una variable (= copiando):

 "; echo "Inside \$closureExampleEarlyBinding() \$b = ".$b."
"; }; echo "Before executing \$closureExampleEarlyBinding() \$a = ".$a."
"; echo "Before executing \$closureExampleEarlyBinding() \$b = ".$b."
"; $closureExampleEarlyBinding(); echo "After executing \$closureExampleEarlyBinding() \$a = ".$a."
"; echo "After executing \$closureExampleEarlyBinding() \$b = ".$b."
"; /* 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 */ ?>

Ejemplo con referencia a una variable (observe el carácter “&” antes de la variable);

 "; echo "Inside \$closureExampleReferencing() \$b = ".$b."
"; }; echo "Before executing \$closureExampleReferencing() \$a = ".$a."
"; echo "Before executing \$closureExampleReferencing() \$b = ".$b."
"; $closureExampleReferencing(); echo "After executing \$closureExampleReferencing() \$a = ".$a."
"; echo "After executing \$closureExampleReferencing() \$b = ".$b."
"; /* 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 */ ?>