81

I need to inject two objects into ImageService. One of them is an instance of Repository/ImageRepository, which I get like this:

$image_repository = $container->get('doctrine.odm.mongodb')
    ->getRepository('MycompanyMainBundle:Image');

So how do I declare that in my services.yml? Here is the service:

namespace Mycompany\MainBundle\Service\Image;

use Doctrine\ODM\MongoDB\DocumentRepository;

class ImageManager {
    private $manipulator;
    private $repository;

    public function __construct(ImageManipulatorInterface $manipulator, DocumentRepository $repository) {
        $this->manipulator = $manipulator;
        $this->repository = $repository;
    }

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

    public function createThumbnail(ImageInterface $image) {
        return $this->manipulator->resize($image->source(), 300, 200);
    }
}
Matthieu Napoli
  • 48,448
  • 45
  • 173
  • 261
ChocoDeveloper
  • 14,160
  • 26
  • 79
  • 117
  • Take a peek at http://blog.code4hire.com/2011/08/custom-repository-with-dic-in-symfony2/ – simshaun Aug 31 '12 at 22:26
  • @simshaun Thanks, that helped me to find how to do it in yml: http://symfony.com/doc/master/components/dependency_injection/factories.html – ChocoDeveloper Sep 01 '12 at 00:30

6 Answers6

108

Here is a cleaned up solution for those coming from Google like me:

Update: here is the Symfony 2.6 (and up) solution:

services:

    myrepository:
        class: Doctrine\ORM\EntityRepository
        factory: ["@doctrine.orm.entity_manager", getRepository]
        arguments:
            - MyBundle\Entity\MyClass

    myservice:
        class: MyBundle\Service\MyService
        arguments:
            - "@myrepository"

Deprecated solution (Symfony 2.5 and less):

services:

    myrepository:
        class: Doctrine\ORM\EntityRepository
        factory_service: doctrine.orm.entity_manager
        factory_method: getRepository
        arguments:
            - MyBundle\Entity\MyClass

    myservice:
        class: MyBundle\Service\MyService
        arguments:
            - "@myrepository"
Matthieu Napoli
  • 48,448
  • 45
  • 173
  • 261
  • 2
    While using MongoDB, use `doctrine.odm.mongodb.document_manager` as factory_service – Pratyush Feb 16 '15 at 21:35
  • This works which is awesome but it makes any repositories you add in this way accessible through controllers with `$this->get('myrepository')`. Is there any way to define/pass the repository as an argument to `myservice` without having to define it as a service itself? – Andy Sep 28 '15 at 15:23
  • 1
    @Andy you can define services as `private`, which means they can be injected (in YAML config) but not fetched using `->get()` – Matthieu Napoli Oct 04 '15 at 10:19
  • @MatthieuNapoli Thank you! That's awesome – Andy Oct 04 '15 at 11:10
  • 2
    **DEPRECATION WARNING:** No more `factory_service` and `factory_method` since **Symfony 2.6**. This is how it should be done now: http://stackoverflow.com/a/31807608/828366 – Francesco Casula Oct 12 '15 at 13:41
  • @FrancescoCasula thank you, I've updated the answer :) – Matthieu Napoli Oct 13 '15 at 09:03
  • 1
    Note that since Symfony **3.0**, you should use quotes for [some YAML configurations](https://github.com/symfony/symfony/blob/master/UPGRADE-3.0.md#yaml). So here you should use `factory: ["@doctrine.orm.entity_manager", getRepository]`, otherwise you'll be greeted by a pretty ParseException. – Czechnology Mar 06 '16 at 20:32
  • PhpStorm complains - "Expect instance of 'Doctrine\ORM\EntityManagerInterface" . Symfony 3.4 – Darius.V Apr 23 '18 at 12:20
46

I found this link and this worked for me:

parameters:
    image_repository.class:            Mycompany\MainBundle\Repository\ImageRepository
    image_repository.factory_argument: 'MycompanyMainBundle:Image'
    image_manager.class:               Mycompany\MainBundle\Service\Image\ImageManager
    image_manipulator.class:           Mycompany\MainBundle\Service\Image\ImageManipulator

services:
    image_manager:
        class: %image_manager.class%
        arguments:
          - @image_manipulator
          - @image_repository

    image_repository:
        class:           %image_repository.class%
        factory_service: doctrine.odm.mongodb
        factory_method:  getRepository
        arguments:
            - %image_repository.factory_argument%

    image_manipulator:
        class: %image_manipulator.class%
ChocoDeveloper
  • 14,160
  • 26
  • 79
  • 117
42

In case if do not want to define each repository as a service, starting from version 2.4 you can do following, (default is a name of the entity manager):

@=service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')
b.b3rn4rd
  • 8,494
  • 2
  • 45
  • 57
  • 3
    How would this look in an XML services files? – Jonny May 19 '15 at 13:53
  • 1
    This is based on the expression component: http://symfony.com/doc/current/book/service_container.html#using-the-expression-language – HenningCash Sep 05 '15 at 18:47
  • 7
    Using Symfony 2.7, I was able to get the repository with shorter syntax: `@=service('doctrine').getRepository('AppBundle:EntityX')` – mgalic Nov 12 '15 at 20:22
  • This is perfectly translated as "$this->get("doctrine")->getRepository("AppBundle:EntityX")" in *Container.php", love this shortcut! – Thomas Decaux Aug 01 '16 at 16:22
  • @Jonny here's the xml version:` service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image') ` – Emilie Sep 06 '16 at 18:06
  • so for injected specific arguments: it would be $orderRepository: "@=service('doctrine').getRepository('App:Order')" and composer require symfony/expression-language – max4ever Sep 13 '19 at 13:08
20

Symfony 3.3, 4 and 5 makes this much simpler.

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

To your code, all you need to do is use composition over inheritance - one of SOLID patterns.

1. Create own repository without direct dependency on Doctrine

<?php

namespace MycompanyMainBundle\Repository;

use Doctrine\ORM\EntityManagerInterface;
use MycompanyMainBundle\Entity\Image;

class ImageRepository
{
    private $repository;

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

    // add desired methods here
    public function findAll()
    {
        return $this->repository->findAll();
    }
}

2. Add config registration with PSR-4 based autoregistration

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

    MycompanyMainBundle\:
        resource: ../../src/MycompanyMainBundle

3. Now you can add any dependency anywhere via constructor injection

use MycompanyMainBundle\Repository\ImageRepository;

class ImageService
{
    public function __construct(ImageRepository $imageRepository)
    {
        $this->imageRepository = $imageRepository;
    }
}
Tomas Votruba
  • 23,240
  • 9
  • 79
  • 115
  • Is this still up-to-date for Symfony 4.1 ? – Isengo Oct 22 '18 at 19:44
  • Yes, construction injection mechanics should not change up to Symfony 5. What troubles do you have? – Tomas Votruba Oct 22 '18 at 20:01
  • I created a service in Service Folder called UserManager and wanna use my UsersRepository there "class UsersRepository extends ServiceEntityRepository" – Isengo Oct 22 '18 at 20:04
  • That's a different approach I'm advocating against in the post. It's create huge vendor lock of almost all your database-related entry services to Symfony and Doctrine at once. See the post for more – Tomas Votruba Oct 22 '18 at 20:51
0

In my case bases upon @Tomáš Votruba answer and this question I propose the following approaches:

Adapter Approach

Without Inheritance

  1. Create a generic Adapter Class:

    namespace AppBundle\Services;
    use Doctrine\ORM\EntityManagerInterface;
    
    class RepositoryServiceAdapter
    {
        private $repository=null;
    
        /**
        * @param EntityManagerInterface the Doctrine entity Manager
        * @param String $entityName The name of the entity that we will retrieve the repository
        */
        public function __construct(EntityManagerInterface $entityManager,$entityName)
        {
            $this->repository=$entityManager->getRepository($entityName)
        }
    
        public function __call($name,$arguments)
        {
          if(empty($arrguments)){ //No arguments has been passed
            $this->repository->$name();
          } else {
            //@todo: figure out how to pass the parameters
            $this->repository->$name(...$argument);
          }
        }
    }
    
  2. Then foreach entity Define a service, for examplein my case to define a (I use php to define symfony services):

     $container->register('ellakcy.db.contact_email',AppBundle\Services\Adapters\RepositoryServiceAdapter::class)
      ->serArguments([new Reference('doctrine'),AppBundle\Entity\ContactEmail::class]);
    

With Inheritance

  1. Same step 1 mentioned above

  2. Extend the RepositoryServiceAdapter class for example:

    namespace AppBundle\Service\Adapters;
    
    use Doctrine\ORM\EntityManagerInterface;
    use AppBundle\Entity\ContactEmail;
    
    class ContactEmailRepositoryServiceAdapter extends RepositoryServiceAdapter
    {
      public function __construct(EntityManagerInterface $entityManager)
      {
        parent::__construct($entityManager,ContactEmail::class);
      }
    }
    
  3. Register service:

    $container->register('ellakcy.db.contact_email',AppBundle\Services\Adapters\RepositoryServiceAdapter::class)
      ->serArguments([new Reference('doctrine')]);
    

Either the case you have a good testable way to function tests your database beavior also it aids you on mocking in case you want to unit test your service without the need to worry too much on how to do that. For example, let us suppose we have the following service:

//Namespace definitions etc etc

class MyDummyService
{
  public function __construct(RepositoryServiceAdapter $adapter)
  {
    //Do stuff
  }
}

And the RepositoryServiceAdapter adapts the following repository:

//Namespace definitions etc etc

class SomeRepository extends \Doctrine\ORM\EntityRepository
{
   public function search($params)
   {
     //Search Logic
   }
}

Testing

So you can easily mock/hardcode/emulate the behavior of the method search defined in SomeRepository by mocking aither the RepositoryServiceAdapter in non-inheritance approach or the ContactEmailRepositoryServiceAdapter in the inheritance one.

The Factory Approach

Alternatively you can define the following factory:

namespace AppBundle\ServiceFactories;

use Doctrine\ORM\EntityManagerInterface;

class RepositoryFactory
{
  /**
  * @param EntityManagerInterface $entityManager The doctrine entity Manager
  * @param String $entityName The name of the entity
  * @return Class
  */
  public static function repositoryAsAService(EntityManagerInterface $entityManager,$entityName)
  {
    return $entityManager->getRepository($entityName);
  }
}

And then Switch to php service annotation by doing the following:

Place this into a file ./app/config/services.php (for symfony v3.4, . is assumed your ptoject's root)

use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$definition = new Definition();

$definition->setAutowired(true)->setAutoconfigured(true)->setPublic(false);

// $this is a reference to the current loader
$this->registerClasses($definition, 'AppBundle\\', '../../src/AppBundle/*', '../../src/AppBundle/{Entity,Repository,Tests,Interfaces,Services/Adapters/RepositoryServiceAdapter.php}');


$definition->addTag('controller.service_arguments');
$this->registerClasses($definition, 'AppBundle\\Controller\\', '../../src/AppBundle/Controller/*');

And cange the ./app/config/config.yml (. is assumed your ptoject's root)

imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    #Replace services.yml to services.php
    - { resource: services.php }

#Other Configuration

Then you can clace the service as follows (used from my example where I used a Dummy entity named Item):

$container->register(ItemRepository::class,ItemRepository::class)
  ->setFactory([new Reference(RepositoryFactory::class),'repositoryAsAService'])
  ->setArguments(['$entityManager'=>new Reference('doctrine.orm.entity_manager'),'$entityName'=>Item::class]);

Also as a generic tip, switching to php service annotation allows you to do trouble-free more advanced service configuration thin one above. For code snippets use a special repository I made using the factory method.

Dimitrios Desyllas
  • 9,082
  • 15
  • 74
  • 164
  • Can you explain **why** you propose that? Compared to a raw solution, you loose auto-completion help from your IDE - and what do you gain? – Nico Haase May 25 '20 at 16:24
0

For Symfony 5 it is really simple, without need of services.yml to inject the dependency:

  1. inject the Entity Manager in the service constructor
private $em;

public function __construct(EntityManagerInterface $em)
{
    $this->em = $em;
}
  1. Then get the repository :

$this->em->getRepository(ClassName::class)

by replacing ClassName with your entity name.

Aris
  • 4,643
  • 1
  • 41
  • 38
  • While this works, it's the wrong approach, you should inject your Repository and not get it via the EntityManager as this breaks proper DI. – ikyuchukov Mar 16 '22 at 11:17