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.

async-cache

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 remove($key):

<?php

namespace React\Cache;

interface CacheInterface
{
    // @return React\Promise\PromiseInterface
    public function get($key);

    public function set($key, $value);

    public function remove($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 in done() 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 return 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.

Remove

To remove something from cache simply call remove($key) method and specify a key to be removed.

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 remove($key)
    {
        unset($this->data[$key]);
    }
}

But there are several more implementations:


You can find examples from this article on GitHub.

This article is a part of the ReactPHP Series.

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: 9.99$
Event-driven PHP with ReactPHP