1

I have a project using a small amount of Symfony components, including dependency injection. Mainly for a small command-line project. Yesterday, I refactored to use Dependency injection, and now I'm trying to refactor and bring Doctrine into the mix.

Before I added DI I had a bootstrap file that set up doctrine and passed the EntityManager in the connection. This is not a good idea; you can see why I wanted to refactor.

So, how do I set up a Doctrine connection with DI? I assumed at first that the auto wiring would take care of it automatically. This is what I have so far. At the bottom, I'll add a couple of things I've tried with no luck but gets me to a different error message.

Composer.json:

"doctrine/orm": "^2.7",
"symfony/event-dispatcher": "^5.1",
"symfony/dependency-injection": "^5.1",
"symfony/config": "^5.1",
"symfony/yaml": "^5.1",
"symfony/monolog-bundle": "^3.5",
"symfony/process": "^5.1"

Security.yml:

parameters:

services:
  _defaults:
    autowire: true
    autoconfigure: true

  _instanceof:
    Symfony\Component\Console\Command\Command:
      tags: ['command']

  App\:
    lazy: true
    resource: '../src'

  App\Application:
    public: true
    arguments:
      - !tagged command

Index.php:

$container = new ContainerBuilder();

$loader = new YamlFileLoader($container, new FileLocator());
$loader->load(__DIR__ . '/config/services.yml');

//require __DIR__ . '/config/bootstrap.php';

$container->compile();

exit($container->get(Application::class)->run());

and when I add EntityManagerInterface to one of the commands I get an error:

class CreateRecipientCommand extends Command
{
    protected $em;

    public function __construct(EntityManagerInterface $entityManager, string $name = null)
    {
        $this->em = $entityManager;
        parent::__construct($name);
    }
}

Error message:

 Cannot autowire service "App\Command\CreateRecipientCommand": argument "$entityManager" of method "__construct()" references interface "Doctrine\ORM\EntityManagerInterface" but no such service exists. Did you create a class that implements this interface?

I know I need to bring in the Entity Manager and Doctrine factory somewhere, but I can't work out how.

Before I added DI I had a bootstrap file that set up the connection like this and I passed that $entityManager variable around:

$paths = [dirname(__DIR__) . '/src/Entities'];

$config = Setup::createConfiguration($isDevmode);
$driver = new AnnotationDriver(new AnnotationReader(), $paths);
$connectionParams = [
    'dbname' => 'dbname',
    'user' => 'root',
    'password' => 'root',
    'port' => '3306',
    'host' => 'localhost',
    'driver' => 'pdo_mysql',
];

$config->setMetadataDriverImpl($driver);

$entityManager = EntityManager::create($connectionParams, $config);

I've seen that some people have made the connection within the service as a factory? But, I can't seem to find the documentation again.

Working with Factory

I've added a DatabaseFactory returning the EntityManager connection.

class DatabaseFactory
{
    public function createManager(ContainerInterface $container)
    {
        $isDevmode = $container->getParameter('doctrine.orm.devmode');

        $paths = [dirname(__DIR__) . '/src/Entities'];

        $config = Setup::createConfiguration($isDevmode);
        $driver = new AnnotationDriver(new AnnotationReader(), $paths);
        
        $connectionParams = [
            'dbname' => $container->getParameter('doctrine.orm.dbname'),
            'user' => $container->getParameter('doctrine.orm.user'),
            'password' => $container->getParameter('doctrine.orm.password'),
            'port' => $container->getParameter('doctrine.orm.port'),
            'host' => $container->getParameter('doctrine.orm.host'),
            'driver' => $container->getParameter('doctrine.orm.driver'),
        ];

        $config->setMetadataDriverImpl($driver);

        return EntityManager::create($connectionParams, $config);
    }
}

services.yaml

Doctrine\ORM\EntityManager:
    factory: ['App\DatabaseFactory', 'createManager']

This now works but I'm wondering if this is the best way to do it, is there a doctrine factory I can call and pass it parameters for the connection?

Galironfydar
  • 171
  • 7
  • 2
    You are one the right track by [defining the service with a factory](https://symfony.com/doc/current/service_container/factories.html). Basically wrap up your entity manager creation code into a factory class. Lots of lots of small details to resolve but a factory will get you started. By the way, with your configuration, autowire only impacts code under the src directory. Basically knows nothing about vendor code. – Cerad Dec 16 '20 at 13:32
  • Thank you, definitely put me on the right track to get it working. I'm wondering is there a DoctrineFactory I can call and pass it the connection rather thank creating my own? – Galironfydar Dec 16 '20 at 15:21
  • Nope. No EntityManagerFactory that I can think of. You can take a peek at the DoctrineBundle DI extension to see all the rigmarole they go through to create their doctrine services. It's one reason why using the framework is so common. Glad you got it working. – Cerad Dec 16 '20 at 15:41

0 Answers0