We are developping an application that, for many reasons, is based on a aging code base. It is actively used by customers, and keeps evolving.
We recently came accross performance problems, which we chose to solve by adding caches. Lists, Menus, big Graphs, they are all stored as some sort of cache (separate files on the file system)
For each cache you basically have two processes:
Since the application is monolithic, the possible actions that cause a cache saving or clearing are not obvious and self-explaining. There are many entry points that could trigger such a process (build a new menu? clear the menu cache - and so forth).
So we sometimes have the problem that a very minor part of the app is not clearing the cache, for example. Or another one is not refreshing the cache when it should.
Basically, we lack overview of all cache clearing/saving triggers.
Two questions:
Caching is a generic kind of cross-cutting concerns and it may be solved using the Aspect-oriented programming. In a nutshell this technique allows to add or modify a behavior to existing code without modifying the code itself. All you need is to spot some join points and attach an advice to them.
For the PHP a most powerful AOP implementation provided by Go! AOP:
For example, some function that should be cached:
class AnyClass
{
/**
* @Cacheable
*/
public function someVerySlowMethod() { /* ... */ }
}
and relevant advice implementation:
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;
}
}
That's all. Description from the original post ("Caching Like a PRO"):
This aspect then will be registered in the AOP kernel. AOP engine will analyze each loaded class during autoloading and if a method matches the @Around("@annotation(Annotation\Cacheable)") pointcut then AOP will change it on the fly to include a custom logic of invoking an advice. Class name will be preserved, so AOP can easily cache static methods and even methods in final classes.
Note that annotation (@Cacheable) is optional: you can various ways to find a pointcut.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With