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

Начиная с версии 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 — это обстоятельство можно использовать для контроля типа переданной в метод переменной.
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 40

    +3
    Стоит отметить, что в PHP 5.4 также стал доступен контекст $this в анонимной функции.
      +1
      А еще трейты. На них тоже кеширование реализуется в полпинка.
        0
        Пример можно?
        0
        Да! Жалко только, что сразу не сделали в 5.3
          +2
          в 5.3 можно использовать трюк
          $_this = $this;
          function() use($_this) {
               // например echo $_this->nickname; 
          };
          

          но конечно private и protected члены будут недоступны в замыкании
            0
            яваскриптовый that больше радует глаз :)
            $that = $this;
            
          +4
          А плюс то в чем? Вы велосипед изобрели. Паттерну отложенной загрузги уже сто лет в обед.
            +4
            Паттерн известный, но когда нужно было решить похожую задачу в 4 PHP — красивого решения я не нашёл (если такое есть, буду очень рад его посмотреть). А цель поста была указать на непривычное применение замыканий, для гуру здесь конечно откровений никаких нет, но для кого-то я думаю этот пост будет полезен. Может кто-то увидит решение своей проблемы, связанной с отложенным выполнением кода, и выложит здесь.
              +1
              Хм. Вы считаете, что вариант с замыканиями в данном случае красивее? Ведь первый вариант прост как тапочки, и соответственно поддерживать его легче. В чем смысл изощрений?
            +1
            Спасибо огоромное за статью. Только один нюанс. Не учите людей плохому. Уберите статики. Иначе Ваши классы превращаются в набор функций. Еще раз спасибо.
              0
              Если понравилось, буду продолжать. Сейчас готовлю себя к ZCE — в процессе всплывает много интересных вещей, которые раньше были в тени. В этом примере классы Cache и PostModel — эот скорее условный код, чтобы показать идею, но в следующих постах буду аккуратнее )
                0
                Мой совет — учите все для ZCE. Когда я сдавал нужно было знать все параметры некоторых функций и их последоватьельность. Удачи!
                  0
                  >Сейчас готовлю себя к ZCE
                  Зачем?
                –5
                Ой. А в PHP нельзя сделать как-то так?
                @ lru_cache #пробела после собаки тут на самом деле нет
                def get(date):
                  return query(blablabla(date))
                
                  0
                  Если не ошибаюсь, то в PHP нет декораторов
                    0
                    Ну не обязательно именно такой синтаксис: get = lru_cache(get) тоже сойдет. Имеется в виду — сделать кеширование во возможности не внося изменений в саму функцию/класс, выдающие данные.
                      0
                      В контексте объектов для этого можно использовать магию.
                  +4
                  Вполне нормально смотрится и так, или я чего-то не понимаю
                  <?php 
                  $dateCreated = date('Y-m-d');
                  $data = $cache->load($dateCreated);
                  if (!$data) {
                  	$data = PostModel::getList($dateCreated);
                  	$cache->set($dateCreated, $data);
                  }
                  
                    +2
                    Открываю я топик. Думаю «Ууух тыыы, наконец-то что-то дельное». И тут такая статья…
                    Ну вы меня поняли.
                      0
                      Нетуда запостил :(
                    0
                    А $cache кто создавать будет?
                      0
                      Тот же, кто опишет метод PostModel::getList
                    0
                    Объясните, почему в первом листинге все методы объявлены без method body? Это же не абстрактные методы и классы.
                      0
                      классы Cache и PostModel — это скорее условный код, чтобы показать общую идею, хотя наверно это мой недочет
                        0
                        Ага, спасибо. Иначе Fatal error.
                    • UFO just landed and posted this here
                        +1
                        спокойнее
                          0
                          Прочь эмоциональную критику, даёшь конструктивные замечания!
                          • UFO just landed and posted this here
                              0
                              Полностью согласен с Вашими замечаниями.
                                0
                                Пользуюсь (и вроде как успешно) замыканиями в пхп уже как пару лет, но суть данной статьи не понял вообще. Совсем не понял.


                                Кстати, да. Но, может быть, Вы тогда поделитесь своим опытом в этом отношении?
                                • UFO just landed and posted this here
                                  0
                                  >2. Далее. У автора описан класс Cache…
                                  в этом классе пример представлен как псевдокод, код связанный с реализацией конкретного кеша пропущен
                                  >6. Автору нужно отправиться в школу, обычную школу…
                                  а он собрался на ZCE
                                    0
                                    >1. Ну во-первых, после фигурной скобки завершающей тело анонимной функции хорошо бы поставить точку с запятой.
                                    Пропустил, исправил

                                    Про методы без тела уже сказали выше, и это исправлено.

                                    Методы у класса Cache естественно public — я же могу обращаться к нему и напрямую без обертки. Обертка это скорее сахар, и хороший пример передачи кода через замыкания (на мой взгляд).

                                    >6. Автору нужно отправиться в школу, обычную школу.
                                    Зачем грубишь? Статья первая, если критика по существу — то только на пользу.
                                      0
                                      И не лень же было столько понавыписывать…
                                      Самое удивительное то, что в подобной статье самое большое негодование вызвало оформление(!) примера. По моему, любому мало-мальски знакомому с php программисту понятно, что автор хотел сказать в примере и что «лишнее» вырезано для краткости. Пример здесь служит для демонстрации логики, а не реализации. Ну, конечно, если до каждой точки с запятой докапываться, то вам надо энциклопедии читать, а не статьи в инете. К слову сказать вы тоже пунктуацией не блещете.

                                      Критика по существу: смысл всей статьи сводится к 1 предложению — «Используя замыкания можно передавать в метод (функцию) или возвращать из него, фрагмент готового к исполнению кода, с локальными переменными из того окружения где этот код объявлен», что можно было продемонстрировать гораздо проще (например, как в документации).
                                  0
                                  если уж кэшируются результаты запросов в базу — то и ключ можно было бы построить иначе. и таким образом получить код, который не требует прерываний и выполняется только если это на самом деле нужно.
                                  хотя возможно в статье просто слишком обобщённый пример
                                    0
                                    все хорошо, но так и не понял двух моментов:
                                    зачем использовать замыкания для кеша и зачем в кеше использовать какой-то счетчик?
                                      0
                                      счетчик был введён намерено, для демонстрации импорта переменной в замыкание по ссылке, если эту переменная должна быть изменена.
                                      +1
                                      По-моему кэш все-таки прослойкой должен быть, а не кусками кода понапиханными во все модели (и не важно как эти куски кода реализованны, замыкания, трейты или еще что).

                                      Only users with full accounts can post comments. Log in, please.