Caching is an important concept in web development that helps to improve the performance of web applications. Symfony 5+ provides various caching mechanisms that you can use to improve the performance of your application, mostly for requests that will require lot of processing. This helps in reducing response times ,but a the same time in reducing system resources usage, allowing the System to be able to serve many more client requests. A good caching strategy is crucial for improving Application performance and reducing costs in infrastructure.

Prerequisites:

 

 Step 1. Install Symfony Cache component.

composer require symfony/cache

Step 2. Configure the Cache component.

# config/packages/cache.yaml
when@dev:
    framework:
        cache:
            pools:
                client_portal_pool_dev:
                    default_lifetime: 3600 #10 min in Dev
                    #adapter: cache.adapter.filesystem
                    adapter: cache.adapter.redis
                    provider: 'redis://redisKey@ip:6380'

when@prod:
    framework:
        cache:
            pools:
                client_portal_pool_prod:
                    default_lifetime: 10800 #3 Hr in Prod
                    #adapter: cache.adapter.filesystem
                    adapter: cache.adapter.redis
                    provider: 'redis://redisKey@ip:6380'

In this example I'm using Redis for caching. There are many other providers that you can look here: Symfony Cache Component

Also, notice how I'm using 2 different Redis pools for each environment. This is very important to keep in mind if you are using a cache provider like Redis, where you need to keep a clear separation of caching pools and not mess with production data.

 Step 3. Create a CacheService to handle the logic for get / set / delete from cache.

<?php

namespace App\Services;

use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\Cache\CacheInterface;

class CoolCacheService
{
    /**
     * @var CacheInterface
     */
    private CacheInterface $cachePool;
    
    /**
     * @param CacheInterface $cachePool
     */
    public function __construct(CacheInterface $cachePool)
    {
        $this->cachePool = $cachePool;
    }
    
    /**
     * @return CacheInterface
     */
    public function getCachePool(): CacheInterface
    {
        return $this->cachePool;
    }
    
    /**
     * @param CacheInterface $cachePool
     * @param string $key
     * @return CacheItem
     */
    public function getCachedItem(CacheInterface $cachePool, string $key): CacheItem
    {
        return $cachePool->getItem($key);
    }
    
    /**
     * @param CacheInterface $cachePool
     * @param CacheItem $item
     * @param array $data
     * @return bool
     */
    public function setCachedItemData(CacheInterface $cachePool, CacheItem $item, array $data): bool
    {
        $item->set(json_encode(['data' => $data, 'last_updated' => time()]));
        return $cachePool->save($item);
    }
    
    /**
     * @param Request $request
     * @return string
     * @throws InvalidArgumentException
     */
    public function deleteItemFromCachePool(Request $request): string
    {
        $keyName = hash( 'sha256', $request->query->get('key'));
        $item = $this->getCachedItem($this->getCachePool(), $keyName);
        if ($item->isHit()) {
            $this->getCachePool()->delete($keyName);
            $html = '<pre>' . $keyName . '</pre>  has been deleted';
        } else {
            $html = '<pre>' . $keyName . '</pre>  not found in cache pool';
        }
        
        return $html;
    }
    
    /**
     * @param Request $request
     * @return string
     */
    public function getHashedKeyName(Request $request): string
    {
        return hash( 'sha256', $request->getPathInfo());
    }
    
}

 Step 4. Now from every where you need to process heavy stuff do this:

<?php

namespace App\Services;

use http\Exception\RuntimeException;
use Symfony\Component\HttpFoundation\Request;

class DefaultService
{
    
    /**
     * @var CoolCacheService
     */
    private CoolCacheService $cacheService;
    
    /**
     * @param CoolCacheService $cacheService
     */
    public function __construct(CoolCacheService $cacheService)
    {
        $this->cacheService = $cacheService;
    }
    
    /**
     * @param Request $request
     * @return array
     */
    public function getHeavyStuff(Request $request): array
    {
        try {
            $keyName = $this->cacheService->getHashedKeyName($request);
            $item = $this->cacheService->getCachedItem($this->cacheService->getCachePool(), $keyName);
            if (!$item->isHit()) {
                $data = $this->processHeavyStuff($args);
                $ok = $this->cacheService->setCachedItemData($this->cacheService->getCachePool(), $item, $data);
            }
            $item = json_decode($this->clientPortalCacheService->getCachedItem($this->cacheService->getCachePool(), $keyName)->get(), true);
        } catch (Throwable $e) {
            throw new RuntimeException($e->getMessage());
        }
        
        return $item['data'] ?? [];
    }
    
    private function processHeavyStuff($args)
    {
        //...execute all heavy calculations here
        return $data;
    }
}

 In this case, I'm using the request path to generate the cache key name. It is a good idea to hash these key names for security reasons and returning the cached data as an associative array.

Sometimes is very useful to implement a cron command to process all these heavy calls and have it always cached and ready for the users.