PHP
закрытие
Поиск…
Основное использование закрытия
Закрытие представляет собой эквивалент PHP анонимной функции, например. функция, не имеющая имени. Даже если это технически не правильно, поведение закрытия остается таким же, как функция, с несколькими дополнительными функциями.
Закрытие - это не что иное, как объект класса Closure, который создается путем объявления функции без имени. Например:
<?php
$myClosure = function() {
echo 'Hello world!';
};
$myClosure(); // Shows "Hello world!"
Имейте в виду, что $myClosure
является экземпляром Closure
так что вы знаете, что вы действительно можете с ним сделать (см. Http://fr2.php.net/manual/en/class.closure.php )
Классический случай, в котором вам понадобится Closure, - это когда вы должны дать возможность callable
функции, например usort .
Вот пример, где массив сортируется по числу братьев и сестер каждого человека:
<?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
Использование внешних переменных
Внутри замыкания можно использовать внешнюю переменную со специальным использованием ключевых слов. Например:
<?php
$quantity = 1;
$calculator = function($number) use($quantity) {
return $number + $quantity;
};
var_dump($calculator(2)); // Shows "3"
Вы можете пойти дальше, создав «динамические» замыкания. Можно создать функцию, которая возвращает определенный калькулятор, в зависимости от количества, которое вы хотите добавить. Например:
<?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"
Базовое связывание
Как было замечено ранее, закрытие представляет собой не что иное, как экземпляр класса Closure, и на них можно вызвать разные методы. Одним из них является bindTo
, который при закрытии возвращает новый, привязанный к данному объекту. Например:
<?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!"
Связывание с закрытием и область действия
Рассмотрим этот пример:
<?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!"
Попытайтесь изменить видимость property
как на protected
и на private
. Вы получаете фатальную ошибку, указывающую, что у вас нет доступа к этому свойству. Действительно, даже если закрытие связано с объектом, область, в которой выполняется замыкание, не та, которая необходима для доступа. Это и есть второй аргумент bindTo
.
Единственный способ доступа к объекту, если он является private
- это доступ к нему из области, которая позволяет это, т.е. класс. В только что предыдущем примере кода область не была указана, что означает, что закрытие было вызвано в той же области, что и в случае, когда была создана закрытие. Давайте изменим это:
<?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!"
Как только что сказано, если этот второй параметр не используется, замыкание вызывается в том же контексте, что и тот, который используется, когда было создано закрытие. Например, замыкание, созданное внутри класса метода, которое вызывается в контексте объекта, будет иметь ту же область, что и метод:
<?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!"
Привязка закрытия для одного вызова
Начиная с PHP7 , можно связать замыкание только для одного вызова, благодаря методу call
. Например:
<?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!"
В отличие от метода bindTo
, об этом не беспокоиться. Область, используемая для этого вызова, такая же, как та, которая используется при доступе или вызове свойства $myInstance
.
Используйте блокировки для реализации шаблона наблюдателя
В общем, наблюдатель представляет собой класс со специальным методом, вызываемым при действии на наблюдаемый объект. В некоторых ситуациях закрытия может быть достаточно для реализации шаблона проектирования наблюдателя.
Вот подробный пример такой реализации. Давайте сначала объявим класс, целью которого является уведомление наблюдателей при изменении его свойства.
<?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();
}
}
Затем давайте объявим класс, который будет представлять разные наблюдатели.
<?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);
}
}
Давайте, наконец, протестируем это:
<?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!
Обратите внимание, что этот пример работает, потому что наблюдатели имеют один и тот же характер (оба они называются наблюдателями.)