Symfony 5 switch cache adapter on condition

1.1k Views Asked by At

I need to switch the Symfony cache adapter depending on ENV conditions. Like if some variable is set, use "cache.adapter.apcu" or use "cache.adapter.filesystem" otherwise.

Is it possible somehow? The documentation is not really helpful with it.

P.S.: It is not possible for us to do this via the creation of a whole new environment

3

There are 3 best solutions below

3
Michael Sivolobov On

It seems like you can not set up your cache configuration to use a environment variable like so:

framework:
    cache:
        app: %env(resolve:CACHE_ADAPTER)%

It is the constraint of FrameworkBundle that provides the cache service. And this constraint will not be "fixed" (Using environment variables at compile time #25173).

To make it possible you need to make your own cache provider that can just pass all arguments to the needed cache provider. You will have access to environment variables at runtime and so you can use it as a proxy that knows what provider to use.

5
dbrumann On

Here is a basic example for a CacheAdapter which has adapters fed into it and then picking one based on a parameter (or alternatively envvar):

<?php

namespace App\Cache;

use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException;
use Psr\Container\ContainerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Service\ServiceSubscriberTrait;

class EnvironmentAwareCacheAdapter implements AdapterInterface, ServiceSubscriberInterface
{
    use ServiceSubscriberTrait;

    private string $environment;

    public function __construct(string $environment)
    {
        $this->environment = $environment;
    }

    public function getItem($key)
    {
        return $this->container->get($this->environment)->getItem($key);
    }

    public function getItems(array $keys = [])
    {
        return $this->container->get($this->environment)->getItems($key);
    }

    // ...
}

This is how you would configure it:


services:
   App\Cache\EnvironmentAwareCacheAdapter:
       arguments:
           $environment: '%kernel.environment%'
       tags:
           - { name: 'container.service_subscriber', key: 'dev', id: 'cache.app' }
           - { name: 'container.service_subscriber', key: 'prod', id: 'cache.system' }

It's not the most elegant solution and is missing error handling and possibly a fallback. Basically, by adding tags with an appropriately named key and the alias to an existing cache as id, you can then refer to that cache with the key in your own adapter. So, depending on your environment you will pick either one. You can replace the key and the constructor argument with anything else you like. I hope that helps.

0
Alex83690 On

You can create a CompilerPass:

class CachePass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $adapter = $container->resolveEnvPlaceholders(
            $container->getParameter('app.cache.app.adapter'),
            true
        );
        /** @var ChildDefinition $cacheApp */
        $cacheApp = $container->getDefinition('cache.app');
        $cacheApp->setParent($adapter);
    }
}

Kernel.php

    $container->addCompilerPass(new CachePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 33);
    // Priority 33 is important. It is before symfony's CachePoolPass

cache.yaml

framework:
  cache:
    default_redis_provider: 'redis://%app.cache.redis_adapter.host%'

services.yaml

parameters:
    env(CACHE_APP_ADAPTER): 'filesystem'
    app.cache.app.adapter: 'cache.adapter.%env(CACHE_APP_ADAPTER)%'
    app.cache.redis_adapter.host: '%env(REDIS_HOST)%'

.env

# filesystem or redis
CACHE_APP_ADAPTER=redis
REDIS_HOST=localhost:5200