34

I'm upgrading a project from Symfony 3 to Symfony 4 (https://github.com/symfony/symfony/blob/master/UPGRADE-4.0.md) and I have many repository/services like this:

namespace App\Entity;

use App\Entity\Activation;
use Doctrine\ORM\EntityRepository;
use Predis\Client;

class ActivationRepository extends EntityRepository
{
    // ...
}

And when I try to run the project in the browser like this:

http://localhost:8000/login

I get this error:

(1/1) RuntimeException
Cannot autowire service "App\Entity\ActivationRepository": 
argument "$class" of method 
"Doctrine\ORM\EntityRepository::__construct()" 
references class "Doctrine\ORM\Mapping\ClassMetadata" 
but no such service exists.

Does this mean you have to create a service for "Doctrine\ORM\Mapping\ClassMetadata" in your services.yaml file?

Thanks to autowiring my new services.yaml file is fairly small compared to the old one, which had 2000+ lines. The new services.yaml just has several of these (so far):

App\:
    resource: '../src/*'

# Controllers
App\Controller\:
    resource: '../src/Controller'
    autowire: true
    public: true
    tags: ['controller.service_arguments']

# Models
App\Model\:
    resource: '../src/Model/'
    autowire: true
    public: true

// etc

Question: Do you really need to add service definitions to services.yaml for third party vendor classes? And if so, can I get an example of how to do that please? Any advice from anyone who has already upgraded from Symfony 3 to Symfony 4 would be great.

PHP 7.2.0-2+ubuntu16.04.1+deb.sury.org+2 (cli) (built: Dec 7 2017 20:14:31) ( NTS ) Linux Mint 18, Apache2 Ubuntu.

EDIT / FYI:

This is the "Doctrine\ORM\EntityRepository::__construct()" which the ActivationRepository extends:

/**
     * Initializes a new <tt>EntityRepository</tt>.
     *
     * @param EntityManager         $em    The EntityManager to use.
     * @param Mapping\ClassMetadata $class The class descriptor.
     */
    public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
    {
        $this->_entityName = $class->name;
        $this->_em         = $em;
        $this->_class      = $class;
    }

which is located here:

/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php
Donal.Lynch.Msc
  • 3,001
  • 12
  • 41
  • 71
  • Yep. What JimL says. The problem is that you cannot simple new a doctrine repository even if you had the right dependencies. You need to use the EntityManager::getRepository method or things won't work. And autowire is not smart enough to be able to figure that out just be a typehint. – Cerad Dec 29 '17 at 16:01

3 Answers3

63

Starting from the 1.8 version of DoctrineBundle, you can extend your class using Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository instead of Doctrine\ORM\EntityRepository. The result will be the same, but this does support the autowire.

Example:

use App\Entity\Activation;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;

class ActivationRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Activation::class);
    }

    // ...
}
David
  • 3,782
  • 2
  • 28
  • 37
Federkun
  • 30,933
  • 8
  • 62
  • 80
  • Thank you Federkun and Massimiliano Arione. Not sure what bits you did between you, but you just helped me get one more step towards the end of my Symfony 3.4 to 4 journey. – WalterEgo Apr 30 '18 at 23:37
  • 1
    Just a note for who are using `doctrine/persistence >= 1.3` used by `doctrine-bundle 2.x`, use `Doctrine\Persistence\ManagerRegistry` instead of `Doctrine\Common\Persistence\ManagerRegistry` – Cristiano Casciotti Feb 12 '20 at 10:57
  • I would mention that because it is also important that they have to create a constructor and define the `entityClass` as you already did in your example. – Zoltán Süle Aug 12 '20 at 18:50
2

Do you really need to add service definitions to services.yaml for third party vendor classes?

No, don't do that. My personal suggestion is: don't extend EntityRepository. Ever. You don't want your repository's interface to have method like createQuery or flush. At least, you don't want that if you consider a repository just like a collection of objects. If you extend EntityRepository you will have a leaky abstraction.

Instead you can inject the EntityManager inside your repository, and that's it:

use App\Entity\Activation;
use App\Repository\ActivationRepository;
use Doctrine\ORM\EntityManagerInterface;

final class DoctrineActivationRepository implements ActivationRepository
{
    private $entityManager;
    private $repository;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
        $this->repository = $this->entityManager->getRepository(Activation::class);
    }

    public function store(Activation $activation): void
    {
        $this->entityManager->persist($activation);
        $this->entityManager->flush();
    }

    public function get($id): ?Activation
    {
        return $this->repository->find($id);
    }

    // other methods, that you defined in your repository's interface.
}

No other steps are required.

Federkun
  • 30,933
  • 8
  • 62
  • 80
  • Doctrine's repositories are more than a collection of objects. Nor is it clear how have a store method fit's in with a collection of objects. I understand what you are saying but your solution is overkill. – Cerad Dec 29 '17 at 16:04
  • Yeah, I know, I'm talking about `collections` and use an example with a `store` method, that's more persistence-oriented instead of collection-oriented. But if I replace that with an `add` method the next question is `where I commit my changes?` and we make the example too complex. Still, I don't think that this is overkill. But I guess it's just my personal opinion. Like everything, there's pro and cons. Using the EntityManager service locator has pro and cons as well. – Federkun Dec 29 '17 at 16:22
  • Injecting EntityManager for getting repository objects is like injecting Symfony DI container. If there is no way to autowire repository classes the only way is to define them in the services.yml. The subject is not about where to put persistent logic, it's about autowiring Doctrine repository classes. – Vadim Ashikhman Dec 29 '17 at 17:13
  • 1
    @VadimAshikhman, I don't mind provide the answer to that specific question: https://stackoverflow.com/a/48026278/711206 But you will notice that that solution is even worst than this one. – Federkun Dec 29 '17 at 17:37
  • 1
    @Federkun, wow, nice solution! In my personal opinion i think it is better than injecting the manager and calling it's `getRepository()` method, so you can only instantiate repository object that corresponds to only 1 entity. – Vadim Ashikhman Dec 29 '17 at 17:54
  • You will need to overwrite `ServiceEntityRepository#__construct` for each repository, and under the hood `ServiceEntityRepository` will do the same thing as what I described here. I guess that, in the end, it's more of a "choose your poison"-kinda of answer. Personally, I'll stick with what I suggested here. But, of course, everybody should choose what works better for them. Cheers! – Federkun Dec 29 '17 at 18:06
  • @Federkun, I agree with you, when you know much about a library you are using you can make many things which developers didn't think of. Hell, you can always use reflection to make something bad. In case of new developers that are joining a project, they will just use single entity class in the constructor, instead of getting any repository from the manager/registry. – Vadim Ashikhman Dec 29 '17 at 18:25
  • Beware that presented "store()" method seems convenient it kills the concept of having Doctrine to manage all writes done in a single transaction where possible. Use it only if you know what are you doing. Not sure why people are so hesitant to use entity manager directly in controllers/services and delegate things to repository/model managers etc. – barell Aug 17 '20 at 08:48
1

My issue was a wrong namespace. File real position was App\Infrastructure\MySQL\Rubric\Specification But namespace was set to App\Infrastructure\Rubric\Specification

Result "[blah blah] but no such service exists".

Balmipour
  • 2,427
  • 20
  • 25