Сидел я как-то вечером и переписывал тонны кода в рамках расширения функционала.
Код старый, кое-где костыли, но куда же без них. Бывает.
А вот на «тяжелых» методах у нас кеширование, реализованное в таком вот виде:
class GarlemShake {
...
public function getTotals($client_id) {
static $cache = null;
$key = md5(serialize(func_get_args()));
if (isset($cache[$key])) {
// вычисляем долго и упорно
// складываем в $result
$cache[$key] = $result;
} else {
$result = $cache[$key];
}
return $result;
}
...
}
Повторяется почти по всему проекту.
Некрасиво? Да.
Долго печатать? Ну можно в сниппеты записать, и на фразу makemycache он будет выдавать заготовленный кусок кода.
Ухудшает код? Читаемость уж точно.
Но, как всем нам известно, лень — двигатель прогресса. Вот и мне пришло в голову упростить немного модель кеширования, максимально используя средства языка. За несколько минут накидал простенький прототип, складывающий результат в массив внутри себя.
class Cacheable {
/** @var object */
protected $_instance = null;
/** @var array */
protected $_cache = array();
/**
* Конструктор-перехватчик
*/
public function __construct() {
$params = func_get_args();
if (!empty($params)) {
$this->_instance = is_object($obj = array_shift($params)) ? $obj : new $obj($params);
}
}
/**
* Магический метод кеширования
*
* @param $method
* @param $params
*
* @return mixed
*/
public function __call($method, $params) {
$instance = isset($this->_instance) ? $this->_instance : $this;
$key = crc32(serialize(array($method, $params)));
if (!isset($this->cache[$key])) {
$result = call_user_func_array(array($instance, $method), $params);
$this->cache[$key] = $result;
} else {
$result = $this->cache[$key];
}
return $result;
}
}
Из языковой магии используются магические методы __call и __construct. Первый для перехвата вызовов (далее будет рассказан финт ушами), второй для передачи объекта в ответственные руки кэширующего механизма.
Теперь пару примеров использования оборачивания вызовов
$cached_db = new Cacheable('db');
// ну или так
$db = new DB;
$cached_db = new Cacheable($db);
В этом случае все вызовы методов $cached_db будут прокешированы, чуть более, чем полностью.
А в чем же финт ушами? В процессе наследования. Предположим, у нас есть класс, некоторые функции которого нам хотелось бы закешировать
class PartialCached extends Cacheable {
public function NotCached() {
// ...
}
public function NotCachedAtAll() {
// ...
}
protected function Cached() {
// ...
}
private function CachedToo() {
// ...
}
}
Посмотрев на код, вы наверняка скажете — а где же инструкции, которые позволят указать, что кешировать, а что нет? Приглядевшись еще раз к коду, можно увидеть разницу — она в том, что при вызове protected методов наш кэширующий механизм включается, а для public кэширование отключено. И реализуется этот финт ушами средствами самого PHP, который любые не-public методы пропускает через __call, который, собственно, был перекрыт у родителя. Такие вот подарки от движка.
Как можно улучшить? Добавить парочку паттернов по вкусу (стратегии хранения кеша/хэширования ключа)
Где использовать? Решайте сами, в моем случае этот оберточный класс улучшил читаемость кода на 146%. Шучу, я еще подумаю над тем, нужно ли это проекту.
Набор для тестов и результаты:
class a {
public function longCalc($a) {
usleep(150000);
return $a * $a;
}
}
class c extends Cacheable {
private function longCalc($a) {
usleep(150000);
return $a * $a;
}
}
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
$b = new Cacheable('a');
$time_start = microtime_float();
/** @var $b a */
$b->longCalc(1);
echo sprintf('%.2f', microtime_float() - $time_start) . ' sec ' . PHP_EOL;
$time_start = microtime_float();
/** @var $b a */
$b->longCalc(2);
echo sprintf('%.2f', microtime_float() - $time_start) . ' sec ' . PHP_EOL;
$time_start = microtime_float();
/** @var $b a */
$b->longCalc(1);
echo sprintf('%.2f', microtime_float() - $time_start) . ' sec ' . PHP_EOL;
$c = new c;
$time_start = microtime_float();
/** @var $c c */
$c->longCalc(1);
echo sprintf('%.2f', microtime_float() - $time_start) . ' sec ' . PHP_EOL;
$time_start = microtime_float();
/** @var $c c */
$c->longCalc(2);
echo sprintf('%.2f', microtime_float() - $time_start) . ' sec ' . PHP_EOL;
$time_start = microtime_float();
/** @var $c c */
$c->longCalc(1);
echo sprintf('%.2f', microtime_float() - $time_start) . ' sec ' . PHP_EOL;
Автор: justhack