18

I'm learning Symfony and I've been trying to create a service, using a repository. I've created my repositories and entities from generate:entity, so they should be fine.

So far what I got in my services.yml is:

parameters:
    mytest.entity: TestTestBundle:Brand
    mytest.class:  Test\TestBundle\Entity\Brand
    default_repository.class: Doctrine\ORM\EntityRepository

services:
     myservice:
          class: %default_repository.class%
          factory-service: doctrine.orm.default_entity_manager
          factory-method: getRepository
          arguments:
            - %mytest.entity%

But when I try to call the service, I get this error:

Catchable Fatal Error: Argument 2 passed to Doctrine\ORM\EntityRepository::__construct() must be an instance of Doctrine\ORM\Mapping\ClassMetadata, none given, called in 

Then I tried to create the service just using an entity. My services.yml would look like:

services:
     myservice:
          class: %mytest.class%
          factory-service: doctrine.orm.default_entity_manager
          factory-method: getRepository
          arguments:
            - %mytest.entity%

But for this, I get:

Error: Call to undefined method 
                Test\TestBundle\Entity\Brand::findAll

Does anybody know what am I doing wrong?

Thanks

Strnm
  • 1,006
  • 2
  • 9
  • 21

7 Answers7

44

DEPRECATION WARNING: No more factory_service and factory_method. This is how you should do it since Symfony 2.6 (for Symfony 3.3+ check below):

parameters:
    entity.my_entity: "AppBundle:MyEntity"

services:
    my_entity_repository:
        class: AppBundle\Repository\MyEntityRepository
        factory: ["@doctrine", getRepository]
        arguments:
            - %entity.my_entity%

The new setFactory() method was introduced in Symfony 2.6. Refer to older versions for the syntax for factories prior to 2.6.

http://symfony.com/doc/2.7/service_container/factories.html

EDIT: Looks like they keep changing this, so since Symfony 3.3 there's a new syntax:

# app/config/services.yml
services:
    # ...

    AppBundle\Email\NewsletterManager:
        # call the static method
        factory: ['AppBundle\Email\NewsletterManagerStaticFactory', createNewsletterManager]

Check it out: http://symfony.com/doc/3.3/service_container/factories.html

Francesco Casula
  • 26,184
  • 15
  • 132
  • 131
  • This works nice, tnx. But I would like to get one more step further: what if I also want to inject a service into this repository (let's say a Paginator service)? I tried adding more params to it, but that doesn't work, since Symfony is not "expecting" any of those I give it. – userfuser Jan 29 '17 at 13:48
  • @userfuser did you try overriding the constructor? You can add all the new dependencies there, just remember to call `parent::__construct($arg)` to provide what Doctrine expects as well. – Francesco Casula Jan 30 '17 at 09:50
  • @FrancescoCasula I did think of it, but I wasn't yet ready to make it a global change for all the repos, since this injection was needed only in few cases. Eventually, I did it for this specific case by using a setter injection, like defined in the docs: http://symfony.com/doc/current/service_container/injection_types.html#setter-injection – userfuser Jan 30 '17 at 11:58
24

Here is how we did it in KnpRadBundle: https://github.com/KnpLabs/KnpRadBundle/blob/develop/DependencyInjection/Definition/DoctrineRepositoryFactory.php#L9

Finally it should be:

my_service:
    class: Doctrine\Common\Persistence\ObjectRepository
    factory_service: doctrine # this is an instance of Registry
    factory_method: getRepository
    arguments: [ %mytest.entity% ]

UPDATE

Since 2.4, doctrine allows to override the default repositor factory.

Here is a possible way to implement it in symfony: https://gist.github.com/docteurklein/9778800

Florian Klein
  • 8,692
  • 1
  • 32
  • 42
  • Thanks, If Doctrine\Common\Persistence\ObjectRepository, how can you instantiate it? factory-service: doctrine # this is an instance of Registry you mean, it is doctrine.orm.default_entity_manager ? – Strnm Jun 24 '13 at 04:47
  • no it's an implementation of https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Persistence/ManagerRegistry.php . You could do the same with an EntityManager instance. And in fact, the `class: ` is not important here, because it's a **factory** service, the container does not instanciate the given class itself but asks a services's method to do it. – Florian Klein Jun 24 '13 at 07:20
  • but the `class: Doctrine\Common\Persistence\ObjectRepository` is not wrong neither, because the returned instance is indeed an implementation of an ObjectRepository interface – Florian Klein Jun 24 '13 at 07:21
  • Thanks Florian. No, it does not, or I'm doing something wrong, but as I said: FatalErrorException: Error: Cannot instantiate interface Doctrine\Common\Persistence\ObjectRepository in – Strnm Jun 26 '13 at 00:26
  • Can you paste the result of your service dump (located in `app/cache/dev/appDevDebugProjectContainer.php`) ? The method should be named: `getMyserviceService` (based on your example). – Florian Klein Jun 26 '13 at 12:26
  • Thanks Florian: protected function getMyserviceService() { return $this->services['myservice'] = new \Test\TestBundle\Entity\Repository\BrandRepository('TestTestBundle:Brand'); } – Strnm Jun 27 '13 at 03:19
  • and you service definition ? – Florian Klein Jun 27 '13 at 08:17
  • 5
    HAHA! I got it I think :) it should be `factory_service` and not `factory-service`. note the `_`. – Florian Klein Jun 27 '13 at 08:57
  • I'm ashamed and I just can't say thank you enough! I was about to give up. Thanks heaps! – Strnm Jun 28 '13 at 00:45
10

You may have used the wrong YAML-Keys. Your first configuration works fine for me using

  • factory_service instead of factory-service
  • factory_method instead of factory-method
Daniel
  • 109
  • 1
  • 2
  • That's quite useful for me, thank you. I also found [some related note posted on Dec'2012](http://www.phamviet.net/2012/12/09/symfony-2-inject-service-as-dependency-in-to-repository/) - I'll leave link here, just in case someone is also looking for something like that. – Ivan Kolmychek May 29 '14 at 17:17
2

Since 2017 and Symfony 3.3+ this is now much easier.

Note: Try to avoid generic commands like generate:entity. They are desined for begginers to make project work fast. They tend to bare bad practises and take very long time to change.

Check my post How to use Repository with Doctrine as Service in Symfony for more general description.

To your code:

1. Update your config registration to use PSR-4 based autoregistration

# app/config/services.yml
services:
    _defaults:
        autowire: true

    Test\TestBundle\:
        resource: ../../src/Test/TestBundle

2. Composition over Inheritance - Create own repository without direct dependency on Doctrine

<?php

namespace Test\TestBundle\Repository;

use Doctrine\ORM\EntityManagerInterface;

class BrandRepository
{
    private $repository;

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

    public function findAll()
    {
        return $this->repository->findAll();
    }
}

3. Use in any Service or Controller via constructor injection

use Test\TestBundle\Repository\BrandRepository;

class MyController
{
    /**
     * @var BrandRepository
     */
    private $brandRepository;

    public function __construct(BrandRepository $brandRepository)
    {
        $this->brandRepository = $brandRepository;
    }

    public function someAction()
    {
        $allBrands = $this->brandRepository->findAll();
        // ...
    }

}
Tomas Votruba
  • 23,240
  • 9
  • 79
  • 115
  • This does instantiate a service with autowiring, but the service is not the repository itself, which doesn't solve the question. I _think_ the original configuration will work with autowiring by renaming the service to `Test\TestBundle\Entity\Brand`, removing the `class`, `factory_service`, and `factory_method` configurations, and adding `factory: 'Doctrine\ORM\EntityManagerInterface:getRepository'`. And maybe quote wrapping the argument. I still have a lot of refactoring on a current project with similar configurations, so I can't yet say absolutely, but I at least get the cache to clear. – iisisrael Jan 09 '18 at 22:18
  • 1
    One other thing - I'm also defining `Doctrine\ORM\EntityManagerInterface: { alias: doctrine.orm.default_entity_manager }` – iisisrael Jan 09 '18 at 22:24
0

I convert service.yml to service.xml, and update DependencyInjection Extension, everything is working for me. I don't know why, but yml config will thrown Catchable Fatal Error. You can try using xml config for service config.

service.yml:

services:
    acme.demo.apikey_userprovider:
        class: Acme\DemoBundle\Entity\UserinfoRepository
        factory-service: doctrine.orm.entity_manager
        factory-method: getRepository
        arguments: [ AcmeDemoBundle:Userinfo ]

    acme.demo.apikey_authenticator:
        class: Acme\DemoBundle\Security\ApiKeyAuthenticator
        arguments: [ "@acme.demo.apikey_userprovider" ]

service.xml:

<services>
    <service id="acme.demo.apikey_userprovider" class="Acme\DemoBundle\Entity\UserinfoRepository"  factory-service="doctrine.orm.entity_manager" factory-method="getRepository">
        <argument>AcmeDemoBundle:Userinfo</argument>
    </service>

    <service id="acme.demo.apikey_authenticator" class="Acme\DemoBundle\Security\ApiKeyAuthenticator">
        <argument type="service" id="acme.demo.apikey_userprovider" />
    </service>
</services>
Rivsen
  • 421
  • 4
  • 8
0

Symfony 3.3 and doctrine-bundle 1.8 there is a Doctrine\Bundle\DoctrineBundle\Repository\ContainerRepositoryFactory which helps to create repository as service.

Example

What we want

$rep = $kernel->getContainer()
    ->get('doctrine.orm.entity_manager')
    ->getRepository(Brand::class);

ORM description

# Brand.orm.yaml
...
repositoryClass: App\Repository\BrandRepository
...

Service description

# service.yaml

App\Repository\BrandRepository:
    arguments:
      - '@doctrine.orm.entity_manager'
      - '@=service("doctrine.orm.entity_manager").getClassMetadata("App\\Entity\\Brand")'
    tags:
        - { name: doctrine.repository_service }
    calls:
        - method: setDefaultLocale
          arguments:
              - '%kernel.default_locale%'
        - method: setRequestStack
          arguments:
              - '@request_stack'
Vladimir Pak
  • 658
  • 1
  • 9
  • 13
-2

sf 2.6+

parameters:
    mytest.entity: TestTestBundle:Brand
    mytest.class:  Test\TestBundle\Entity\Brand
    default_repository.class: Doctrine\ORM\EntityRepository

services:
     myservice:
          class: %default_repository.class%
          factory: ["@doctrine.orm.default_entity_manager", "getRepository"]
          arguments:
            - %mytest.entity%
  • Welcome to SO. I think it would be appreciated if you could add some explanation why this solves the problem. – René Vogt Feb 08 '16 at 13:28
  • https://symfony.com/doc/2.6/components/dependency_injection/factories.html Symfony\Component\DependencyInjection\Definition::setFactoryMethod(getRepository) is deprecated since version 2.6 and will be removed in 3.0. Use Definition::setFactory() – user3665895 Feb 08 '16 at 14:12