1

I have a Symfony application that shares the same codebase for many DBs.

It's quite typical, I have a single database for each "instance" (or "account") of my application, which is a simple medical appointments tool. I need to keep this architecture as is.

However, we are migrating from Symfony 2.8 to Symfony 4. Before Symfony 4, I had to create a "pseudo-prod" Symfony environment for each DB, because Symfony (with Doctrine) is very tied to having a single DB specified by the config, and even using env vars like SYMFONY__DATABASE__NAME, the database connection properties get hardcoded into the cache, so I needed to create a different environment for each app instance, hence creating a cache dir for each account, which is far from ideal.

Will the new env vars features from Symfony 4 make dynamic DB connection more feasible in a "native" manner?

Should I use another method to achieve this? (for example, creating a custom connection factory, etc)

I would like to have a single cache for my prod environment, and let the connection parameters be dynamic.

Any ideas appreciated. Thanks!

Note:

This is NOT a duplicate of: Using Relationships with Multiple Entity Managers

I am already using multiple entity managers, it has nothing to do with that.

  • https://symfony.com/doc/current/doctrine/multiple_entity_managers.html – caramba Feb 04 '18 at 20:34
  • Thanks, I read that, but I hardly see such a pattern ("database_host2") as "dynamic", I don't have a single "customer", what if I have > 50 databases? Do I have to add an entity manager for each one, database_host3, database_host4? All these values get hardcoded into the cache, which prevents me from having a single cache environment for all DBs. Will the env shorthand introduced in 3.2 solve this (`"password: "%env(DB_PASSWORD)%"`)? – Pablo Hessualdo Feb 04 '18 at 21:09
  • You would need to add a connection for each database that your application uses, in order for doctrine to connect to it. If you're using separate environments for multiple applications, then you can separate them out that way. `Entities cannot define associations across different entity managers. If you need that, there are several alternatives that require some custom setup.` – Will B. Feb 05 '18 at 06:49
  • Possible duplicate of [Using Relationships with Multiple Entity Managers](https://stackoverflow.com/questions/11463517/using-relationships-with-multiple-entity-managers) – Will B. Feb 05 '18 at 06:50
  • Yes, I get it, I'm actually using two entity managers, one for my client's data, which depends on its subdomain (say: johndoe.myapp.com), and one for some shared data, so I have two EMs, one for "myapp_shared" DB, and one for a "johndoe_data" DB. The thing is, DB connection data is hardcoded into the cache, which feels like nonsense IMO, because it forces me to have a cache dir per DB. – Pablo Hessualdo Feb 06 '18 at 09:13
  • @fyrye this is not a duplicate of the question you mentioned, can you remove that? It's confusing. I already clarified on the question. – Pablo Hessualdo Feb 06 '18 at 09:20

1 Answers1

2

Setting up your application for different hosts with Symfony 4 should be straight forward. You can just provide the DATABASE_URL as environment variable to each host, e.g. with nginx:

server {
    server_name domain.tld www.domain.tld;
    root /var/www/project/public;
    location / {
        try_files $uri /index.php$is_args$args;
    }
    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php7.1-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;

        fastcgi_param DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name";

        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;

        internal;
    }
    location ~ \.php$ {
        return 404;
    }
    error_log /var/log/nginx/project_error.log;
    access_log /var/log/nginx/project_access.log;
}

The import bit is fastcgi_param DATABASE_URL ....

By default all instances will use the same cache dir, which is probably not what you want. Imagine customer A seeing customer B's data, because it's pulled from cache and B accessed it first.

A way around this is to modify src/Kernel.php to factor in some other env variable, the project's base name or some other info identifying each host and append that to the cache base directory defined in getCacheDir. By default it looks like this:

public function getCacheDir(): string
{
    return dirname(__DIR__).'/var/cache/'.$this->environment;
}

You could also use Symfony's built in cache component to specify different app.caches per host instead. This way you can possibly reuse the system cache responsible for caching the container, annotations, validations, etc.

You can find the cache configuration in your config/packages/framework.yml under framework.cache. See: https://speakerdeck.com/dbrumann/caching-in-symfony-an-overview?slide=37

dbrumann
  • 16,803
  • 2
  • 42
  • 58
  • This is exactly what I'm doing, but using Apache. It looks like the DB connection properties (even when coming from environment variables) gets compiled into the class cache. Do you know any way of circumventing this? – Pablo Hessualdo Feb 06 '18 at 09:16
  • What we are doing in my current project is extending the kernel to set an additional parameter in construct: `$this->portalIdentifier = getenv('APP_PORTAL'); parent::__construct($environment, $debug);` and then use that identifier to load different config files and append it to the cache directory. I don't think we encountered your issue, so without further info I don't think I can help. – dbrumann Feb 06 '18 at 09:51
  • Yes. We also use a Redis server where we could easily share caches should we feel the need to. Luckily storage and memory are not a big issue on our servers, so the added overhead from duplicate caches doesn't bother us. – dbrumann Feb 06 '18 at 13:06
  • Yes, I know it's not big, my current cache size is about 8Mb for every directory, so no big deal. I guess I could live with that, I just wanted to know if there was any way of avoiding repetition, as the only thing changing from dir to dir is the connection params... :( – Pablo Feb 08 '18 at 13:52
  • If you use my approach with the additional portal identifier in the Kernel you could also change what name the Kernel returns and then use the same cache dir. Just make `getName` return the additional portal identifier or check how the filename for the compiled container is created. Then only the compiled container will be separate cache items but the remaining cache is the same. In theory this is the only place where the connection should be stored, so that should work and you can reuse the cache. – dbrumann Feb 09 '18 at 05:02