Promise-Based Cache With ReactPHP
In the previous article, we have already touched caching (when caching DNS records). It is an asynchronous promise-based Cache Component. The idea behind this component is to provide a promise-based CacheInterface
and instead of waiting for a result to be retrieved from a cache the client code gets a promise. If there is a value in a cache the fulfilled with this value promise is returned. If there is no value by a specified key the rejected promise returns.
Interface
The component has one simple in-memory ArrayCache
implementation of CacheInterface
. The interface is pretty simple and contains three methods: get($key)
, set($key, $value)
and delete($key)
:
<?php
namespace React\Cache;
interface CacheInterface
{
// @return React\Promise\PromiseInterface
public function get($key);
public function set($key, $value);
public function delete($key);
}
Set/Get
Let’s try it to see how it works. At first, we put something in cache:
<?php
$cache = new React\Cache\ArrayCache();
$cache->set('foo', 'bar');
Method set($key, $value)
simply sets the value of the key foo
to bar
. If this key already exists the value will be overridden.
The next step is to get foo
value back from the cache. Before calling get($key)
take a look at the CacheInterface
:
<?php
// @return React\Promise\PromiseInterface
public function get($key);
Notice, that get($key)
method doesn’t return the value from cache, instead, it returns a promise. Which means that we should use promise done()
method to attach onFulfilled handler and actually retrieve the value from cache:
<?php
$cache = new React\Cache\ArrayCache();
$cache->set('foo', 'bar');
$cache->get('foo')
->done(function($value){
var_dump($value); // outputs 'bar'
});
In the previous example actually, there is no need to create a callback simply to call
var_dump
function inside. You can pass a string right indone()
method and everything will work exactly the same:
<?php
$cache = new React\Cache\ArrayCache();
$cache->set('foo', 'bar');
// outputs 'bar'
$cache->get('foo')->done('var_dump');
Fallback
It may occur that there is no value in a cache. To catch this situation we should use promise otherwise()
method and attach an onRejected handler:
<?php
$cache = new React\Cache\ArrayCache();
$cache->set('foo', 'bar');
$cache
->get('baz')
->otherwise(function(){
echo "There is no value in cache";
});
The last two examples can be merged and rewritten with one promise then()
call which accepts both onFulfilled and onRejected handlers:
<?php
$cache = new React\Cache\ArrayCache();
$cache->set('foo', 'bar');
$cache->get('baz')->then(
function($value) {
var_dump($value);
},
function() {
echo "There is no value in cache";
});
With this approach, we can easily provide a fallback value for situations when there is no value in a cache:
<?php
$cache = new React\Cache\ArrayCache();
$cache->set('foo', 'bar');
$data = null;
$cache->get('baz')
->then(
function($value) use (&$data) {
$data = $value;
},
function() use (&$data) {
$data = 'default';
}
);
echo $data; // outputs 'default'
If there is a value in a cache the first callback is triggered and this value will be assigned to $data
variable, otherwise the second callback is triggered and $data
variable gets 'default'
value.
Fallbacks chain
The onRejected handler can itself return a promise. Let’s create a callback with a new promise. For example, this callback tries to fetch some data from a database. On success the promise is fulfilled with this data. If there is no required data in a database we return a some default value. Here is a new fallback callback:
<?php
$getFromDatabase = function() {
$resolver = function(callable $resolve, callable $reject) {
return $resolve('some data from database');
};
return new React\Promise\Promise($resolver);
};
A quick overview. Our promise has a resolver handler. This handler accepts two callbacks: one to fulfill the promise with some value, and another - to reject a promise. In our example we immediately fulfill the promise with a string 'some data from database'
.
The next step is to replace the onRejected handler for a promise which was returned when we call get($key)
method:
<?php
$cache->get('baz')
->then(
function($value) use (&$data) {
$data = $value;
},
$getFromDatabase
);
In the snippet above $getFromDatabase
callback is triggered when there is no value in cache. This callback returns a promise, so we can continue chaining and attach one more then()
method:
<?php
$data = null;
$cache->get('baz')
->then(
function($value) use (&$data) {
$data = $value;
},
$getFromDatabase
)
->then(function($value) use (&$data) {
$data = $value;
});
echo $data;
As you remember our promise is fulfilled with 'some data from database'
string. This value will be passed into the last then
callback. As a result, this string will be printed by echo
.
Delete
To delete something from cache simply call delete($key)
method and specify a key to be deleted.
Conclusion
The Cache Component comes with one simple in-memory ArrayCache
implementation. It simply stores all values in array in memory:
<?php
class ArrayCache implements CacheInterface
{
private $data = array();
public function get($key)
{
if (!isset($this->data[$key])) {
return Promise\reject();
}
return Promise\resolve($this->data[$key]);
}
public function set($key, $value)
{
$this->data[$key] = $value;
}
public function delete($key)
{
unset($this->data[$key]);
}
}
But there are several more implementations:
- WyriHaximus/react-cache-redis uses Redis.
- WyriHaximus/react-cache-filesystem uses filesystem.
- php-react-cache-memcached uses Memcached.
You can find examples from this article on GitHub.
This article is a part of the ReactPHP Series.
Learning Event-Driven PHP With ReactPHP
The book about asynchronous PHP that you NEED!
A complete guide to writing asynchronous applications with ReactPHP. Discover event-driven architecture and non-blocking I/O with PHP!
Minimum price: 5.99$