En PHP, ¿qué es un cierre y por qué utiliza el identificador de "uso"?

407

Estoy revisando algunas PHP 5.3.0características 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 sobre funciones anónimas .

¿Alguien sabe sobre esto? Cualquier documentación? Y se ve malvado, ¿debería alguna vez usarse?

SeanDowney
fuente

Respuestas:

362

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

Básicamente, esto significa que está permitiendo que la función anónima "capture" variables locales (en este caso, $taxy una referencia a $total) fuera de su alcance y conserve sus valores (o en el caso de $totalla referencia a $totalsí misma) como estado dentro La función anónima misma.

Andrew Hare
fuente
1
Entonces, ¿SOLO se usa para cierres? Gracias por su explicación, no sabía la diferencia entre la función anónima y un cierre
SeanDowney
136
La usepalabra clave también se usa para crear alias de espacios de nombres . Es sorprendente que, más de 3 años después del lanzamiento de PHP 5.3.0, la sintaxis function ... usetodavía esté oficialmente indocumentada, lo que hace que los cierres sean una característica indocumentada. El documento incluso confunde funciones anónimas y cierres . La única documentación (beta y no oficial) use ()que pude encontrar en php.net fue el RFC para cierres .
2
Entonces, ¿ cuándo se implementaron los cierres de uso de funciones en PHP? ¿Supongo que fue en PHP 5.3? ¿Está documentado en el manual de PHP ahora de alguna manera?
rubo77
@Mytskine Bueno, según el documento, las funciones anónimas utilizan la clase de cierre
Manny Fleurmond
1
Ahora usetambién se usa para incluir un traiten un class!
CJ Dennis
477

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
  2. Un cierre es un espacio de nombres separado, normalmente, no puede acceder a las variables definidas fuera de este espacio de nombres. Llega la palabra clave de uso :
  3. use le permite acceder (usar) las variables siguientes dentro del cierre.
  4. el uso es vinculante temprano. Eso significa que los valores de las variables se COPIAN al DEFINIR el cierre. Por lo tanto, modificar$taxdentro del cierre no tiene efecto externo, a menos que sea un puntero, como lo es un objeto.
  5. Puede pasar variables como punteros como en el caso de &$total. De esta manera, modificando el valor de $totalTIENE UN efecto externo, el valor de la variable original cambia.
  6. Las variables definidas dentro del cierre tampoco son accesibles desde el exterior del cierre.
  7. Los cierres y las funciones tienen la misma velocidad. Sí, puedes usarlos en todos tus scripts.

Como señaló @Mytskine , probablemente la mejor explicación en profundidad es el RFC para cierres . (Votéalo por esto).

zupa
fuente
44
La palabra clave as en la declaración de uso me está dando un error de sintaxis en php 5.5: El $closure = function ($value) use ($localVar as $alias) { //stuff};error dado es:Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Kal Zekdor
1
@KalZekdor, confirmado también con php5.3, parece obsoleto. Actualicé la respuesta, gracias por tu esfuerzo.
zupa
44
Yo agregaría al punto 5 que de esta manera, modificar el valor de un puntero también &$totaltiene un efecto interno. En otras palabras, si cambia el valor de $total fuera del cierre después de que se define, el nuevo valor solo se transfiere si es un puntero.
billynoah
2
@ AndyD273 el propósito es muy similar, excepto que globalsolo permite el acceso al espacio de nombres global mientras que usepermite el acceso a las variables en el espacio de nombres padre. Las variables globales generalmente se consideran malvadas. Acceder al ámbito principal es a menudo el propósito de crear un cierre. No es "malvado" ya que su alcance es muy limitado. Otros lenguajes como JS utilizan implícitamente las variables del ámbito primario (como puntero, no como valor copiado).
zupa
1
Esta línea se detuvo mi dos horas vana búsquedaYou 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

El function () use () {}es como el cierre para PHP.

Sin use, la función no puede acceder a la variable de ámbito principal

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

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

$f(); // hello

El usevalor de la variable es de cuando se define la función, no cuando se llama

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

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

use por referencia variable con &

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

$s = "how are you?";
$f(); // how are you?
Ala de acero
fuente
44
después de leer esto, no me arrepiento de un poco más de desplazamiento, pero supongo que necesito una edición menor para el error tipográfico en el tercer bloque. Debería haber $ s en lugar de $ obj.
Usuario de pila del
53

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 programadores de javascript usan cierres todo el tiempo, a veces incluso sin saberlo, porque las variables enlazadas no están explícitamente definidas, para eso sirve "uso" en php.

Hay mejores ejemplos del mundo real que el anterior. supongamos que tiene que ordenar una matriz multidimensional por un subvalor, pero la clave 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);
?>

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

Hay un inconveniente: muchos desarrolladores de PHP pueden ser un poco indefensos si los confrontas con cierres.

Para comprender mejor la simplicidad de los cierres, te daré otro ejemplo, esta vez en javascript. Uno de los problemas es el alcance y la asincronía inherente al navegador. especialmente, si se trata de window.setTimeout();(o -intervalo). entonces, pasa una función a setTimeout, pero realmente no puede dar ningún parámetro, ¡porque proporcionar 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 honesto, me gusta mucho más php desde 5.3 y funciones / cierres anónimos. Los espacios de nombres pueden ser más importantes, pero son mucho menos sensuales .

stefs
fuente
44
ohhhhhhhh, por lo que los usos se utilizan para pasar variables adicionales , pensé que era una tarea divertida. ¡Gracias!
SeanDowney el
38
ten cuidado. Los parámetros se utilizan para pasar valores cuando la función se llama. los cierres se usan para "pasar" valores cuando la función está DEFINIDA.
stefs el
En Javascript, se puede usar bind () para especificar argumentos iniciales para las funciones; consulte Funciones aplicadas parcialmente .
Sᴀᴍ el
17

Zupa hizo un gran trabajo al explicar los cierres con 'uso' y la diferencia entre EarlyBinding y Referenciar las variables que se 'usan'.

Así que hice un ejemplo de código con enlace temprano de una variable (= 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
*/

?>

Ejemplo con referencia a una variable (observe el carácter '&' antes de 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
fuente
2

Hasta años muy recientes, PHP ha definido su intérprete AST y PHP ha aislado el analizador de la parte de evaluación. Durante el tiempo en que se introduce el cierre, el analizador de PHP está altamente asociado con la evaluación.

Por lo tanto, cuando el cierre se introdujo por primera vez en PHP, el intérprete no tiene ningún método para saber qué variables se usarán en el cierre, porque aún no se ha analizado. Por lo tanto, el usuario debe complacer al motor zend mediante la importación explícita, haciendo los deberes que debe hacer zend.

Esta es la llamada forma simple en PHP.

Zhu Jinxuan
fuente