PHP
Zamknięcie
Szukaj…
Podstawowe użycie zamknięcia
Zamknięcie jest odpowiednikiem anonimowej funkcji PHP, np. funkcja, która nie ma nazwy. Nawet jeśli nie jest to technicznie poprawne, zachowanie zamknięcia pozostaje takie samo jak funkcji, z kilkoma dodatkowymi funkcjami.
Zamknięcie jest niczym innym jak obiektem klasy Closure, który jest tworzony przez zadeklarowanie funkcji bez nazwy. Na przykład:
<?php
$myClosure = function() {
echo 'Hello world!';
};
$myClosure(); // Shows "Hello world!"
Pamiętaj, że $myClosure
jest instancją Closure
dzięki czemu masz świadomość tego, co możesz naprawdę z tym zrobić (por. Http://fr2.php.net/manual/en/class.closure.php )
Klasycznym przypadkiem, w którym potrzebujesz zamknięcia, jest podanie funkcji callable
funkcji, na przykład usort .
Oto przykład, w którym tablica jest posortowana według liczby rodzeństwa każdej osoby:
<?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
Korzystanie ze zmiennych zewnętrznych
W zamknięciu możliwe jest użycie zmiennej zewnętrznej ze specjalnym użyciem słowa kluczowego. Na przykład:
<?php
$quantity = 1;
$calculator = function($number) use($quantity) {
return $number + $quantity;
};
var_dump($calculator(2)); // Shows "3"
Możesz pójść dalej, tworząc „dynamiczne” zamknięcia. Możliwe jest utworzenie funkcji, która zwraca określony kalkulator, w zależności od ilości, którą chcesz dodać. Na przykład:
<?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"
Podstawowe wiązanie zamknięcia
Jak widać poprzednio, zamknięcie jest niczym innym jak instancją klasy Zamknięcie i można na nich wywoływać różne metody. Jednym z nich jest bindTo
, który po zamknięciu zwróci nowy, związany z danym obiektem. Na przykład:
<?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!"
Wiążące zamknięcie i zakres
Rozważmy ten przykład:
<?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!"
Spróbuj zmienić widoczność property
na protected
lub private
. Pojawia się błąd krytyczny wskazujący, że nie masz dostępu do tej właściwości. Rzeczywiście, nawet jeśli zamknięcie zostało powiązane z obiektem, zakres, w którym wywoływane jest zamknięcie, nie jest tym, który jest potrzebny do uzyskania tego dostępu. Do tego służy drugi argument bindTo
.
Jedynym sposobem uzyskania dostępu do właściwości, jeśli jest ona private
, jest dostęp z zakresu, który na to pozwala, tj. zakres klasy. W poprzednim przykładzie kodu zakres nie został określony, co oznacza, że wywołanie zamknięcia zostało wywołane w tym samym zakresie, co w przypadku, w którym zamknięcie zostało utworzone. Zmieńmy to:
<?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!"
Jak już powiedziano, jeśli ten drugi parametr nie jest używany, wywołanie zamknięcia jest wywoływane w tym samym kontekście, co w przypadku tego, w którym zamknięcie zostało utworzone. Na przykład zamknięcie utworzone w klasie metody, która jest wywoływana w kontekście obiektu, będzie miało taki sam zakres jak metoda:
<?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!"
Wiązanie zamknięcia dla jednego połączenia
Od PHP7 możliwe jest powiązanie zamknięcia tylko dla jednego wywołania, dzięki metodzie call
. Na przykład:
<?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!"
W przeciwieństwie do metody bindTo
nie ma się czym martwić. Zakres używany dla tego wywołania jest taki sam, jak zakres używany podczas uzyskiwania dostępu lub wywoływania właściwości $myInstance
.
Użyj zamknięć, aby wprowadzić wzór obserwatora
Zasadniczo obserwator jest klasą, w której wywoływana jest określona metoda, gdy nastąpi akcja na obserwowanym obiekcie. W niektórych sytuacjach zamknięcia mogą wystarczyć do wdrożenia wzorca projektowego obserwatora.
Oto szczegółowy przykład takiej implementacji. Najpierw zadeklarujmy klasę, której celem jest powiadamianie obserwatorów o zmianie jej właściwości.
<?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();
}
}
Następnie zadeklarujmy klasę, która będzie reprezentować różnych obserwatorów.
<?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);
}
}
W końcu przetestujmy to:
<?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!
Zauważ, że ten przykład działa, ponieważ obserwatorzy mają ten sam charakter (obaj są „nazwanymi obserwatorami”).