Best way to implement a decorator pattern for method result caching in PHP
If you don't need Type Safety, you can use a generic Cache Decorator:
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
You would then wrap an instance that needs caching into this Decorator like this:
$cachedInstance = new Cached(new Instance);
$cachedInstance->defineCachingForMethod('foo', 3600);
Obviously, the $cachedInstance
does not have a foo()
method. The trick here is to utilize the magic __call
method to intercept all calls to inaccessible or non-existing methods and delegate them to the decorated instance. This way we are exposing the entire public API of the decorated instance through the Decorator.
As you can see, the __call
method also contains the code to check whether there is a caching defined for that method. If so, it will return the cached method call. If not, it will call the instance and cache the return.
Alternatively, you pass in a dedicated CacheBackend to the Decorator instead of implementing the Caching in the decorator itself. The Decorator would then only work as a Mediator between the decorated instance and the backend.
The drawback of this generic approach is that your Cache Decorator will not have the type of the Decorated Instance. When your consuming code expects instances of type Instance, you will get errors.
If you need type-safe decorators, you need to use the "classic" approach:
- Create an Interface of the decorated instance public API. You can do that manually or, if it's a lot of work, use my Interface Distiller)
- Change the TypeHints on every method expecting the decorated instance to the Interface
- Have the Decorated instance implement it.
- Have the Decorator implement it and delegate any methods to the decorated instance
- Modify all methods that need caching
- Repeat for all classes that want to use the decorator
In a nutshell
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
}
}
The drawback is, that it is more work. You need to do that for every class supposed to use caching. You'll also need to put the check to the cache backend into every function (code duplication) as well as delegating any calls that don't need caching to the decorated instance (tedious and error prone).
is this a decorator pattern (PHP)?
The example you posted is not a decorator, it simply provides a new interface (lower
and uppercase
) on top of a string. A decorator adds behaviour without changing the underlying interface.
A typical example of decorators is literally decorating graphical elements. For example:
interface Shape {
draw();
}
class Rectangle implements Shape { ... }
class Circle implements Shape { ... }
class BorderDecorator implements Shape {
Shape shape;
draw() {
shape.draw();
drawBorder();
}
}
All of the classes above have the same interface, so if a function needs a Shape
, you can pass in a plain old Rectangle
or you can wrap it in a BorderDecorator
to get a rectangle with a border.
PHP equivalent for a python decorator?
Apparently runkit might help you.
Also, you can always do this the OO way. Put the original fun in a class, and the decorator into an extended class. Instantiate and go.
Related Topics
Understanding PHP Curl_Multi_Exec
Making a Http Get Request with Http-Basic Authentication
How to Use C++ Binaries from PHP
Detect In-App Browser (Webview) with PHP/Javascript
Select Last 20 Order by Ascending - PHP/Mysql
Utf8_(En|De)Code Removed from PHP7
Code to Parse User Agent String
PHP Get Previous Array Element Knowing Current Array Key
Difference Between File, File_Get_Contents, and Fopen in PHP
How to Execute My SQL Query in Codeigniter
Using If(!Empty) with Multiple Variables Not in an Array
Why Does PHP Not Complain When I Treat a Null Value as an Array Like This
Pdo Exception Questions - How to Catch Them