Symfony2: Working with multiple databases

September 26, 2011Marek Kalnik4 min read

Symfony2 has been around for quite a while. Personally, I love how much PHP-oriented it is. It feels much closer to the language base than the first version of the framework. It means less of the magic and more of the important decisions in the hands of the development team. But there is no real framework without some magic, which is sometimes really hard to master.

Enough of the introduction, it is time for the technical stuff. First, let us take a look at some user cases:

  • As the bundle base grows, we see or might see soon a lot of bundles which would make heavy use of the database - blogs, forums, eCommerce... Let's say we want to integrate such a bundle with our EnormousWebsiteBundle. Easy, isn't it? But wait! We did not think of prefixing our table names (who does?), and neither did the author of the bundle. And all of a sudden we have conflicts everywhere.
  • We have some databases that already exist. And the client wants us to make an application that uses all of them.
  • We want to backup different sets of data at different frequencies.
  • We need to optimize our application, using different data storage solutions: a SQL database, a NoSQL database, etc.

In all those cases one of the solutions (or a necessity) is to use multiple databases. So let us do some Symfony2 magic!

It begins in config.yml

Lets say we have a simple blog bundle, which we want to adapt to use its own database. The easy part is configuring the connection:

doctrine: dbal: connections: ... blog: driver: %blog.database_driver% host: %blog.database_host% dbname: %blog.database_name% user: %blog.database_user% password: %blog.database_password% charset: UTF8

Next, create a second entity manager:

doctrine: orm: entity_managers: ... blog: connection: blog mappings: MyAwesomeBlogBundle: ~

Well, we can say that your bundle is configured.

Try to use your second database

It is easy to find in the official documentation that you can simply do

$this->get('doctrine')->getEntityManager($name)

to use your custom entity manager. But I guess you never actually had to do it, being happy with the default one. So this will require some refactoring. The simplest solution is to specify a parameter in your config, let's say:

parameters: my_awesome_blog.entity_manager.name: blog

If you're going to publish your bundle, set it to 'default', in case somebody wouldn't want to use a separate EM, and you should be safe. Now, you need to pass the parameter to each (well, most of) getEntityManager calls in your bundle. It will be a bit of work, depending on the was you used that function. Let's hope you defined some services, like this one:

my_awesome_blog.content_repository: class: %my_awesome_blog.content_repository.class% arguments: ['@doctrine.orm.entity_manager']

or some functions like

$this->getEntityManager()

in your controllers. Don't worry about the extra work, at least it will help you to decouple your code even more (and we like loosely coupled code, don't we?).

Is it all?

It depends. These are the basics. Things are getting tricky when:

You need to login with an entity which is not in the default entity manager

You will need to overwrite the user provider, and pass your custom entity manager to it. In the simplest form it will be something like that:

# security.yml security: providers: blog_user: id: my_awesome_blog.user_provider #this is the name of your service

Now you need to register a simple service which will use your custom entityManager:

# services.yml parameters: my_awesome_blog.user_provider.class: Symfony\Bridge\Doctrine\Security\User\EntityUserProvider my_awesome_blog.user_provider.user.class: MyCompany\MyAwesomeBlogBundle\Entity\User my_awesome_blog.user_provider.user.parameter: username

services: my_awesome_blog.user_provider: class: %my_awesome_blog.user_provider.class% arguments: - '@doctrine.orm.blog_entity_manager' - %my_awesome_blog.user_provider.user.class% - %my_awesome_blog.user_provider.user.parameter%

This one will allow you to use a standard "form_login" configuration, as long as you pass the provider to your firewall (see security reference if you're not familiar with the config options: http://symfony.com/doc/2.0/reference/configuration/security.html).

You have some forms that use your entities

This one is a little tricky. By default most of internal functions use

$container->get('doctrine')->getEntityManager()

which just doesn't work with multiple EM's. You'll get errors saying you Entity is not an Entity (feels like JavaScript!). Don't worry it is an Entity, just not registered in that manager. I've recently made a pull request about this issue (here), and it got into symfony:master, but if you still use 2.0 you have to take the matters in your own hands [update: The PR is merged in Symfony 2.1]. So far I've found one class that needs to be changed (see the pull request). Simply change the few mentioned lines of code, save it in your bundle and add this to your services' parameters:

doctrine.orm.validator.unique.class: MyCompany\MyAwesomeBlogBundle\Validator\Constraints\BlogUniqueEntityValidator

Well, if you ever find anything else, and you don't feel like defining your very own service, just try to use the awesome getEntityManagerForClass() function and overload some default classes.

Good luck!

Defining your own entity manager seems easy. This part of Symfony2 configuration is awesome. It is easy, as long as you're not trying to force it to do some more complicated stuff. After a certain point, you find a bunch of default services, which you need to redefine/overload/give up on using at all. Well, whether you really need to do this, or just want to see how it would be like... I wish you best of luck, and don't forget to share your experience!

M

Marek Kalnik

Web Developer at Theodo