У меня есть набор классов, которые имеют привычку многократно вызываться с теми же аргументами. Эти методы обычно запускают запросы к базе данных и строят массивы объектов и т. Д., И поэтому, чтобы вырезать это дублирование, я разработал несколько методов кэширования для оптимизации. Они используются так:
Перед применением кеширования:
public function method($arg1, $arg2) { $result = doWork(); return $result; }
После применения кэширования:
public function method($arg1, $arg2, $useCached=true) { if ($useCached) {return $this->tryCache();} $result = doWork(); return $this->cache($result); }
К сожалению, теперь я оставил немного кропотливую задачу вручную добавить это ко всем методам – я считаю, что это пример использования шаблона декоратора, но я не могу понять, как его проще реализовать в PHP для Это дело.
Каков наилучший способ сделать это, мы надеемся, что либо все методы в любом из этих классов автоматически сделают это, либо мне просто нужно добавить одну строку в метод и т. Д.?
Я посмотрел, как переопределить оператор return и т. Д., Но ничего не вижу.
Благодаря!
Если вам не нужна Type Safety , вы можете использовать общий кэш-декоратор:
class Cached { public function __construct($instance, $cacheDir = null) { $this->instance = $instance; $this->cacheDir = $cacheDir === null ? sys_get_temp_dir() : $cacheDir; } public function defineCachingForMethod($method, $timeToLive) { $this->methods[$method] = $timeToLive; } public function __call($method, $args) { if ($this->hasActiveCacheForMethod($method, $args)) { return $this->getCachedMethodCall($method, $args); } else { return $this->cacheAndReturnMethodCall($method, $args); } } // … followed by private methods implementing the caching
Затем вы оберните экземпляр, который нужно кэшировать в этот Decorator следующим образом:
$cachedInstance = new Cached(new Instance); $cachedInstance->defineCachingForMethod('foo', 3600);
Очевидно, что $cachedInstance
не имеет метода foo()
. Трюк здесь заключается в использовании метода магии __call
для перехвата всех вызовов недоступным или несуществующим методам и делегирования их декорированному экземпляру. Таким образом, мы обнародуем весь открытый API украшенного экземпляра через Decorator.
Как вы можете видеть, метод __call
также содержит код для проверки наличия кеширования, определенного для этого метода. Если это так, он вернет вызов метода кэширования. В противном случае он вызовет экземпляр и кеширует возврат.
Кроме того, вы передаете выделенную CacheBackend для Decorator вместо реализации кэширования в самом декораторе. Декоратор тогда будет работать только как посредник между украшенным экземпляром и бэкэндом.
Недостатком этого общего подхода является то, что у вашего Cache Decorator не будет типа Decorated Instance. Когда ваш потребительский код ожидает экземпляры типа Instance, вы получите ошибки.
Если вам нужны декораторы с типом , вам необходимо использовать «классический» подход:
В двух словах
class CachedInstance implements InstanceInterface { public function __construct($instance, $cachingBackend) { // assign to properties } public function foo() { // check cachingBackend whether we need to delegate call to $instance } }
Недостатком является то, что это больше работает. Вам нужно сделать это для каждого класса, который должен использовать кеширование. Вам также необходимо поместить проверку в бэкэнд кэша в каждую функцию (дублирование кода), а также делегировать любые вызовы, которые не требуют кэширования для украшенного экземпляра (утомительный и подверженный ошибкам).
Используйте метод __call
magic.
class Cachable { private $Cache = array(); public function Method1(){ return gmstrftime('%Y-%m-%d %H:%M:%S GMT'); } public function __call($Method, array $Arguments){ // Only 'Cached' or '_Cached' trailing methods are accepted if(!preg_match('~^(.+)_?Cached?$~i', $Method, $Matches)){ trigger_error('Illegal Cached method.', E_USER_WARNING); return null; } // The non 'Cached' or '_Cached' trailing method must exist $NotCachedMethod = $Matches[1]; if(!method_exists($this, $NotCachedMethod)){ trigger_error('Cached method not found.', E_USER_WARNING); return null; } // Rebuild if cache does not exist or is too old (5+ minutes) $ArgumentsHash = md5(serialize($Arguments)); // Each Arguments product different output if( !isset($this->Cache[$NotCachedMethod]) or !isset($this->Cache[$NotCachedMethod][$ArgumentsHash]) or ((time() - $this->Cache[$NotCachedMethod][$ArgumentsHash]['Updated']) > (5 * 60)) ){ // Rebuild the Cached Result $NotCachedResult = call_user_func_array(array($this, $NotCachedMethod), $Arguments); // Store the Cache again $this->Cache[$NotCachedMethod][$ArgumentsHash] = array( 'Method' => $NotCachedMethod, 'Result' => $NotCachedResult, 'Updated' => time(), ); } // Deliver the Cached result return $this->Cache[$NotCachedMethod][$ArgumentsHash]['Result']; } } $Cache = new Cachable(); var_dump($Cache->Method1()); var_dump($Cache->Method1Cached()); // or $Cache->Method1_Cached() sleep(5); var_dump($Cache->Method1()); var_dump($Cache->Method1Cached()); // or $Cache->Method1_Cached()
Это используется с использованием внутреннего хранилища, но вы можете использовать БД для этого и создать свое собственное хранилище переходных процессов. Просто добавьте _Cached
или _Cached
в любой существующий метод. Очевидно, вы можете изменить срок службы и многое другое.
Это просто доказательство концепции. Там есть много улучшений 🙂
Вот выдержка из статьи, посвященной кешированию в php
/** * Caching aspect */ class CachingAspect implements Aspect { private $cache = null; public function __construct(Memcache $cache) { $this->cache = $cache; } /** * This advice intercepts the execution of cacheable methods * * The logic is pretty simple: we look for the value in the cache and if we have a cache miss * we then invoke original method and store its result in the cache. * * @param MethodInvocation $invocation Invocation * * @Around("@annotation(Annotation\Cacheable)") */ public function aroundCacheable(MethodInvocation $invocation) { $obj = $invocation->getThis(); $class = is_object($obj) ? get_class($obj) : $obj; $key = $class . ':' . $invocation->getMethod()->name; $result = $this->cache->get($key); if ($result === false) { $result = $invocation->proceed(); $this->cache->set($key, $result); } return $result; } }
Это имеет для меня больше смысла, поскольку он реализуется с помощью SOLID-реализации. Я не большой поклонник реализации того же самого с аннотациями, предпочел бы что-то более простое.