3

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.

5
  • 2
    Why do you want to use another configuration file? .env or the system environment is the suggested place for configuration in the latest Symfony versions Commented Jul 31, 2019 at 13:07
  • 2
    From what it sounds like, using envvars is exactly what you want. Assuming you have multiple vhosts for each url like customer1.com, customer2.com, ... etc. In your webserver-config you can pass a DATABASE_URL envvar to your application, which will override .env. In Apache you would do this with SetEnv DATABASE_URL "mysql://..." and with nginx it would be fastcgi_param DATABASE_URL "mysql://...". No need to touch any config files. If this does not help, please explain how a config.php is supposed to solve your problem. Commented Jul 31, 2019 at 13:12
  • i have multiple databases which should be connected dynamically based on the sub-domain name @NicoHaase Commented Jul 31, 2019 at 14:06
  • my domain name will be my database name. In that case i can use the host name directly into the config file so that i connects dynamically to its respective database. @dbrumann Commented Jul 31, 2019 at 14:08
  • however, you answer relates my question. I will give it a try. @dbrumann Commented Jul 31, 2019 at 14:10

2 Answers 2

5

(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
Sign up to request clarification or add additional context in comments.

4 Comments

The answer seems a bit incomplete. How exactly does adding parameters with a php file allow connecting to different databases based in the application's url?
I understand your answer @domis86 . But what is the use if i'm able to access the values in services.yaml file ? The db configuration values are stored in doctrine.yaml or .env file.
Instead of doing like that, what i did is, the db configuration values are stored in /config/packages/doctrine.yaml so i had created a file in the same directory with the name of my_config.php Now I'm able to access the values in console by running the command php bin/console debug:container --parameter=my_param Now how can i use this values in the .yaml file ?
@Cerad , Vamsi Krishna - i updated my answer - now should be better ;)
0

Finally what i did to resolve this issue is, made few changes to @domis86 answer.

  1. Created a file as "my_config.php" inside "app/config/packages/".
  2. add code to it as follows
$container->setParameter('my_param', 'mysql://[email protected]:0000/'.$_SERVER['HTTP_HOST']);

The credentials from .env file are being used in "app/config/packages/doctrine.yaml"

Previously the "doctrine.yaml" looked like this:

doctrine:
    dbal:
        # configure these for your database server
        driver: 'pdo_mysql'
        server_version: '5.7'
        charset: utf8mb4
        default_table_options:
            charset: utf8mb4
            collate: utf8mb4_unicode_ci

        url: '%env(resolve:DATABASE_URL)%'
    orm:
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                type: annotation
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

Now, i added the below line to import my newly created "my_config.php" file to "doctrine.yaml" like this:

imports:
    - { resource: my_config.php }

then made few changes to code like this: (only added "'%my_param%'" to url line)

imports:
    - { resource: my_config.php }

doctrine:
    dbal:
        # configure these for your database server
        driver: 'pdo_mysql'
        server_version: '5.7'
        charset: utf8mb4
        default_table_options:
            charset: utf8mb4
            collate: utf8mb4_unicode_ci

        url: '%my_param%'
    orm:
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                type: annotation
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

This solved my issue.

Thank you all @Nico Haase @dbrumann @domis86 @Cerad for your support.

1 Comment

Unfortunately this solution will not work very well inside the CLI commands. Please check my answer again - i updated it with a "connection factory" solution

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.