Symfony 4: caching in memory and with Redis

Introduction
I've been writing a lot of PHP over the last few months, and have been getting a decent amount of experience with the Symfony framework. In my Symfony 4 project, I wanted to swap out the default memory caching for a Redis database, and thought that it might be helpful to others if I shared my solution.
I've created a Github repo for anyone that wants a quick working example, but remember that you'll still need to configure a Redis instance if you want to go beyond in-memory caching.
This is only a very simple example, but it should illustrate the basic steps that I took to get caching up and running in Symfony; both in memory and with Redis. I'm only just starting out with PHP, so do your own research too.
Goals
By the end of this article, you should be able to:
- set up a skeleton Symfony 4 project
- use Symfony's default in-memory caching
- configure Symfony to use Redis caching
Prerequisites
- OSX Mojave
- A local installation of the Composer dependency manager
- A running instance of Redis available either locally or at a remote location
- Basic knowledge of PHP and the Symfony framework
Create a Symfony 4 skeleton project
If you're unsure about this process then please read my article that explains how to create a new Symfony project from scratch.
Create a new Controller
We'll need a controller to deal with requests to our new Symfony application.
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Routing\Annotation\Route;
use DateInterval;
class HomeController extends AbstractController
{
public function __construct(AdapterInterface $cache)
{
$this->cache = $cache;
}
/**
* @Route("/", name="home")
*/
public function index()
{
$cacheKey = 'thisIsACacheKey';
$item = $this->cache->getItem($cacheKey);
$itemCameFromCache = true;
if (!$item->isHit()) {
$itemCameFromCache = false;
$item->set('this is some data to cache');
$item->expiresAfter(new DateInterval('PT10S')); // the item will be cached for 10 seconds
$this->cache->save($item);
}
return $this->render('home/index.html.twig', ['isCached' => $itemCameFromCache ? 'true' : 'false']);
}
}
Let's have a look at what's going on in that code.
The first line of interest involves the AdapterInterface:
use Symfony\Component\Cache\Adapter\AdapterInterface;
A quick look at the Symfony 4 documentation confirms that the Adapter Interface is used as a blueprint for "adapters managing instances of Symfony's CacheItem.", and that Cache Items "are the information units stored in the cache as a key/value pair".
The super useful auto-wiring functionality available in Symfony 4 allows us to inject AdapterInterface as a dependency, simple by requiring it as a constructor parameter:
public function __construct(AdapterInterface $cache)
{
$this->cache = $cache;
}
We then create a cache key, and check whether there is any data stored against it in the cache:
$cacheKey = 'thisIsACacheKey';
$item = $this->cache->getItem($cacheKey);
If the item doesn't already exist in the cache.......
if (!$item->isHit()) {
.......we add some data to the cache, set an expiry time for the item and then save the item to the cache:
$item->set('this is some data to cache');
$item->expiresAfter(new DateInterval('PT10S')); // the item will be cached for 10 seconds
$this->cache->save($item);
Depending on how we set the $itemCameFromCache variable, we then send a true or false string to a Twig template:
return $this->render('home/index.html.twig', ['isCached' => $itemCameFromCache ? 'true' : 'false']);
Create a Twig template
The Twig template that we're referencing in the code above doesn't even exist yet, so let's create it.
{% extends 'base.html.twig' %}
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>
<div class="example-wrapper">
<h1>Caching with Symfony 4</h1>
<p>The cache was hit: {{ isCached }}</p>
</div>
{% endblock %}
Testing the in-memory caching


You should see that the item we chose to cache is saved for 10 seconds before being cleared.
If you wish to verify this further, click on the cache icon in Symfony's "sticky bar" that appears at the bottom of the page, and take a look at how the cache is being used.
Look in the Calls section, and you should see the entry for the thisIsACacheKey key that we stored.

Using Redis caching instead
Before we can use Redis caching, we need to install a Redis client for PHP.
The one I'm going to use here is called Predis.
composer require predis/predis
You'll also need to update the cache section of your config/packages/cache.yaml file as follows:
cache:
# Put the unique name of your app here: the prefix seed
# is used to compute stable namespaces for cache keys.
prefix_seed: hackerbox_io/symfony_4_redis_cache
# The app cache caches to the filesystem by default.
# Other options include:
# Redis
app: cache.adapter.redis
default_redis_provider: redis://redis:6379
Let's take a closer look at that.
As the comment indicates, the prefix_seed value is used to create namespaces for cache keys. Use a name that you feel is appropriate here.
prefix_seed: hackerbox_io/symfony_4_redis_cache
The app value tells Symfony which adapter to use:
app: cache.adapter.redis
And default_redis_provider is the reference to your Redis instance:
default_redis_provider: redis://redis:6379
Testing the Redis caching
The first part of this is pretty much identical to the steps we took to test the in-memory caching, but I'll reiterate the process to make for easier reading.


You should see that the item we chose to cache is saved for 10 seconds before being cleared.
If you wish to verify this further, click on the cache icon in Symfony's "sticky bar" that appears at the bottom of the page, and take a look at how the cache is being used.

Look in the Calls section, and you should see the entry for the thisIsACacheKey key that we stored.
We can further confirm that Redis has stored our cached value by searching for the thisIsACacheKey key in the database itself.
You'll need to connect to your Redis instance and then search for the key, remembering of course that it will only exist for 10 seconds if you've following this tutorial exactly.
The exact process for connection will depend on how you have Redis set up, but here are the steps that I use when connecting to my local instance (which runs as a Docker container) using the redis-cli tool:
redis-cli -p 6379
keys *
You should see something similar to:
1) "ATVjoFtyo0:thisIsACacheKey"
Summary
So that's how I managed to get in-memory and Redis caching working for Symfony 4. I'm only quite new to PHP and the Symfony framework, but whilst there are probably better ways to achieve the same goal, there'll hopefully be enough in this tutorial to get you off the starting blocks.
Be sure to do your own research before you use any of this stuff in production, and please share my article around if you find it useful.
If you liked that article, then why not try:
Setting up Postgres inside a Docker container
I've been doing a bit of work with Postgres recently, and encountered a few gotchas whilst getting things set up inside a Docker container for the first time. I thought I'd share my final process here, just in case it comes in handy for the equally uninitiated.