Pull to refresh

Наглядный пример использования замыканий в PHP

Reading time 2 min
Views 12K
Начиная с версии 5.3, PHP позволяет создавать замыкания. К сожалению, пример их использования в официальной документации http://www.php.net/manual/en/functions.anonymous.php#example-163 (example 3) обладает редкой изощрённостью и надуманностью. Надеюсь, пример под катом поможет увидеть в замыканиях другое применение, кроме как с функциями типа array_map().

Самый распространённый метод повышения производительности приложения – это кэширование, и обычно схема его применения выглядит так:
<?php 
Class Cache
{
    static function set( $key, $value) {
       //  код записи в кеш
    }
    static function get( $key ) {
       // код чтения из кеша
    }
}

Class PostModel
{
    static function getList($dateCreated) {
       // код получения постов с указанной даты
    }
}

$dateCreated = date('Y-m-d');
$posts = Cache::get("posts_" . $dateCreated);

if( !$posts ) {
   $posts = PostModel::getList($dateCreated);
   Cache::set( "posts_" . $dateCreated , $posts); 
}
?>


Пытаемся получить данные из кеша, если данные не найдены — делаем запрос к БД и пишем результат в кеш. Логика каждый раз почти одинаковая и хотелось бы написать универсальную обёртку для таких случаев, но как передавать в неё не просто переменную, а кусок кода, который должен выполняться уже внутри обёртки, т.е. отложено?

И тут на помощь приходят замыкания, чтобы передать в функцию (или метод) кусок кода для отложенного выполнения его нужно обернуть анонимной функцией.
<?php 
$dateCreated = date('Y-m-d');
$dbQueryCounter = 0;
$fallback = function() use($dateCreated, &$dbQueryCounter) { 
   $dbQueryCounter++;   //счетчик импортирован в замыкание по ссылке
   return PostModel::getList($dateCreated); // не забываем return
}; 
Cache::wrapper( "posts_" . $dateCreated , $fallback );

Class Cache
{
    static function set( $key, $value) {
       //  код записи в кеш
    }
    static function get( $key ) {
       // код чтения из кеша
    }
    static function wrapper( $key, Closure $fallback ) {
        $data = self::get( $key );
        if( !$data ) {
            $data = $fallback(); // отложенное выполнение кода
            self::set( $key, $data); 
        }
        return $data;
    }
}
?>

На что в этом коде нужно обратить внимание:
  1. Используя замыкания можно передавать в метод (функцию) или возвращать из него, фрагмент готового к исполнению кода, с локальными переменными из того окружения где этот код объявлен.
  2. Если код, который мы передаем в метод для отложенного выполнения, должен возвращать данные — не забываем про return в замыкании.
  3. Вместе с фрагментом кода, через замыкание, можно передать и все необходимые переменные из того контекста, где этот код используется, используя ключевое слово use — это принципиальное отличие объявления анонимной функции в PHP 5.3 от использования create_function() в более ранних версиях.
  4. Переменные импортируются в замыкание по значению, поэтому если какую-либо переменную нужно внутри замыкания изменять (например счетчик $dbQueryCounter), то её нужно импортировать по ссылке.
  5. Анонимные функции в PHP5.3 являются экземплярами класса Closure — это обстоятельство можно использовать для контроля типа переданной в метод переменной.
Tags:
Hubs:
+22
Comments 40
Comments Comments 40

Articles