Двойное обертывание в PHP

// Июнь 7th, 2012 // PHP, Веб-разработка

В этой статье я расскажу о том, как делать двойное (ну или n-ое) обертывание некой функции в php 5.3 Вообще это может понадобиться для очень многих вещей: кэширование, логирование и т.д.

Задача.

Мы находимся в контроллере, в котором происходит итерация некой функции по месяцам. При этом в каждом экшене контроллера функция — разная. В тоже самое время мы хотим внедрить кэширование данных, которые выдают такие функции.

Представим себе, что мы находимся в контроллере, и там присутствует много вот такого кода:

/**
* Информация собираемая с помощью 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);
}
}

Отчётливо видим повторение кода.
Теперь второй случай, который наверняка многим знаком.

/**
*Получение данных
*/
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

/**
* экшен контроллера
*/
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

/**
* Обертка для выдачи данных по месяцам
* на основе замыкания по одному месяцу
* @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

/**
* Обертка для кэширования $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/

Share

Спасибо!


Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:


8 Responses to “Двойное обертывание в PHP”

  1. Ilya:

    А для чего передаются $rep, $fallbackForMonthWrapper по ссылкам (&) в конструкции use?

    • google.com Андрей Токарчук:

      Они используеются внутри замыкания: return $rep->monthWrapper($fallbackForMonthWrapper);

      • Ilya:

        Верно, но я к тому, что объекты всегда передаются по ссылки

        • google.com Андрей Токарчук:

          Хм, а можете ссылку кинуть? А то натыкаюсь на противоречивые сведения (1,2).

          • Ilya:

            Замете, что в обоих примерах переменные не объекты — а скалярные типы.

            В первой ссылке число $my_var = 0, а во второй строка $function = uniqid().

            Т.е. если в конструкции use используются не объекты и они могут меняться внутри тела замыкания, то их обязательно передавать по ссылке.

          • Ilya:

            Пруф линк — первое предложение: http://php.net/manual/en/language.oop5.references.php

  2. Ilya:

    P.S. s/Замете/Заметьте/

    • google.com Андрей Токарчук:

      Ок, спасибо за наводку. Ну собственно тогда амперсанды в данном случае вовсе не нужны.

Комментировать