How to use Caching in Symfony to speed up your application?

We all need to call external API’s sometimes to fetch some data or do some heavy calculations. Sometimes those external API’s can be very slow which degrades the user experience of our application. To combat this, we can use a caching strategy to make sure we only call the API’s when we really need to. In this tutorial I’ll explain to you how to leverage caching in Symfony to speed up your application.

What is caching?

You guys probably already know what caching is, but for completeness sake I’ll add this paragraph for the people that don’t know already. Caching is a method of (temporarily) storing data in a fast accessible location. This location can be the servers RAM or the filesystem.

When your application wants to fetch some data it first checks the cache. If the data is found there, it’s used. When the data is not in the cache, it is fetched from, for example an external API, and then stored in the cache and used. The next request for the same data then uses the faster cache instead of calling the ‘slow’ external API.

The Symfony framework by itself already uses cache. You’re probably familiar with the bin/console cache:clear command or the var/cache folder. This is because Symfony is a smart framework which caches alot out of the box to ensure your application runs smoothly.

Setup caching in Symfony 5

To setup your own application caching in Symfony you first need to understand a few concepts:

Pools: A pool is a cache service your application will interact with. Each pool is an indepent cache, meaning you can have multiple pools with different settings, but with the same keys for your cache items. This is possible because of namespacing.

Adapters: A cache pool uses one adapter. An adapter is basically the interface to the storage method. For example the filesystem adapter stores the cache on, you guessed it, the file system. Using a special adapter, the so called ChainAdapter, you can add multiple adapters to one cache pool.

Now we got that out of the way, lets setup our basic caching example. In our example we’ll store our cache on the file system.

framework:
    cache:
        # Set the directory we want our cache to be stored in
        directory: '%kernel.project_dir%/cache'

        # Next we create our cache pool and set it to store on the file system
        pools:
            example.cache:
                adapter: cache.adapter.filesystem
                tags: true  # Make sure our pool supports taggingCode language: PHP (php)

In this configuration we have configured a cache pool named example.cache which we can use throughout our application using dependency injection. This pool stores it’s cache in the projectdir/cache folder and supports tagging of cache items.

How to use caching in Symfony?

Now that our caching pool is configured we can use its power. Let’s create a simple service that calls an external API to fetch some data and return it.

<?php

namespace App\Services;

use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

class ExampleService
{
    private TagAwareCacheInterface $exampleCache;

    public function __construct(TagAwareCacheInterface $exampleCache)
    {
        $this->exampleCache = $exampleCache;
    }

    public function getDataFromSlowApi(): array
    {
        return $this->exampleCache->get('cache_key', function (ItemInterface $item) {

            // ...Execute your API Request or caculation here

            $result = 'Data from a very slow API';

            // This cache is valid for 1800 seconds, or 30 minutes
            $item->expiresAfter(1800);

            $item->tag(['blogitems', 'user-1']);

            // Return your result
            return $result;
        });
    }
}Code language: HTML, XML (xml)

Now that we’ve created our ExampleService class lets see how it works. Lets start with the constructor. We inject our cache pool using TagAwareCacheInterface $exampleCache. We are using this name for our parameter because the name of the pool is example.cache. The dependency injection container automatically injects this pool when we name our variable $exampleCache, the camelcased version of example.cache. Very nice!

Next we use this cache pool to make our API call faster. To do this, our cache pool service has a method called get which requires two parameters. A cache key and a callback function. First this method checks that our cache key exists in the cache and it is not expired. If that’s the case, it just returns the value in the cache. When the cache key does not exist in the cache or it is expired, the callback function is called. In this function we can configure the cache item, like the lifetime and we can call the API or do our calculation in here. When we return the result from this function, the result is stored in the cache and returned to our getDataFromSlowApi method.

Ofcourse you can make the cache key what ever you want. You can even make it dynamic based on the id of the logged in user for example. Using this method you can have multiple users have different values in the cache for the same API call. That’s up to you though.

This is basically how to use caching in Symfony, as you can see it’s very easy and straight forward to setup.

BONUS: Tagging our cache items

If you looked closely at the service example above you might have spotted this line of code $item->tag(['blogitems', 'user-1']);. You might be wondering what does it do and why do I need it? Well let me explain the purpose of tagging.

Tagging is very useful when you have a lot of users in your application for whom you need to cache different types of information. In our example the slow API returned blogitems, so we added a tag blogitems to the cache item. The user that was requesting the list was the user with id 1, so we added a tag user-1.

The power of tagging is that when we want to remove all blogitems from our cache, because we switched API or something. We can simply remove all cached items which are tagged with blogitem. Or when user 1 deletes his or her account we can simply remove all cache items which are tagged with user-1. Let me show you how to do that with an example.

<?php

namespace App\Services;

use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

class ExampleService
{
    // ... Code from the previous example here ...

    public function removeUserCache(string $userId): void
    {
        $this->exampleCache->invalidateTags(['user-'.$userId]);
    }

    public function removeBlogItemCache(): void
    {
        $this->exampleCache->invalidateTags(['blogitems']);
    }
}Code language: HTML, XML (xml)

As you can see it’s really easy to manage your cache using tags. Simply call the invalidateTags method of your cache pool with the tag(s) you want to invalidate. If you just want to invalidate the blog items from user 1 simple combine the tags into one invalidateTags call.

What do you think of the caching mechanism in Symfony? Let me know in the comments!