Symfony 2: Creating a service from a Repository
Asked Answered
H

7

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

Haftarah answered 21/6, 2013 at 5:42 Comment(0)
D
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

Decrease answered 21/6, 2013 at 8:0 Comment(9)
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 ?Haftarah
no it's an implementation of github.com/doctrine/common/blob/master/lib/Doctrine/Common/… . 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.Decrease
but the class: Doctrine\Common\Persistence\ObjectRepository is not wrong neither, because the returned instance is indeed an implementation of an ObjectRepository interfaceDecrease
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 inHaftarah
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).Decrease
Thanks Florian: protected function getMyserviceService() { return $this->services['myservice'] = new \Test\TestBundle\Entity\Repository\BrandRepository('TestTestBundle:Brand'); }Haftarah
and you service definition ?Decrease
HAHA! I got it I think :) it should be factory_service and not factory-service. note the _.Decrease
I'm ashamed and I just can't say thank you enough! I was about to give up. Thanks heaps!Haftarah
Y
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

Yettayetti answered 4/8, 2015 at 11:4 Comment(3)
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.Instrumentality
@Instrumentality 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.Yettayetti
@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: symfony.com/doc/current/service_container/…Instrumentality
D
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

Decrease answered 21/6, 2013 at 8:0 Comment(9)
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 ?Haftarah
no it's an implementation of github.com/doctrine/common/blob/master/lib/Doctrine/Common/… . 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.Decrease
but the class: Doctrine\Common\Persistence\ObjectRepository is not wrong neither, because the returned instance is indeed an implementation of an ObjectRepository interfaceDecrease
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 inHaftarah
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).Decrease
Thanks Florian: protected function getMyserviceService() { return $this->services['myservice'] = new \Test\TestBundle\Entity\Repository\BrandRepository('TestTestBundle:Brand'); }Haftarah
and you service definition ?Decrease
HAHA! I got it I think :) it should be factory_service and not factory-service. note the _.Decrease
I'm ashamed and I just can't say thank you enough! I was about to give up. Thanks heaps!Haftarah
I
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
Initiate answered 17/11, 2013 at 16:6 Comment(1)
That's quite useful for me, thank you. I also found some related note posted on Dec'2012 - I'll leave link here, just in case someone is also looking for something like that.Defamatory
V
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();
        // ...
    }

}
Vanden answered 21/10, 2017 at 11:23 Comment(2)
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.Swound
One other thing - I'm also defining Doctrine\ORM\EntityManagerInterface: { alias: doctrine.orm.default_entity_manager }Swound
R
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>
Ruche answered 17/7, 2014 at 7:9 Comment(1)
your yml keys are wrong, this factory-service should be factory_service (underscore).Abjuration
B
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'
Bellow answered 18/11, 2017 at 9:51 Comment(0)
S
-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%
Sailer answered 8/2, 2016 at 13:10 Comment(2)
Welcome to SO. I think it would be appreciated if you could add some explanation why this solves the problem.Pokeberry
symfony.com/doc/2.6/components/dependency_injection/… Symfony\Component\DependencyInjection\Definition::setFactoryMethod(getRepository) is deprecated since version 2.6 and will be removed in 3.0. Use Definition::setFactory()Sailer

© 2022 - 2024 — McMap. All rights reserved.