В этой заметке, я хочу предоставить еще один пример использования замыкания, т.к. в сети их довольно мало. Особое внимание уделено анонимной функции, с помощью которой и делается замыкание. Реализуем передачу в нее параметров из разных областей видимости для выполнение определенных задач.
В качестве примера - 2 класса, упрощенно демонстрирующих соединение пользователей с базой данных.
Class User { private $name, $pas; static public $countConnect = 0; public $host = 'localhost'; public $dbname = 'test'; function __construct($name, $pas) { $this->name = $name; $this->pas = $pas; $db = new Db(); $db->check($this->name, $this->pas, $this->fallback()); } function get($text) { echo $text; } function fallback() { $host = $this->host; $dbname = $this->dbname; $countConnect = self::$countConnect; $fallback = function($start) use ($host, $dbname, $countConnect) { $this->get($start); self::$countConnect++; return ['host' => $host, 'dbname' => $dbname, 'countConnect' => $countConnect]; }; return $fallback; } } class Db { const GO = 'Старт!.. '; function connect($name, $config) { echo 'Соединяю пользователя ' . $name; echo ' ' . $config['countConnect'] . '... '; echo 'Настройки: ' . $config['host'] . ', ' . $config['dbname']; echo '<br>'; } function check($name, $pas, Closure $fallback) { if ($name != 'admin' || $pas != '123') { echo 'Неверный логин или пароль.<br>'; return false; } $config = $fallback(self::GO); // отложенное выполнение кода. Результат сохраняем $this->connect($name, $config); //результат выполнения (массив)передаем } } $user1 = new User('admin', 123); $user1 = new User('admin', 555); $user1 = new User('admin', 123); $user1 = new User('admin', 123);Результат выполнения этого кода:
Старт!.. Соединяю пользователя admin 0... Настройки: localhost, test Неверный логин или пароль. Старт!.. Соединяю пользователя admin 1... Настройки: localhost, test Старт!.. Соединяю пользователя admin 2... Настройки: localhost, testПроверку логина и пароля я сделал просто для большей правдоподобности примера.
Итак, для соединения с базой данных, создается новый объект - экземпляр класса User, в конструктор которого пользователь передает свои данные (логин и пароль). Данный класс так же хранит настройки для входа в БД по-умолчанию:
public $host = 'localhost';
public $dbname = 'test';
и используется счетчик успешных соединений с БД.
Сохранив в свойствах класса данные пользователя, конструктор создает объект класса Db, который и производит соединение с БД. В данном классе реализована проверка логина и пароля, но в него не передаются дополнительные параметры, такие как хост и название базы данных. Это реализовано с помощью анонимной функции.
Вместе с данными пользователя, в метод check() класса Db так же передается наша анонимная функция:
$this->fallback()
которую возвращает метод fallback().
Анонимные функции c PHP5.3 являются экземплярами класса Closure, поэтому при приеме аргумента, можем ограничить его тип:
function check($name, $pas, Closure $fallback) { ...
До сих пор данная функция не вызывалась и только строкой
$config = $fallback(self::GO);
выполняем ее.
Тут следует обратить внимание на аргументы, которая она получает для своей работы. Эта анонимная функция выполняется в области видимости класса Db, поэтому мы легко передаем ей нужные данные в качестве аргуметов. В данном примере это константа "GO". Исходный класс, где она была объявлена не содержал такого параметра, поэтому мы предусмотрели его заранее. Кроме того, анонимная функция может получать доступ к данным из той области видимости, где она была объявлена - метод fallback() класса User с помощью оператора "use":
$fallback = function($start) use ($host, $dbname, $countConnect) { $this->get($start); self::$countConnect++; return ['host' => $host, 'dbname' => $dbname, 'countConnect' => $countConnect]; };Тут и проявляется механизм замыканий в PHP.
Также, внутри функции, мы имеем доступ к свойствам и методам класса в котором она объявлена - в примере мы увеличиваем значение статического свойства на единицу при каждом соединении пользователя:
self::$countConnect++;
Но мы могли сделать по-другому, используя передачу параметров в функцию по ссылке (для возможности изменения). В таком случае, метод мог бы выглядеть так:
function fallback() { $host = $this->host; $dbname = $this->dbname; static $countConnect = 0; $fallback = function($start) use ($host, $dbname, &$countConnect) { $this->get($start); $countConnect++; return ['host' => $host, 'dbname' => $dbname, 'countConnect' => $countConnect]; }; return $fallback; }создаем статическую переменную (принадлежащую классу, а не объекту, что позволяет сохранять свое значение при создании каждого нового экземпляра класса) $countConnect и при каждом следующем вызове анонимной функции увеличиваем ее значение на единицу.
Для примера, я показал передачу (и вывод в виде сообщения) аргумента функции из класса Db, а именно $start, которая, тем не менее, за счет анонимной функции появляется и используется в контексте класса User:
$this->get($start);
Так же, как я писал выше, анонимная функция у нас ответственна за передачу дополнительных параметров соединения с базой данных в класс Db. Поэтому она возвращает нужные данные:
return ['host' => $host, 'dbname' => $dbname, 'countConnect' => $countConnect];
которые сохраняются в переменную $config и передаются в метод устанавливающий соединение с БД:
$config = $fallback(self::GO);
$this->connect($name, $config);
Вот и все, применение замыканиям можно найти в самых разных случаях. Главное научиться ими пользоваться.