Get Started With Symfony Cache
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 Item
s.
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.