Skip to content

Symfony console breaks when injecting an EntityRepository to a service Command and the database is not created #17727

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1ma opened this issue Feb 8, 2016 · 11 comments

Comments

@1ma
Copy link
Contributor

1ma commented Feb 8, 2016

I've created a "PoC" project in order to ease with the bug reproduction, you can find it here: https://github.com/1ma/symfony-cmd-issue

Project contents

A brand new Symfony3 project with an empty entity (Test), its companion repository (TestRepository) and a command (TestCommand) defined as a service, as per the cookbook entry found at http://symfony.com/doc/current/cookbook/console/commands_as_services.html

# app/config/services.yml
services:
    test_repo:
        class: AppBundle\Repository\TestRepository
        factory: ["@doctrine.orm.default_entity_manager", getRepository]
        arguments: ["AppBundle:Test"]

    test_command:
        class: AppBundle\Command\TestCommand
        arguments:
            - "@test_repo"
        tags:
            - { name: console.command }

Steps to reproduce

  1. $ git clone https://github.com/1ma/symfony-cmd-issue.git

  2. $ cd symfony-cmd-issue; composer install

    When the prompt shows up, be sure to set valid values for database_user and database_password. They should map to a MySQL user with enough permissions to create the database.

  3. At this point the installation will break. Further calls to bin/console will also fail with the following error message:

    marcel@workstation:~/workspace/symfony-cmd-issue$ php bin/console
    
    [Doctrine\DBAL\Exception\ConnectionException]                                      
    An exception occured in driver: SQLSTATE[42000] [1049] Unknown database 'symfony'  
    
    
    [Doctrine\DBAL\Driver\PDOException]                
    SQLSTATE[42000] [1049] Unknown database 'symfony'  
    
    
    [PDOException]                                     
    SQLSTATE[42000] [1049] Unknown database 'symfony'
    
  4. In order to restore the console, remove the tags: block from the command service definition. Now you'll be able to execute the console and create the database:

    marcel@workstation:~/workspace/symfony-cmd-issue$ php bin/console doctrine:database:create
    Created database `symfony` for connection named default
    
  5. Once the database is created you can restore the tags: block in the command service definition and the console will still work:

    marcel@workstation:~/workspace/symfony-cmd-issue$ php bin/console cmd:test
    AppBundle\Entity\Test
    
  6. Drop the database, and the console breaks again:

    marcel@workstation:~/workspace/symfony-cmd-issue$ php bin/console doctrine:database:drop --force
    Dropped database for connection named `symfony`
    
    marcel@workstation:~/workspace/symfony-cmd-issue$ php bin/console cmd:test
    
    
    [Doctrine\DBAL\Exception\ConnectionException]                                      
    An exception occured in driver: SQLSTATE[42000] [1049] Unknown database 'symfony'  
    
    
    [Doctrine\DBAL\Driver\PDOException]                
    SQLSTATE[42000] [1049] Unknown database 'symfony'  
    
    
    [PDOException]                                     
    SQLSTATE[42000] [1049] Unknown database 'symfony'  
    

Troubleshooting

From what I've seen from the above exception stack trace, it seems that every service tagged with the console.command tag is instantiated at Application::registerCommands().

Then, if one of the command dependencies is a subclass of EntityRepository instantiated as EntityManager->getRepository('AppBundle:EntityName') a connection to the database will be always attempted down the line, even if it doesn't exist yet.

Here is the full stack trace with some of the most relevant entries highlighted in bold font:
#0 .../symfony-cmd-issue/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php(45): Doctrine\DBAL\Driver\PDOConnection::__construct('mysql:host=127....', 'root', 'root', Array)

#1 .../symfony-cmd-issue/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php(360): Doctrine\DBAL\Driver\PDOMySql\Driver->connect(Array, 'root', 'root', Array)
#2 .../symfony-cmd-issue/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php(429): Doctrine\DBAL\Connection->connect()
#3 .../symfony-cmd-issue/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php(389): Doctrine\DBAL\Connection->getDatabasePlatformVersion()
#4 .../symfony-cmd-issue/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php(328): Doctrine\DBAL\Connection->detectDatabasePlatform()
#5 .../symfony-cmd-issue/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php(763): Doctrine\DBAL\Connection->getDatabasePlatform()

#6 .../symfony-cmd-issue/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php(616): Doctrine\ORM\Mapping\ClassMetadataFactory->getTargetPlatform()
#7 .../symfony-cmd-issue/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php(174): Doctrine\ORM\Mapping\ClassMetadataFactory->completeIdGeneratorMapping(Object(Doctrine\ORM\Mapping\ClassMetadata))
#8 .../symfony-cmd-issue/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php(332): Doctrine\ORM\Mapping\ClassMetadataFactory->doLoadMetadata(Object(Doctrine\ORM\Mapping\ClassMetadata), NULL, false, Array)
#9 .../symfony-cmd-issue/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php(78): Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory->loadMetadata('AppBundle\Entit...')
#10 .../symfony-cmd-issue/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php(216): Doctrine\ORM\Mapping\ClassMetadataFactory->loadMetadata('AppBundle\Entit...')
#11 .../symfony-cmd-issue/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php(281): Doctrine\Common\Persistence\Mapping\AbstractClassMetadataFactory->getMetadataFor('AppBundle:Test')
#12 .../symfony-cmd-issue/vendor/doctrine/orm/lib/Doctrine/ORM/Repository/DefaultRepositoryFactory.php(44): Doctrine\ORM\EntityManager->getClassMetadata('AppBundle:Test')
#13 .../symfony-cmd-issue/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php(698): Doctrine\ORM\Repository\DefaultRepositoryFactory->getRepository(Object(Doctrine\ORM\EntityManager), 'AppBundle:Test')

#14 .../symfony-cmd-issue/var/cache/dev/appDevDebugProjectContainer.php(2465): Doctrine\ORM\EntityManager->getRepository('AppBundle:Test')
#15 .../symfony-cmd-issue/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Container.php(273): appDevDebugProjectContainer->getTestRepoService()

#16 .../symfony-cmd-issue/var/cache/dev/appDevDebugProjectContainer.php(2452): Symfony\Component\DependencyInjection\Container->get('test_repo')
#17 .../symfony-cmd-issue/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Container.php(273): appDevDebugProjectContainer->getTestCommandService()

#18 .../symfony-cmd-issue/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php(101): Symfony\Component\DependencyInjection\Container->get('test_command')
#19 .../symfony-cmd-issue/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php(71): Symfony\Bundle\FrameworkBundle\Console\Application->registerCommands()
#20 .../symfony-cmd-issue/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php(117): Symfony\Bundle\FrameworkBundle\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#21 .../symfony-cmd-issue/bin/console(29): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput))
#22 {main}

Further details

Tested on a Debian Jessie x64 virtual machine with PHP 5.6, Symfony 3.0.2 and Doctrine ORM 2.5.4

@peterrehm
Copy link
Contributor

It looks like your issue might be related to: doctrine/DoctrineBundle#351

Setting the server version in your config should fix that:

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                dbname:   Symfony2
                user:         root
                password: null
                host:         localhost
                driver:       pdo_mysql
                server_version: 5.6 # your database server version here

@peterrehm
Copy link
Contributor

Which version of Doctrine/DoctrineBundle are you using? composer show -i doctrine/dcotrine-bundle

@1ma
Copy link
Contributor Author

1ma commented Feb 8, 2016

@peterrehm 1.6.1

@1ma
Copy link
Contributor Author

1ma commented Feb 8, 2016

BTW I can confirm that this issue is related to the one you linked. Using the server_version parameter indeed solves the problem.

Thank you.

@xabbuh
Copy link
Member

xabbuh commented Feb 8, 2016

Closing here as there is nothing we can do in Symfony (as the issue is located in Doctrine).

@xabbuh xabbuh closed this as completed Feb 8, 2016
@peterrehm
Copy link
Contributor

@xabbuh @Tobion @weaverryan I thought that was fixed already? Is there anything we could do in this regards as it seems quite some people stumble upon this issue...

@xabbuh
Copy link
Member

xabbuh commented Feb 8, 2016

Not sure what we can do about this as doctrine/dbal#990 is still open.

@weaverryan
Copy link
Member

@peterrehm what we need (and what we don't have that I'm aware of) is very repeatable steps when someone hits this. It's very finnicky and only seems to happen under certain conditions. If we can repeat it consistently, then we may be able to fix it on a case-by-case basis. For example, I fixed one situation where Doctrine was loading some metadata (that now requires a DB connection due to the linked issue) but it didn't actually need that metadata in order to accomplish what it was doing. Since then, I've had almost no luck hitting the issue.

@peterrehm
Copy link
Contributor

Okay, looks like in the standard edition I am not able to reproduce it with the database:drop command.

But it looks like in the code above that it happens once you use console commands as a service and inject the entity manager in the command. With the above mentioned repo it easy to reproduce, see the steps above.

One simple fix could be adding the server_version as parameter to the standard edition with 5.6 as default. No matter how we fix it we should provide some solution to avoid people experiencing that road block.

@1ma
Copy link
Contributor Author

1ma commented Feb 9, 2016

@weaverryan here is a more concise step by step guide to reproduce this issue:

  1. define a repository as a service
  2. define a command as a service (with the console.command tag)
  3. inject the repository into the command
  4. drop the database
  5. don't use the server_version option in the DBAL configuration

Now every time you run the console, all the command dependencies will be instantiated, and in the case of the repository a connection to the database will be attempted in order to guess the server version, even though the database doesn't exist.

Hardcoding the server_version option into the Standard Edition config.yml as suggested by @peterrehm seems like a sensible move to me. Since the driver is already hardcoded to pdo_mysql I don't think it wouldn't break anything for new installations (of course, a fix in the Doctrine library would be better).

@ryanotella
Copy link

Injecting a PDO-based session handler into a console command also causes this issue, for similar reasons I believe.

It can be solved in Symfony 3.4+ by making the command lazy.

App\Command\Security\SessionGarbageCollectionCommand:
    tags:
        - { name: console.command, command: user:session:gc }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants