Buscar..
Uso básico de un cierre.
Un cierre es el equivalente de PHP de una función anónima, por ejemplo. Una función que no tiene nombre. Incluso si eso no es técnicamente correcto, el comportamiento de un cierre sigue siendo el mismo que el de una función, con algunas características adicionales.
Un cierre no es más que un objeto de la clase Closure que se crea al declarar una función sin nombre. Por ejemplo:
<?php
$myClosure = function() {
echo 'Hello world!';
};
$myClosure(); // Shows "Hello world!"
Tenga en cuenta que $myClosure
es una instancia de Closure
para que esté al tanto de lo que realmente puede hacer con ella (consulte http://fr2.php.net/manual/en/class.closure.php )
El caso clásico que se necesita un cierre es cuando se tiene que dar una callable
a una función, por ejemplo usort .
Aquí hay un ejemplo donde una matriz está ordenada por el número de hermanos de cada persona:
<?php
$data = [
[
'name' => 'John',
'nbrOfSiblings' => 2,
],
[
'name' => 'Stan',
'nbrOfSiblings' => 1,
],
[
'name' => 'Tom',
'nbrOfSiblings' => 3,
]
];
usort($data, function($e1, $e2) {
if ($e1['nbrOfSiblings'] == $e2['nbrOfSiblings']) {
return 0;
}
return $e1['nbrOfSiblings'] < $e2['nbrOfSiblings'] ? -1 : 1;
});
var_dump($data); // Will show Stan first, then John and finally Tom
Utilizando variables externas
Es posible, dentro de un cierre, utilizar una variable externa con el uso especial de palabras clave. Por ejemplo:
<?php
$quantity = 1;
$calculator = function($number) use($quantity) {
return $number + $quantity;
};
var_dump($calculator(2)); // Shows "3"
Puedes ir más lejos creando cierres "dinámicos". Es posible crear una función que devuelva una calculadora específica, dependiendo de la cantidad que desee agregar. Por ejemplo:
<?php
function createCalculator($quantity) {
return function($number) use($quantity) {
return $number + $quantity;
};
}
$calculator1 = createCalculator(1);
$calculator2 = createCalculator(2);
var_dump($calculator1(2)); // Shows "3"
var_dump($calculator2(2)); // Shows "4"
Encuadernación de cierre básico.
Como se vio anteriormente, un cierre no es más que una instancia de la clase Closure, y se pueden invocar diferentes métodos en ellos. Uno de ellos es bindTo
, que, dado un cierre, devolverá uno nuevo que está vinculado a un objeto dado. Por ejemplo:
<?php
$myClosure = function() {
echo $this->property;
};
class MyClass
{
public $property;
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
}
$myInstance = new MyClass('Hello world!');
$myBoundClosure = $myClosure->bindTo($myInstance);
$myBoundClosure(); // Shows "Hello world!"
Cierre de encuadernación y alcance.
Consideremos este ejemplo:
<?php
$myClosure = function() {
echo $this->property;
};
class MyClass
{
public $property;
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
}
$myInstance = new MyClass('Hello world!');
$myBoundClosure = $myClosure->bindTo($myInstance);
$myBoundClosure(); // Shows "Hello world!"
Intente cambiar la visibilidad de la property
a protected
o private
. Recibes un error fatal que indica que no tienes acceso a esta propiedad. De hecho, incluso si el cierre se ha vinculado al objeto, el alcance en el que se invoca el cierre no es el necesario para tener ese acceso. Para eso es el segundo argumento de bindTo
.
La única forma de acceder a una propiedad si es private
es que se accede desde un ámbito que lo permita, es decir. El alcance de la clase. En el ejemplo de código anterior, el alcance no se ha especificado, lo que significa que el cierre se ha invocado en el mismo ámbito que el utilizado donde se creó el cierre. Vamos a cambiar eso:
<?php
$myClosure = function() {
echo $this->property;
};
class MyClass
{
private $property; // $property is now private
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
}
$myInstance = new MyClass('Hello world!');
$myBoundClosure = $myClosure->bindTo($myInstance, MyClass::class);
$myBoundClosure(); // Shows "Hello world!"
Como acabo de decir, si este segundo parámetro no se utiliza, el cierre se invoca en el mismo contexto que el utilizado donde se creó el cierre. Por ejemplo, un cierre creado dentro de una clase de método que se invoca en un contexto de objeto tendrá el mismo alcance que el método:
<?php
class MyClass
{
private $property;
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
public function getDisplayer()
{
return function() {
echo $this->property;
};
}
}
$myInstance = new MyClass('Hello world!');
$displayer = $myInstance->getDisplayer();
$displayer(); // Shows "Hello world!"
Encuadernación de un cierre para una llamada.
Desde PHP7 , es posible vincular un cierre solo para una llamada, gracias al método de call
. Por ejemplo:
<?php
class MyClass
{
private $property;
public function __construct($propertyValue)
{
$this->property = $propertyValue;
}
}
$myClosure = function() {
echo $this->property;
};
$myInstance = new MyClass('Hello world!');
$myClosure->call($myInstance); // Shows "Hello world!"
A diferencia del método bindTo
, no hay que preocuparse por el alcance. El alcance utilizado para esta llamada es el mismo que el utilizado para acceder o invocar una propiedad de $myInstance
.
Utilizar cierres para implementar patrón observador.
En general, un observador es una clase con un método específico que se llama cuando ocurre una acción en el objeto observado. En ciertas situaciones, los cierres pueden ser suficientes para implementar el patrón de diseño del observador.
Aquí hay un ejemplo detallado de tal implementación. Primero declaremos una clase cuyo propósito es notificar a los observadores cuando se cambia su propiedad.
<?php
class ObservedStuff implements SplSubject
{
protected $property;
protected $observers = [];
public function attach(SplObserver $observer)
{
$this->observers[] = $observer;
return $this;
}
public function detach(SplObserver $observer)
{
if (false !== $key = array_search($observer, $this->observers, true)) {
unset($this->observers[$key]);
}
}
public function notify()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function getProperty()
{
return $this->property;
}
public function setProperty($property)
{
$this->property = $property;
$this->notify();
}
}
Luego, declaremos la clase que representará a los diferentes observadores.
<?php
class NamedObserver implements SplObserver
{
protected $name;
protected $closure;
public function __construct(Closure $closure, $name)
{
$this->closure = $closure->bindTo($this, $this);
$this->name = $name;
}
public function update(SplSubject $subject)
{
$closure = $this->closure;
$closure($subject);
}
}
Por fin probemos esto:
<?php
$o = new ObservedStuff;
$observer1 = function(SplSubject $subject) {
echo $this->name, ' has been notified! New property value: ', $subject->getProperty(), "\n";
};
$observer2 = function(SplSubject $subject) {
echo $this->name, ' has been notified! New property value: ', $subject->getProperty(), "\n";
};
$o->attach(new NamedObserver($observer1, 'Observer1'))
->attach(new NamedObserver($observer2, 'Observer2'));
$o->setProperty('Hello world!');
// Shows:
// Observer1 has been notified! New property value: Hello world!
// Observer2 has been notified! New property value: Hello world!
Tenga en cuenta que este ejemplo funciona porque los observadores comparten la misma naturaleza (ambos son "observadores nombrados").