Двойное обертывание в PHP
В этой статье я расскажу о том, как делать двойное (ну или n-ое) обертывание некой функции в php 5.3 Вообще это может понадобиться для очень многих вещей: кэширование, логирование и т.д.
Задача.
Мы находимся в контроллере, в котором происходит итерация некой функции по месяцам. При этом в каждом экшене контроллера функция – разная. В тоже самое время мы хотим внедрить кэширование данных, которые выдают такие функции.
Представим себе, что мы находимся в контроллере, и там присутствует много вот такого кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/** * Информация собираемая с помощью SomeModel->getSome1 */ public function functionOneAction() { $months = $this->_em->getRepository('Model\SomeModel')->getInterval(); foreach($months as $elt) { $tmp = explode("-", $elt); $year = $tmp[0]; $month = $tmp[1]; $this->view->data[$year][$month] = $this->_em->getRepository('Model\SomeModel')->getSome1($year, $month); } } /** * Информация собираемая с помощью SomeModel->getSome2 */ public function functionTwoAction() { $months = $this->_em->getRepository('Model\SomeModel')->getInterval(); foreach($months as $elt) { $tmp = explode("-", $elt); $year = $tmp[0]; $month = $tmp[1]; $this->view->data[$year][$month] = $this->_em->getRepository('Model\SomeModel')->getSome2($year, $month); } } |
Отчётливо видим повторение кода.
Теперь второй случай, который наверняка многим знаком.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** *Получение данных */ public function getSome1($year = 2012, $month = 05) { $cachekey = __FUNCTION__.'_'.sprintf('%u', crc32(igbinary_serialize(var_export(func_get_args(),true)))); $cache = \Zend_Registry::get('cacheManager')->getCache('ttl_7200'); if($result = $cache->load($cachekey)) return $result; $sql = "SELECT count(s.id) as count FROM some_table s"; $results = $this->_em->getConnection()->executeQuery($sql); $tmp = $results->fetchAll(); $count = (int) $tmp[0]['count']; $result = $count; $cache->save($result, $cachekey); return $result; } |
И так в каждой функции, которая требует кэширование. Ух, сколько же можно сократить кода в проекте!)
Решение.
Будем использовать две обертки. Первая (внешнаяя) обертка (wrapper) будет оборачивать переданную функцию (fallback) итерируя её по месяцам. Вторая (внутренняя) обертка будет оборачивать целевую функцию в кэширующий слой. Если быть точным, то это будет выглядеть так: MonthWrapper ( CacheWrapper (Fallback) ) ). Т.е. для внешней обертки в качестве fallback передаётся не сама функция а вторая её обертка.
SomeController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * экшен контроллера */ public function someAction() { // Готовим замыкания для обертывания $rep = $this->_rep; // После php 5.4 можно будет сразу this передавать $fallbackForMonthWrapper = function($year, $month) use ($rep) { return $rep->usersCountByMonth($year, $month); }; $fallbackForCacheWrapper = function() use ($rep, $fallbackForMonthWrapper) { return $rep->monthWrapper($fallbackForMonthWrapper); }; // Вызываем первую (внешнюю) обертку $cacheKey = __CLASS__.'::'.__FUNCTION__; $this->view->statData = Service::cacheWrapper($fallbackForCacheWrapper, $cacheKey, 7200); } |
Это обертка по месяцам (внешняя).
RepositoryFunction.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * Обертка для выдачи данных по месяцам * на основе замыкания по одному месяцу * @param Closure $fallback */ public function monthWrapper(\Closure $fallback) { // Обработка $months = $this->_em->getRepository('Model\SomeModel')->getInterval(); $result = array(); foreach($months as $elt) { $tmp = explode("-", $elt); $year = $tmp[0]; $month = $tmp[1]; $result[$year][$month] = $fallback($year, $month); } return $result; } |
А это вторая обертка (внутренняя) для кэширования.
Service.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/** * Обертка для кэширования $fallback'а * @static * @param Closure $fallback * @param $cacheKey * @param $ttl */ public static function cacheWrapper(\Closure $fallback, $cacheKey, $ttl = 7200) { // Кэширование $cache = \Zend_Registry::get('cacheManager')->getCache('ttl_'.$ttl); if($result = $cache->load($cacheKey)) { return $result; } $result = $fallback(); $cache->save($result, $cacheKey); return $result; } |
Теперь самое интересное, что обертки можно использовать и отдельно. Например только кэшировать.
Ссылки
http://habrahabr.ru/post/145317/
http://habrahabr.ru/company/mailru/blog/103983/
А для чего передаются $rep, $fallbackForMonthWrapper по ссылкам (&) в конструкции use?
Они используеются внутри замыкания: return $rep->monthWrapper($fallbackForMonthWrapper);
Верно, но я к тому, что объекты всегда передаются по ссылки
Хм, а можете ссылку кинуть? А то натыкаюсь на противоречивые сведения (1,2).
Замете, что в обоих примерах переменные не объекты – а скалярные типы.
В первой ссылке число $my_var = 0, а во второй строка $function = uniqid().
Т.е. если в конструкции use используются не объекты и они могут меняться внутри тела замыкания, то их обязательно передавать по ссылке.
Пруф линк – первое предложение: http://php.net/manual/en/language.oop5.references.php
P.S. s/Замете/Заметьте/
Ок, спасибо за наводку. Ну собственно тогда амперсанды в данном случае вовсе не нужны.