Replace parameters in Symfony

Cyril Pereira
3 min readJun 4, 2024

--

In my previous post i talked about Multi-tenant.

But there is another way to replace configuration, and do almost the same things, but with replacing the parameters.yml

I used this technique on Symfony 2.8, i guess it’s still working on recent Symfony version.

The strategy

We are going to manager multiple parameters.yml file for each of our customer, for the same reason descibed in the previous post.

Here is the file architecture of the parameters.

app/config
├── config.yml
├── config_dev.yml
├── config_prod.yml
├── config_test.yml
├── customers
│ ├── parameters.web1.yml
│ ├── ...
│ └── parameters.web10.yml
├── parameters.php
├── routing.yml
├── routing_dev.yml
├── security.yml
└── services.yml

The console

First of all we are going to edit the console to add a global param : customer.

app/console d:s:u -f --customer=web1
#!/usr/bin/env php
<?php

// app/console

umask(0000);

set_time_limit(0);

require_once __DIR__.'/bootstrap.php.cache';
require_once __DIR__.'/AppKernel.php';
require_once __DIR__.'/ApplicationCustomer.php';

use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Debug\Debug;

$input = new ArgvInput();
$env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';

if ($debug) {
Debug::enable();
}

$customer = $input->getParameterOption("--customer");

$kernel = new AppKernel($env, $debug, $customer);
$application = new ApplicationCustomer($kernel);
$application->run($input);

as you see we added a class ApplicationCustomer

ApplicationCustomer

with this class we just informe the console that we have a new param.

<?php

// app/ApplicationCustomer.php

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\HttpKernel\KernelInterface;

class ApplicationCustomer extends Application
{
public function __construct(KernelInterface $kernel)
{
parent::__construct($kernel);

$this->getDefinition()->addOption(new InputOption('--customer', null, InputOption::VALUE_REQUIRED, 'The Customer name.'));
}

}

The Kernel

Inside the kernel we are going to set the cache and the log depending of each customer, because we are going to generate cache and we don’t want to replace the parameters during all the request.

<?php

// app/AppKernel.php

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
private $customer;
public function __construct($environment, $debug, $customer = false)
{
$this->customer = $customer;
parent::__construct($environment, $debug);

}
public function registerBundles()
{
$customer = $this->getName();

$bundles = array(
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
new Symfony\Bundle\MonologBundle\MonologBundle(),
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
new Symfony\Bundle\AsseticBundle\AsseticBundle(),
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
);

if (in_array($this->getEnvironment(), array('dev', 'test'))) {
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
$bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
}

return $bundles;
}

public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
}

public static function loadParameters($customer, $loader)
{
return basename(__DIR__."/config/customers/parameters.$customer.yml");
}

public function getCustomer()
{
$customer = isset($_SERVER['SERVER_NAME']) ?
strtolower(current(explode(".", $_SERVER['SERVER_NAME']))) :
$this->customer;

return str_replace('-', '_', $customer);
}

public function getName()
{
return $this->getCustomer();
}

public function getCacheDir()
{
return $this->rootDir.'/cache/'.$this->getName().'/'.$this->getEnvironment();
}

public function getLogDir()
{
return $this->rootDir.'/logs/'.$this->getName();
}
}

The parameters.php, the key of all

in this file we are going to load the correct parameters.yml for the current customer.

This will work, from console or during a handle request.

<?php

// app/config/parameters.php

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader;

$customer = $container->getParameter('kernel.name');
$hostDir = $container->getParameter('kernel.root_dir') . '/config/customers/';

$filename = AppKernel::loadParameters($customer, $hostDir);

if (file_exists($hostDir.$filename)) {
$hostParamLoader = new Loader\YamlFileLoader($container, new FileLocator($hostDir));
$hostParamLoader->load($filename);
}

As you see we just load the correct file defined in the kernel during the boot.

And then, the final touch : config.yml

To make it works, we need to ask the framework to load parameters.php instead of parameters.yml

# app/config/config.yml

imports:
- { resource: parameters.php }
- { resource: services.yml }
- { resource: security.yml }

Conclusion

It’s really more easy than the Tenant Aware Bundle, but it’s not configurable as from database, and not easy to make it evolve easily.

But it does the job, and it’s really useful if you need to use the same code base for all your customer, but you want to get some change in the configuration, like database configuration or activate from feature-flag some feature.

Hope it can help you, enjoy !

--

--