Поиск…


Основное использование закрытия

Закрытие представляет собой эквивалент 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!

Обратите внимание, что этот пример работает, потому что наблюдатели имеют один и тот же характер (оба они называются наблюдателями.)



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow