Skip to content

7 funciones anonimas y closures en php

@intelguasoft edited this page Jan 22, 2020 · 1 revision

Curso Master PHP

Funciones anónimas y closures

Las funciones anónimas son funciones sin nombre:

function() {
    return "Hola";
};

Se puede llamar a una función anónima de dos formas: utilizando la sintaxis de funciones variables o llamarla desde otra función en forma de argumento.

Empleando la sintaxis de funciones variables, el valor de la variable es la función:

$saludo = function() {
    return "Hola que tal";
};

echo $saludo(); // Devuelve: Hola que tal

Añadiendo la función en otra:

function decir ($algo) {
    echo $algo();
}

decir(function(){
    return "Esto es algo";
});

La función decir() es una función regular normal, cuyo argumento es $algo. decir() imprimirá lo que devuelva la función $algo(). Al ejecutar decir() le pasamos el argumento: una función anónima que devuelve "Esto es algo".

Una función anónima no es más que una función que no tiene nombre, y por tanto no está enlazada a ningún identificador.

Una clausura o closure es una función anónima que captura el scope actual, y proporciona acceso a ese scope cuando se invoca el closure.

Las clausuras permiten usar variables mediante la palabra use:

$colorCoche = 'rojo';

$mostrarColor = function() use ($colorCoche) {
    echo "El color del coche es $colorCoche";
};

$mostrarColor(); // Mostrará El color del coche es rojo

Cuando se emplea use, se hereda una variable del ámbito padre. Esto no es lo mismo que usar variables globales. El ámbito padre de una clausura es el ámbito de la función en el que la clausura fue declarada (no desde la función desde la que se llamó).

Si se altera el valor de la variable $colorCoche dentro de la clausura, no afectará a la variable original:

$colorCoche = 'rojo';

$mostrarColor = function() use ($colorCoche) {
    $colorCoche = 'azul';
};

$mostrarColor();
echo $colorCoche; // Mostrará rojo

Si se quiere que su valor se altere, se pasa por referencia añadiéndole un ampersand & delante:

$colorCoche = 'rojo';

$mostrarColor = function() use (&$colorCoche) {
    $colorCoche = 'azul';
};

$mostrarColor();
echo $colorCoche; // Mostrará azul

También es posible añadir argumentos en las clausuras:

// Las clausuras también aceptan argumentos normales
$mensaje = "¿Qué tal?";
$saludar = function ($saludo) use ($mensaje) {
    echo $saludo . ', ' . $mensaje;
};
$saludar("Hola"); // Devuelve: Hola, ¿Qué tal?

La principal utilidad de las funciones anónimas es como parámetro de los callbacks, que son funciones llamadas por otra función que usa a la primera como parámetro. Se llama a un callback cuando ocurre un evento. Hay algunas funciones PHP que usan un callback como argumento, como array_map, array_filter o array_walk. La más sencilla es call_user_func, cuyo primer argumento es el callback y el segundo son los argumentos del primero:

$saludo = function($nombre)
{
    printf("Hola %s\r\n", $nombre);
};

$saludo('Carlos'); // Devuelve Hola Carlos
call_user_func($saludo, "PHP"); // Devuelve Hola PHP

Se puede llamar a funciones simples o a métodos de un objeto:

function mostrarColor() {
    echo 'Rojo';
}
class Numeros {
    static function mostrarNumero()
    {
        echo "5";
    }
}

call_user_func('mostrarColor');  // Muestra rojo

call_user_func(array('Numeros', 'mostrarNumero')); // Muestra 5
// Esta forma de llamar al método es estática.
// Si el método no fuera static, con esta forma generaría un error
// Strict Standards, non-static method should not be called statically

// Llamada a un método estático o no estático:
$numeros = new Numeros;
call_user_func(array($numeros, 'mostrarNumero')); // Muestra 5

// Llamada a un método estático:
call_user_func('Numeros::mostrarNumero'); // Muestra 5

Otra función que también emplea callbacks es array_walk, que aplica una función a cada miembro de un array:

class Coche
{
    protected $coches = array();
    public function add($coche, $potencia)
    {
        $this->coches[$coche] = $potencia;
    }
    public function mostrarCoches()
    {
        $callback = function ($potencia, $coche) {
            echo $coche . " -> " . $potencia . "\n";
        };
        array_walk($this->coches, $callback);
    }
}
$coche = new Coche;
$coche->add('Audi', '200');
$coche->add('BMW', '220');
$coche->add('Mercedes', 250);
// Empleamos el método mostrarCoches()
$coche->mostrarCoches(); // Devuelve: Audi -> 200 BMW -> 220 Mercedes -> 250

En la función array_walk, el primer parámetro es un array, y el segundo un callback que suele aceptar dos parámetros, el primero es el valor ($potencia) y el segundo la clave ($coche). El callback también puede aceptar sólo un parámetro:

<?php
$multiplicador = 2;

$numeros = [2, 4, 6, 8];

array_walk($numeros, function($numero) use ($multiplicador){
    echo $numero * $multiplicador . "\n";
}); // Devuelve: 4 8 12 16

En el artículo de funciones anónimas de la documentación PHP hay un buen ejemplo de la relación entre clausuras y ámbitos, es el siguiente:

class CarroDeLaCompra {
    const PRECIO_BROCOLI = 1.00;
    const PRECIO_PIMIENTO = 0.40;
    const PRECIO_CALABACIN = 0.60;

    protected $productos = array();

    public function añadir($producto, $cantidad)
    {
        $this->productos[$producto] = $cantidad;
    }
    public function obtenerTotal($impuesto)
    {
        $total = 0.00;

        $callback = function ($cantidad, $producto) use ($impuesto, &$total)
        {
            $precioUnidad = constant(__CLASS__ . "::PRECIO_" .
                strtoupper($producto));
            $total += ($precioUnidad * $cantidad) * ($impuesto + 1.0);
        };

        array_walk($this->productos, $callback);

        return round($total, 2);
    }
}
$miCarro = new CarroDeLaCompra();
// Añadir artículos al carro de la compra
$miCarro->añadir('brocoli', 2);
$miCarro->añadir('pimiento', 4);
$miCarro->añadir('calabacin', 3);
// Devolver el total con un impuesto del 5%
echo $miCarro->obtenerTotal(0.05) . "<br>";
// Resultado: 5.67

Se puede usar $this en las funciones anónimas, además de funciones como func_num_args(), func_get_arg() y func_get_arg() desde dentro de la clausura.

Los closures son realmente objetos, instancias de la clase Closure. Esta clase dispone de los métodos bind() y bindTo(), que permiten duplicar una clausura con un objeto vinculado y ámbito de clase nuevos.

public Closure Closure::bindTo (object $newthis [,mixed $newscope = "static" ])

Lo que hace es crear un nuevo closure en el scope del nuevo objeto $newthis, por lo que tendrá los valores de este nuevo objeto:

class ClaseA {
    function __construct($val) {
        $this->val = $val;
    }
    function getClosure() {
        // Devuelve la clausura vinculada a este objeto y el ámbito
        return function() { return $this->val; };
    }
}

$objeto1 = new ClaseA(1);
$objeto2 = new ClaseA(2);

// Creamos el closure de forma normal
$closure = $objeto1->getClosure();
echo $closure(), "\n"; // Devuelve 1

// Ahora creamos el closure desde el anterior, vinculándolo a otro objeto
$closure = $closure->bindTo($objeto2);
echo $closure(), "\n"; // Devuelve 2

Para ver un poco mejor sus posibilidades en la práctica, vamos a crear una claseUno con propiedades privadas:

class ClaseUno
{
    private $privadaUno = "Valor Uno";
    private $privadaDos = "Valor Dos";
    public function obtenerPropiedades()
    {
        return 'Las propiedades privadas son: '.
        $this->privadaUno . ' y ' . $this->privadaDos;
    }
}
$claseUno = new ClaseUno();
echo $claseUno->obtenerPropiedades() . "\n"; // Devuelve Valor 1 Valor 2

En el mismo archivo vamos a crear ClaseDos, donde vamos a acceder a esas propiedades privadas mediante binding:

class ClaseDos
{
    public function obtenerPropiedadUno()
    {
        $claseUno = new claseUno();
        $closure = function(ClaseUno $claseUno) {
            return $claseUno->privadaUno;
        };
        $bind = Closure::bind($closure, null, $claseUno);
        return $bind($claseUno);
    }
    public function obtenerPropiedadDos()
    {
        $claseUno = new ClaseUno();
        $closure = function(ClaseUno $claseUno) {
            return $claseUno->privadaDos;
        };

        $bind = Closure::bind($closure, null, $claseUno);
        return $bind($claseUno);
    }
}
$claseDos = new ClaseDos();
echo $claseDos->obtenerPropiedadUno() . "\n";
echo $claseDos->obtenerPropiedadDos() . "\n";

bind() es una versión estática de bindTo().

public static Closure Closure::bind (Closure $closure, object $newthis [, mixed $newscope = "static" ])

Ejemplo:

class ClaseA {
    private static $soyUno = "Valor Uno";
    private $soyDos = "Valor Dos";
}
$closureUno = static function() {
    return ClaseA::$soyUno;
};
$closureDos = function() {
    return $this->soyDos;
};
$primerBind = Closure::bind($closureUno, null, 'ClaseA');
$segundoBind = Closure::bind($closureDos, new ClaseA(), 'ClaseA');
echo $primerBind(), "\n"; // Devuelve: Valor Uno
echo $segundoBind(), "\n"; // Devuelve: Valor Dos
Anterior Siguiente

Indice de contenidos

Básicos Sintaxis básica
Operadores
Operadores bit a bit
Variables
Estructuras de control
Constantes y constructores base
Espacio de nombres
Extensiones
Configuraciones
Variables al descubierto
Recolector de basuras
Rendimiento (Performance)
Funciones Funciones
Argumentos en funciones
Funciones variables
Valores por referencia en funciones
Funciones que devuelven algo
Ámbito de variables
Funciones anónimas y closure's
Cadenas y patrones Las comillas y las cadenas de caracteres
Heredoc y Nowdoc
Comparando cadenas de caracteres
Extracción en cadenas de caracteres
Análisis en cadenas de caracteres
Reemplazos en cadenas de caracteres
Formato de cadena de caracteres
Expresiones regulares (RegEx)
Codificación de caracteres
Codificación en cadenas de caracteres
Arreglos (Array's) Arreglos
Arreglos asociativos
Iterar arreglos
Funciones de arreglos
SPL Arreglos mediante objetos
Conversión de arreglos
Archivos (I/O) Manejo de archivos
Lectura de archivos
Escritura de archivos
Funciones del sistema de archivos
Socket's y archivos
Streams (?)
Seguridad Concepto y funcionamiento CGI
Configurando la seguridad
Seguridad en Sesiones
Ataques XSS
Ataques CSRF
Ataques SQLInjection
Ataques CodeInjection
Ataques EmailInjection
Filtrado de datos de entrada
Escape de datos de salida
Encriptación y contraseñas
Seguridad en el almacenamiento de datos
Seguridad en la subida de datos
SSL y OpenSSL
Base de datos Principios básicos SQL
Los joins en SQL
Principales funciones SQL
Preparando sentencias SQL
Transacciones en SQL
Algo de PDO
Programación Orientada a Objetos Instancias de clases
Modificadores y herencia de clases
Interfaces
Excepciones
Auto-carga (Autoload)
Reflexión (Reflection)
Determinación de tipos (Type hinting)
Constantes de clase
Enlace estático de ejecución (Late Static Binding)
Métodos mágicos
Librería estándar PHP (SPL)
Generadores (Generators)
Traits
Clases abstractas
Formatos de información Algo de XML
Algo de SimpleXML
Algo de XML Parser
Algo de PHP DOM
Algo de Web Services
Web Services con SOAP
Algo de REST
Algo de JSON
Formato de fecha y hora
Características web's Sesiones
Formularios
Métodos GET y POST
Cookies
Protocolo HTTP y sus headers
Autenticación HTTP
Códigos de estado HTTP
Referencias Referencias
Recopilación
Conclusión
Clone this wiki locally