Cierres en PHP… ¿qué son, precisamente, y cuándo necesitarías usarlos?

82

Así que estoy programando de una manera agradable, actualizada y orientada a objetos. Regularmente hago uso de los diversos aspectos de la programación orientada a objetos que implementa PHP, pero me pregunto cuándo podría necesitar usar cierres. ¿Algún experto que pueda arrojar algo de luz sobre cuándo sería útil implementar cierres?

rg88
fuente

Respuestas:

82

PHP admitirá cierres de forma nativa en 5.3. Un cierre es bueno cuando desea una función local que solo se use para algún propósito pequeño y específico. El RFC para cierres da un buen ejemplo:

function replace_spaces ($text) {
    $replacement = function ($matches) {
        return str_replace ($matches[1], ' ', ' ').' ';
    };
    return preg_replace_callback ('/( +) /', $replacement, $text);
}

Esto le permite definir la replacementfunción localmente en el interior replace_spaces(), de modo que no:
1) Aborde el espacio
de nombres global 2) Hace que las personas tres años después se pregunten por qué hay una función definida globalmente que solo se usa dentro de otra función

Mantiene las cosas organizadas. Observe cómo la función en sí no tiene nombre, simplemente se define y se asigna como una referencia a $replacement.

Pero recuerde, debe esperar a PHP 5.3 :)

También puede acceder a variables fuera de su alcance en un cierre utilizando la palabra clave use. Considere este ejemplo.

// Set a multiplier  
 $multiplier = 3;

// Create a list of numbers  
 $numbers = array(1,2,3,4);

// Use array_walk to iterate  
 // through the list and multiply  
 array_walk($numbers, function($number) use($multiplier){  
 echo $number * $multiplier;  
 }); 

Aquí se ofrece una excelente explicación ¿Qué son las lambdas y los cierres de php?

sucio
fuente
1
Esta es una explicación asombrosa. +1
David J Eddy
4
Disfruté de la explicación de por qué usarías cierres. La mayoría de la gente realmente no comprende eso. +1
Carrie Kendall
10
Esta es una explicación de funciones anónimas, no una explicación de cierres. Las funciones anónimas son, como usted dice, como funciones con nombre, excepto que no son globales. Los cierres, por otro lado, son funciones que contienen variables libres de ámbito léxico (declaradas con "uso"); es decir. pueden copiar y hacer referencia a valores del ámbito en el que están declarados, incluso después de que todo lo demás se haya recolectado como basura.
Warbo
@Warbo Esto es cierto; en ese momento, realmente no percibía la diferencia entre una función anónima y un cierre. Los cierres solo tienen sentido una vez que asimilas las funciones anónimas, pero hasta el día de hoy todavía encuentro "explicaciones" de lo que es un cierre (como el mío, de hace 7 años ;-)) que no explican el aspecto del alcance.
dirtside
Es por eso que estaba diciendo, revise JavaScript donde los cierres se usan mucho, pero tenga en cuenta que las reglas de alcance variable son diferentes en PHP.
Rolf
17

Cuando necesitará una función en el futuro que realice una tarea que ha decidido ahora.

Por ejemplo, si lee un archivo de configuración y uno de los parámetros le dice que el hash_method algoritmo de su algoritmo es en multiplylugar de square, puede crear un cierre que se usará siempre que necesite aplicar un hash a algo.

El cierre se puede crear en (por ejemplo) config_parser(); crea una función llamada do_hash_method()usando variables locales paraconfig_parser() (desde el archivo de configuración). Siempre que do_hash_method()se llama, tiene acceso a las variables en el ámbito local de config_parser()aunque no se llame en ese ámbito.

Un buen ejemplo hipotético con suerte:

function config_parser()
{
    // Do some code here
    // $hash_method is in config_parser() local scope
    $hash_method = 'multiply';

    if ($hashing_enabled)
    {
        function do_hash_method($var)
        {
            // $hash_method is from the parent's local scope
            if ($hash_method == 'multiply')
                return $var * $var;
            else
                return $var ^ $var;
        }
    }
}


function hashme($val)
{
    // do_hash_method still knows about $hash_method
    // even though it's not in the local scope anymore
    $val = do_hash_method($val)
}
Dan Udey
fuente
No puedo simplemente copiar, pegar este ejemplo y ejecutarlo. Preferí un ejemplo que simplemente pueda ejecutar.
Kim Stacks
3
Esta respuesta es pobre. Esta es una afirmación sin sentido: "Cuando necesitará una función en el futuro que realice una tarea que ha decidido ahora".
Relajarse en Chipre
15

Aparte de los detalles técnicos, los cierres son un requisito previo fundamental para un estilo de programación conocido como programación orientada a funciones. Un cierre se usa aproximadamente para lo mismo que usa un objeto en la programación orientada a objetos; Vincula datos (variables) junto con algún código (una función), que luego puede pasar a otro lugar. Como tales, tienen un impacto en la forma en que escribe programas o, si no cambia la forma en que escribe sus programas, no tienen ningún impacto en absoluto.

En el contexto de PHP, son un poco extraños, ya que PHP ya es pesado en el paradigma orientado a objetos basado en clases, así como en el antiguo procedimiento. Por lo general, los lenguajes que tienen cierres tienen un alcance léxico completo. Para mantener la compatibilidad con versiones anteriores, PHP no va a obtener esto, por lo que significa que los cierres serán un poco diferentes aquí que en otros lenguajes. Creo que todavía tenemos que ver exactamente cómo se utilizarán.

troelskn
fuente
10

Me gusta el contexto proporcionado por la publicación de troelskn. Cuando quiero hacer algo como el ejemplo de Dan Udey en PHP, uso el patrón de estrategia OO. En mi opinión, esto es mucho mejor que introducir una nueva función global cuyo comportamiento se determina en tiempo de ejecución.

http://en.wikipedia.org/wiki/Strategy_pattern

También puede llamar a funciones y métodos usando una variable que contenga el nombre del método en PHP, lo cual es genial. por lo que otra versión del ejemplo de Dan sería algo como esto:

class ConfigurableEncoder{
        private $algorithm = 'multiply';  //default is multiply

        public function encode($x){
                return call_user_func(array($this,$this->algorithm),$x);
        }

        public function multiply($x){
                return $x * 5;
        }

        public function add($x){
                return $x + 5;
        }

        public function setAlgorithm($algName){
                switch(strtolower($algName)){
                        case 'add':
                                $this->algorithm = 'add';
                                break;
                        case 'multiply':        //fall through
                        default:                //default is multiply
                                $this->algorithm = 'multiply';
                                break;
                }
        }
}

$raw = 5;
$encoder = new ConfigurableEncoder();                           // set to multiply
echo "raw: $raw\n";                                             // 5
echo "multiply: " . $encoder->encode($raw) . "\n";              // 25
$encoder->setAlgorithm('add');
echo "add: " . $encoder->encode($raw) . "\n";                   // 10

por supuesto, si desea que esté disponible en todas partes, puede hacer que todo esté estático ...

Grossvogel
fuente
2

Un cierre es básicamente una función para la que escribe la definición en un contexto pero se ejecuta en otro contexto. Javascript me ayudó mucho a comprenderlos, porque se utilizan en JavaScript en todas partes.

En PHP, son menos efectivos que en JavaScript, debido a las diferencias en el alcance y la accesibilidad de las variables "globales" (o "externas") dentro de las funciones. Sin embargo, a partir de PHP 5.4, los cierres pueden acceder al objeto $ this cuando se ejecutan dentro de un objeto, esto los hace mucho más efectivos.

De esto se tratan los cierres, y debería ser suficiente para entender lo que está escrito arriba.

Esto significa que debería ser posible escribir una definición de función en algún lugar y usar la variable $ this dentro de la definición de función, luego asignar la definición de función a una variable (otros han dado ejemplos de la sintaxis), luego pasar esta variable a un objeto y llamarlo en el contexto del objeto, la función puede acceder y manipular el objeto a través de $ this como si fuera solo otro de sus métodos, cuando de hecho no está definido en la definición de clase de ese objeto, sino en otro lugar.

Si no está muy claro, no se preocupe, se aclarará una vez que comience a usarlos.

Rolf
fuente
Sinceramente, esto no está nada claro, ni siquiera para mí, el autor. Básicamente estoy diciendo: para saber qué son los cierres, compruébalos en JavaScript, pero ten en cuenta que el alcance de las variables es diferente entre JavaScript y PHP.
Rolf
1

Básicamente, Closure son las funciones internas que tienen acceso a las variables externas y se utilizan como una función de devolución de llamada a una función anónima (funciones que no tienen ningún nombre).

 <?php
      $param='ironman';
      function sayhello(){
          $param='captain';
          $func=function () use ($param){
                $param='spiderman';
          };
       $func();
       echo  $param;
       }
      sayhello();
?>

//output captain

//and if we pass variable as a reference as(&$param) then output would be spider man;
Harsh Gehlot
fuente
$param='captain'in func sayhello()es una variable local de func sayhello(). $param='ironman'arriba sayhello()es la variable global. Si desea hacer solo una variable $ param en su script, debe llamar: global $param;dentro de sayhello()func
vlakov
0

Aquí hay ejemplos de cierres en php

// Author: [email protected]
// Publish on: 2017-08-28

class users
{
    private $users = null;
    private $i = 5;

    function __construct(){
        // Get users from database
        $this->users = array('a', 'b', 'c', 'd', 'e', 'f');
    }

    function displayUsers($callback){
        for($n=0; $n<=$this->i; $n++){
            echo  $callback($this->users[$n], $n);
        }
    }

    function showUsers($callback){
        return $callback($this->users);

    }

    function getUserByID($id, $callback){
        $user = isset($this->users[$id]) ? $this->users[$id] : null;
        return $callback($user);
    }

}

$u = new users();

$u->displayUsers(function($username, $userID){
    echo "$userID -> $username<br>";
});

$u->showUsers(function($users){
    foreach($users as $user){
        echo strtoupper($user).' ';
    }

});

$x = $u->getUserByID(2, function($user){

    return "<h1>$user</h1>";
});

echo ($x);

Salida:

0 -> a
1 -> b
2 -> c
3 -> d
4 -> e
5 -> f

A B C D E F 

c
Hisham Dalal
fuente
0

Cierres:

MDN tiene la mejor explicación en mi opinión:

Un cierre es la combinación de una función agrupada (encerrada) con referencias a su estado circundante (el entorno léxico). En otras palabras, un cierre le da acceso al alcance de una función externa desde una función interna.

es decir, un cierre es una función con acceso a las variables que están en el ámbito principal. Un cierre nos permite crear funciones cómodamente sobre la marcha, ya que en algunas situaciones una función solo se necesita en un lugar (devoluciones de llamada, argumentos invocables).

Ejemplo:

$arr = [1,2,3,3];
$outersScopeNr = 2;

// The second arg in array_filter is a closure
// It would be inconvenient to have this function in global namespace
// The use keyword lets us access a variable in an outer scope
$newArr = array_filter($arr, function ($el) use ($outersScopeNr) {
    return $el === 3 || $el === $outersScopeNr;
});

var_dump($newArr);
// array (size=3)
//  1 => int 2
//  2 => int 3
//  3 => int 3
Willem van der Veen
fuente