13

While using Symfony 3.3, I am declaring a service like this:

class TheService implements ContainerAwareInterface
{
    use ContainerAwareTrait;
    ...
}

Inside each action where I need the EntityManager, I get it from the container:

$em = $this->container->get('doctrine.orm.entity_manager');

This is a bit annoying, so I'm curious whether Symfony has something that acts like EntityManagerAwareInterface.

user3429660
  • 2,420
  • 4
  • 25
  • 41

4 Answers4

42

Traditionally, you would have created a new service definition in your services.yml file set the entity manager as argument to your constructor

app.the_service:
    class: AppBundle\Services\TheService
    arguments: ['@doctrine.orm.entity_manager']

More recently, with the release of Symfony 3.3, the default symfony-standard-edition changed their default services.yml file to default to using autowire and add all classes in the AppBundle to be services. This removes the need for adding the custom service and using a type hint in your constructor will automatically inject the right service.

Your service class would then look like the following:

use Doctrine\ORM\EntityManagerInterface;

class TheService
{
    private $em;

    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    // ...
}

For more information about automatically defining service dependencies, see https://symfony.com/doc/current/service_container/autowiring.html

The new default services.yml configuration file is available here: https://github.com/symfony/symfony-standard/blob/3.3/app/config/services.yml

S. Dre
  • 647
  • 1
  • 4
  • 18
2

Sometimes I inject the EM into a service on the container like this in services.yml:

 application.the.service:
      class: path\to\te\Service
      arguments:
        entityManager: '@doctrine.orm.entity_manager'

And then on the service class get it on the __construct method. Hope it helps.

Albeis
  • 1,544
  • 2
  • 17
  • 30
  • 1
    *Small note:* In the parameter list of the `__construct` method there must be a parameter named `$entityManager`, otherwise Symfony don't know which one to use for argument `entityManager`. – k00ni Nov 26 '19 at 16:22
0

I ran into the same issue and solved it by editing the migration code.

I replaced

$this->addSql('ALTER TABLE user ADD COLUMN name VARCHAR(255) NOT NULL');

by

$this->addSql('ALTER TABLE user ADD COLUMN name VARCHAR(255) NOT NULL DEFAULT "-"');

I don't know why bin/console make:entity doesn't prompt us to provide a default in those cases. Django does it and it works well.

VBobCat
  • 2,527
  • 4
  • 29
  • 56
0

So I wanted to answer your subquestion:

This is a bit annoying, so I'm curious whether Symfony has something that acts like EntityManagerAwareInterface.

And I think there is a solution to do so (I use it myself). The idea is that you slightly change your kernel so tha it checks for all services which implement the EntityManagerAwareInterface and injects it for them.

You can also add write an EntityManagerAwareTrait that implements the $entityManager property and the setEntityManager()setter. The only thing left after that is to implement/use the interface/trait couple the way you would do for the Logger for example.

(you could have done this through a compiler pass as well).

<?php
// src/Kernel.php

namespace App;

use App\Entity\EntityManagerAwareInterface;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use function array_key_exists;

class Kernel extends BaseKernel implements CompilerPassInterface
{
    use MicroKernelTrait;

    public function process(ContainerBuilder $container): void
    {
        $definitions = $container->getDefinitions();
        foreach ($definitions as $definition) {
            if (!$this->isAware($definition, EntityManagerAwareInterface::class)) {
                continue;
            }
            $definition->addMethodCall('setEntityManager', [$container->getDefinition('doctrine.orm.default_entity_manager')]);
        }
    }

    private function isAware(Definition $definition, string $awarenessClass): bool
    {
        $serviceClass = $definition->getClass();
        if ($serviceClass === null) {
            return false;
        }
        $implementedClasses = @class_implements($serviceClass, false);
        if (empty($implementedClasses)) {
            return false;
        }
        if (array_key_exists($awarenessClass, $implementedClasses)) {
            return true;
        }

        return false;
    }
}

The interface:

<?php
declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\EntityManagerInterface;

interface EntityManagerAwareInterface
{
    public function setEntityManager(EntityManagerInterface $entityManager): void;
}

The trait:

<?php
declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\EntityManagerInterface;

trait EntityManagerAwareTrait
{
    /** @var EntityManagerInterface */
    protected $entityManager;

    public function setEntityManager(EntityManagerInterface $entityManager): void
    {
        $this->entityManager = $entityManager;
    }
}

And now you can use it:

    <?php
    
    // src/SomeService.php
    declare(strict_types=1);
    
    namespace App;
    
    use Exception;
    use App\Entity\EntityManagerAwareInterface;
    use App\Entity\Entity\EntityManagerAwareTrait;
    use App\Entity\Entity\User;
    
    class SomeService implements EntityManagerAwareInterface
    {
        use EntityManagerAwareTrait;

        public function someMethod()
        {    
           $users = $this->entityManager->getRepository(User::Class)->findAll();
           // ...
        }
    }
medunes
  • 541
  • 3
  • 16