Symfony2 and Doctrine plateform configuration

Recently, I try to create a complete build archive of the Sonata Project web site (including vendor and cache) from a remote machine to deploy quickly on the production server.

I get a few problems and one is quite interesting as it show how fine tuning of the Symfony2 and the Doctrine configuration is important.

To generate the cache, I just start the cache:warmup command with the production settings and I get a nice exception:

[Doctrine\DBAL\Driver\PDOException]
    SQLSTATE[HY000] [1130] Host 'xxxxxx' is not allowed to connect to this MySQL server

This is quite weird to open a connection to the MySQL server to build the cache. So I restart the command with verbosity enabled, and get a nice stack trace. You need to read it bottom to top:

() at ROOT/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:43
PDO->__construct() at ROOT/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:43
Doctrine\DBAL\Driver\PDOConnection->__construct() at ROOT/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php:45
Doctrine\DBAL\Driver\PDOMySql\Driver->connect() at ROOT/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php:360
Doctrine\DBAL\Connection->connect() at ROOT/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php:429
Doctrine\DBAL\Connection->getDatabasePlatformVersion() at ROOT/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php:389
Doctrine\DBAL\Connection->detectDatabasePlatform() at ROOT/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php:328
Doctrine\DBAL\Connection->getDatabasePlatform() at ROOT/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php:81
Doctrine\ORM\Mapping\ClassMetadataFactory->initialize() at ROOT/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:292
Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory->loadMetadata() at ROOT/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php:211
Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory->getMetadataFor() at ROOT/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:265
Doctrine\ORM\EntityManager->getClassMetadata() at ROOT/vendor/doctrine/orm/lib/Doctrine/ORM/Repository/DefaultRepositoryFactory.php:67
Doctrine\ORM\Repository\DefaultRepositoryFactory->createRepository() at ROOT/vendor/doctrine/orm/lib/Doctrine/ORM/Repository/DefaultRepositoryFactory.php:50
Doctrine\ORM\Repository\DefaultRepositoryFactory->getRepository() at ROOT/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:665
Doctrine\ORM\EntityManager->getRepository() at ROOT/vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Doctrine/UserManager.php:40
FOS\UserBundle\Doctrine\UserManager->__construct() at ROOT/app/cache/dev/appDevDebugProjectContainer.php:2209

The first understanding is that the ClassMetadataFactory from Doctrine requires some information from the database. After digging into the code, I found out that Doctrine need to know the target plateform. So it connect to the database to found out which version of MySQL, or others databases you are using.

The stack trace is not complete, here another fragment:

FOS\UserBundle\Doctrine\UserManager->__construct() at ROOT/app/cache/dev/appDevDebugProjectContainer.php:2209
appDevDebugProjectContainer->getFosUser_UserManagerService() at ROOT/app/bootstrap.php.cache:2097
Symfony\Component\DependencyInjection\Container->get() at ROOT/app/cache/dev/appDevDebugProjectContainer.php:11108
appDevDebugProjectContainer->getSecurity_Authentication_ManagerService() at ROOT/app/bootstrap.php.cache:2097
Symfony\Component\DependencyInjection\Container->get() at ROOT/app/cache/dev/appDevDebugProjectContainer.php:3306
appDevDebugProjectContainer->getSecurity_AuthorizationCheckerService() at ROOT/app/bootstrap.php.cache:2097
Symfony\Component\DependencyInjection\Container->get() at ROOT/app/cache/dev/appDevDebugProjectContainer.php:3319
appDevDebugProjectContainer->getSecurity_ContextService() at ROOT/app/bootstrap.php.cache:2097
Symfony\Component\DependencyInjection\Container->get() at ROOT/app/cache/dev/appDevDebugProjectContainer.php:10607
appDevDebugProjectContainer->getTwigService() at ROOT/app/bootstrap.php.cache:2097

As you can see, anytime you want the twig service you are opening a database connection to know the target plateform. The target plateform will rarely change over time …

There is an hidden option in Symfony2 where you can configure the plateform service to use for a specific connection.

services:
    doctrine.plateform.mysql:
        class: Doctrine\DBAL\Platforms\MySqlPlatform

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:           %database_driver%
                dbname:           %database_name%
                user:             %database_user%
                host:             %database_host%
                password:         %database_password%
                platform_service: doctrine.plateform.mysql

The doctrine.plateform.mysql is the service identifier of the plateform used in the platform_service option from the connection settings. The complete plateforms list is available on the doctrine project’s website: http://doctrine-dbal.readthedocs.org/en/latest/reference/platforms.html

You should really tweak this settings if you have some actions or background processes which do not require any database connections.