Cannot autowire service: Argument references class but no such service exists
Asked Answered
A

5

46

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
Antimissile answered 29/12, 2017 at 14:39 Comment(1)
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.Chlorpromazine
M
80

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);
    }

    // ...
}
Moonrise answered 29/12, 2017 at 17:35 Comment(3)
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.Homogeneous
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\ManagerRegistryChef
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.Glaucescent
M
5

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.

Moonrise answered 29/12, 2017 at 15:20 Comment(9)
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.Chlorpromazine
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.Moonrise
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.Septal
@VadimAshikhman, I don't mind provide the answer to that specific question: https://mcmap.net/q/364189/-cannot-autowire-service-argument-references-class-but-no-such-service-exists But you will notice that that solution is even worst than this one.Moonrise
@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.Septal
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!Moonrise
@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.Septal
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.Lechery
w-wait, isn't this the standart way of constructor - injecting? what are others even talking about. You could argue about injecting classes via a setter to make it backwards compatible in some cases, or inject mock-classes. But constructor injection should be the go-to way!Shanney
P
4

I got this issue. My service had a private constructor. I had to change:

private function __construct(

to

public function __construct(
Prison answered 25/1, 2023 at 9:40 Comment(0)
S
3

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".

Smallman answered 2/10, 2020 at 13:31 Comment(1)
Thanks a ton, that was my issue as well. Had renamed the Folder containing the implementor and did not adjust the namespace.Amathist
B
1

I ran into the same problem, but used another solution by adding a factory method in the autowire yaml configuration file.

repositories.yml:

    MyCompany\SurveyBundle\Repository\SurveyQuestionRepository:
       class: MyCompany\SurveyBundle\Repository\SurveyQuestionRepository
       factory:    ["@doctrine.orm.entity_manager", getRepository]
       arguments:
           - MyCompany\SurveyBundle\Entity\SurveyQuestion

The SurveyQuestionRepository simply extends extends EntityRepository

Bagley answered 1/9, 2023 at 9:57 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.