Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using dynamic database names using config.php and doctrine.yaml in Symfony

I had setup a symfony project. All my database connections are in app/.env.

This is the way I had configured in .env file:

DATABASE_URL=mysql://[email protected]:3306/abcdefg

Now I want to use a .php file like config.php where I can store the values of database configurations in it and the application should also use the same instead of taking values from .env file.

This is to connect with different database's based on the application URL. So the database name depends on the URL.

To make it dynamic, I want to use a PHP file instead of .env file.

like image 512
Vamsi Krishna Avatar asked Nov 23 '25 02:11

Vamsi Krishna


1 Answers

(im assuming you are using Symfony version 4 or higher - but should also work in earlier versions with slight modifications)

Part 1 - loading container parameters from php

  1. Create file "config/my_config.php" like this:
<?php

$container->setParameter('my_param', 'something1');

$elements = [];
$elements[] = 'yolo1';
$elements[] = 'yolo2';

$container->setParameter('my_param_which_is_array', $elements);
  1. In your services.yaml file import "my_config.php" like this:
imports:
    - { resource: my_config.php }
  1. Clear cache.
  2. Check if those parameters are loaded in container - for example by running following commands:
php bin/console debug:container --parameter=my_param
 ----------- ------------
  Parameter   Value
 ----------- ------------
  my_param    something1
 ----------- ------------

php bin/console debug:container --parameter=my_param_which_is_array
 ------------------------- -------------------
  Parameter                 Value
 ------------------------- -------------------
  my_param_which_is_array   ["yolo1","yolo2"]
 ------------------------- -------------------

If above steps work then you can use your parameters from container in your application.

Important warning: If you will store security credentials in such php file (db user and password etc) then make sure you are not adding it to repository along with code of rest of your application - so add it to ".gitignore" similarily as ".env" is added there.

For more info about handling of symfony parameters see https://symfony.com/doc/current/service_container/parameters.html (on the code snippets click the "PHP" tab instead of "YAML" to see PHP examples)

Part 2 - using different database depending on url(host) or CLI parameter

To dynamically choose database connection credentials we can use a doctrine connection factory. We will decorate default service 'doctrine.dbal.connection_factory' with our modified version:

Create new file "src/Doctrine/MyConnectionFactory.php":

<?php

namespace App\Doctrine;

use Doctrine\Bundle\DoctrineBundle\ConnectionFactory;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\HttpFoundation\Request;

class MyConnectionFactory
{
    /**
     * @var array
     */
    private $db_credentials_per_site;

    /**
     * @var ConnectionFactory
     */
    private $originalConnectionFactory;

    public function __construct($db_credentials_per_site, ConnectionFactory $originalConnectionFactory)
    {
        $this->db_credentials_per_site = $db_credentials_per_site;
        $this->originalConnectionFactory = $originalConnectionFactory;
    }

    /**
     * Decorates following method:
     * @see \Doctrine\Bundle\DoctrineBundle\ConnectionFactory::createConnection
     */
    public function createConnection(array $params, Configuration $config = null, EventManager $eventManager = null, array $mappingTypes = [])
    {
        $siteName = $this->getSiteNameFromRequestOrCommand();

        if (!isset($this->db_credentials_per_site[$siteName])) {
            throw new \RuntimeException("MyConnectionFactory::createConnection - Unknown site name: {$siteName}");
        }

        return $this->originalConnectionFactory->createConnection(
            [
                'url' => $this->db_credentials_per_site[$siteName]['url'],
            ],
            $config,
            $eventManager,
            $mappingTypes
        );
    }

    /**
     * @return string
     */
    private function getSiteNameFromRequestOrCommand()
    {
        // If we are inside CLI command then take site name from '--site' command option:
        if (isset($_SERVER['argv'])) {
            $input = new ArgvInput();
            $siteName = $input->getParameterOption(['--site']);

            if (!$siteName) {
                throw new \RuntimeException("MyConnectionFactory::getSiteNameFromRequestOrCommand - You must provide option '--site=...'");
            }

            return (string) $siteName;
        }

        // Otherwise determine site name by request host (domain):
        $request = Request::createFromGlobals();
        $host = $request->getHost();
        switch ($host) {
            case 'my-blue-site.local.dev2':
                return 'blue_site';
            case 'redsite.local.com':
                return 'red_site';
        }

        throw new \RuntimeException("MyConnectionFactory::getSiteNameFromRequestOrCommand - Unknown host: {$host}");
    }
}

Now lets setup the decoration in services.yaml:

(you can read more about decorating of services here: https://symfony.com/doc/current/service_container/service_decoration.html)

    App\Doctrine\MyConnectionFactory:
        decorates: doctrine.dbal.connection_factory
        arguments:
            $db_credentials_per_site: '%db_credentials_per_site%'

and add 'db_credentials_per_site' parameter in "config/my_config.php" - as you see it is injected to MyConnectionFactory above:

$container->setParameter('db_credentials_per_site', [
    'blue_site' => [
        'url' => 'mysql://user1:[email protected]:3306/dbname-blue',
    ],
    'red_site' => [
        'url' => 'mysql://user2:[email protected]:3306/dbname-red',
    ],
]);

We need one more thing to support this feature in CLI commands - we need to add '--site' option to each command. As you see it is being read in \App\Doctrine\MyConnectionFactory::getSiteNameFromRequestOrCommand. It will be mandatory for all commands that will use database connection:

in services.yaml:

    App\EventListener\SiteConsoleCommandListener:
        tags:
            - { name: kernel.event_listener, event: console.command, method: onKernelCommand, priority: 4096 }

create new file "src/EventListener/SiteConsoleCommandListener.php":

<?php

namespace App\EventListener;

use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Input\InputOption;

class SiteConsoleCommandListener
{
    public function onKernelCommand(ConsoleCommandEvent $event)
    {
        // Add '--site' option to every command:
        $command = $event->getCommand();
        $command->addOption('site', null, InputOption::VALUE_OPTIONAL);
    }
}

Now we are ready to test if it works:

  • when you call http://my-blue-site.local.dev2/something then the 'blue_site' database credenatials will be used.
  • when you call http://something.blabla.com/something then the 'red_site' database credenatials will be used.
  • when you run following command then 'blue_site' database credenatials will be used:
php bin/console app:my-command --site=blue_site
  • when you run following command then 'red_site' database credenatials will be used:
php bin/console app:my-command --site=red_site
like image 102
domis86 Avatar answered Nov 25 '25 15:11

domis86



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!