The most important new feature in Symfony 3.1 is the PSR-6 compatible Cache component.

Being that the Cache component conforms to a PSR, we are hopefully moving towards a time whereby you only need to learn one interface for using the cache, and this knowledge will be transferable to any framework. Hopefully :)

In this video we take a very quick look at how we can get started using the new Cache component. You will likely be very happy to hear it only takes two lines of config - yes, just 2.

Of course, you can dive a little deeper into the configuration and start creating your own cache pools, specifying various defaults such as cached item expiration timers, and extra configuration around the caching adapter you wish to use. But you don't need too.

Let's quickly check up on the config needed to get started:

# /app/config/config.yml
framework:
    # your other config here
    cache:
        app: cache.adapter.filesystem

Yup, that's all that we need to get our cache up and running.

Reading and writing to the file system is a significantly more costly operation than reading / writing to memory, so consider a different adapter for production.

Now, you may wish to use a more advanced cache backend such as Redis, or Doctrine Cache, but the easiest way to get started testing out the Cache component is to simply use the local disk (filesystem) as our storage mechanism.

Sir, They Have Adapted

The concept / naming of adapter may be confusing here. A different way to think about this is like the plugs you take on your holiday. Here in the UK we have a three pronged plug for all our electrical gadgets. But in Europe, plug sockets tend to only have two prongs. We still need to use our gadgets, so we buy a travel adapter plug, which converts our three pronged plugs into a two pronged plug.

Similarly, from our high level perspective, we want to use Redis in the same way we use the File System. We don't much care how that happens underneath, we just plug in the correct adapter and away we go.

That said, if you really do care about the specific implementation, you can always read the source.

A Pool Of Items

The other key terms are Item and Pool.

Simply put, a cache Pool will contain all our cached Items.

Each item consists of a unique key, and a value. A key / value pair, in other words. The key cannot change, but the value can change as needed.

An important point to understand is that items in the cache must implement CacheItemInterface. You don't really need to worry about this, as you won't directly need to instantiate anything implementing this interface, nor create your own implementation. Instead, any time you ask the cache for an item for a given key, you are returned a object implementing this interface - quite unintuitive, but more on this below.

Example

Let's look at the code from the video, and then cover each part in more detail:

public function cacheExampleAction()
{
    /** @var $cache CacheItemPoolInterface */
    $cache = $this->get('cache.app');

    $cachedData = $cache->getItem('random_number');

    if ($cachedData->isHit()) {
        return new JsonResponse([
            'data' => $cachedData->get(),
            'hit'  => $cachedData->isHit(),
        ]);
    }

    $number = rand(1,100);

    $cachedData->set($number);
    $cachedData->expiresAfter(10);

    $cache->save($cachedData);

    return new JsonResponse([
        'data' => $cachedData->get(),
        'hit'  => $cachedData->isHit(),
    ]);
}

It's a contrived example, of course, but it illustrates a few interesting things.

Firstly, we need to access the cache in some way. We can grab the cache.app that we configured inside config.yml at the beginning of this write up:

/** @var $cache CacheItemPoolInterface */
$cache = $this->get('cache.app');

I've added the type hint to enable PHPStorm to provide autocomplete, but it isn't required.

Next, and crucially:

$cachedData = $cache->getItem('random_number');

Imagine we have never called the cacheExampleAction before.

We don't have any data in the cache, so why on Earth are we asking the cache (getItem) for the random_item key?

Well, really importantly, this will get us an instance of CacheItem whether the key existed before or not.

CacheItem implements CacheItemInterface, which is why we never need to directly instantiate a CacheItem.

As we've already covered, the only objects that we can save to the cache are those implementing CacheItemInterface. So whether this particular key existed before or not, we now have an object with this key that is compatible with our cache.

Next, we check if this item did exist before, by checking if the cache was hit for this key:

if ($cachedData->isHit()) {

Note here that we check the CacheItem itself, rather than the $cache. I found this peculiar.

If there was some data already stored in the cache for our random_number key then we will go ahead and return a JsonResponse containing the contents of the CacheItem, and a boolean - which should only ever be true in this if statement:

if ($cachedData->isHit()) {
    return new JsonResponse([
        'data' => $cachedData->get(),
        'hit'  => $cachedData->isHit(),
    ]);
}

Ok, that covers the scenario where data did exist in the cache.

But what about the first time we call this controller action? Or what about if the item in the cache has expired?

Well, we wouldn't have triggered the if statement, as isHit would have returned false. So, let's continue:

$number = rand(1,100);

$cachedData->set($number);

We've covered how the CachePool can only work with objects implementing CacheItemInterface.

But what data (the value part of the key/value pair) can a CacheItem store?

The PSR states that all serializable PHP data types are supported, including:

  • strings
  • integers
  • floats
  • booleans
  • null
  • arrays
  • objects

Objects are likely to give you the most grief. Read [this section] of the PSR for more information.

In our case, saving the integer output of our rand(1,100) call is simple enough.

For my purposes, I then set an expiration time of 10 seconds for this particular item:

$cachedData->expiresAfter(10);

The important thing to note here is that if you don't set an expiration time, the items will persist indefinitely. At least, they do for the filesystem, redis, and apcu adapters.

If you need to remove an item from your cache you can do so by either calling:

$cache->deleteItem('your_key_to_delete');
// or
$cache->clear();

Note here that this occurs on the $cache, not on the $cachedData.

Lastly, once we have set a value on our CacheItem, we need to tell the cache to save it:

$cache->save($cachedData);

And just like in the first return statement when the cache was hit, we return the same data structure, only this time always expecting isHit to return false.

return new JsonResponse([
    'data' => $cachedData->get(),
    'hit'  => $cachedData->isHit(),
]);

In this example, the cached data should remain in the cache for 10 seconds. During this time, any calls to the the cache for random_number should return the same number as first generated.

Then, after 10 seconds, the item is removed from the cache, and on the next call to this action, a new number will be generated, saved to the cache, and so the process repeats.


Share This Episode

If you have found this video helpful, please consider sharing. I really appreciate it.