¿Cómo inyectar un repositorio en un servicio en Symfony?

78

Necesito inyectar dos objetos en ImageService. Uno de ellos es una instancia de Repository/ImageRepository, que obtengo así:

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

Entonces, ¿cómo lo declaro en mi services.yml? Aquí está el servicio:

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);
    }
}
ChocoDesarrollador
fuente
Eche un vistazo a blog.code4hire.com/2011/08/…
simshaun
@simshaun Gracias, eso me ayudó a encontrar cómo hacerlo en yml: symfony.com/doc/master/components/dependency_injection/…
ChocoDeveloper

Respuestas:

105

Aquí hay una solución limpia para aquellos que vienen de Google como yo:

Actualización: aquí está la solución Symfony 2.6 (y superior):

services:

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

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

Solución obsoleta (Symfony 2.5 y menos):

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 Nápoles
fuente
2
Mientras usa MongoDB, use doctrine.odm.mongodb.document_managercomo
factory_service
Esto funciona, lo cual es increíble, pero hace que los repositorios que agregue de esta manera sean accesibles a través de los controladores $this->get('myrepository'). ¿Hay alguna forma de definir / pasar el repositorio como un argumento myservicesin tener que definirlo como un servicio en sí mismo?
Andy
1
@Andy puede definir los servicios como private, lo que significa que se pueden inyectar (en la configuración YAML) pero no se pueden recuperar usando->get()
Matthieu Napoli
2
ADVERTENCIA DE DEPRECACIÓN: No más factory_servicey factory_methoddesde Symfony 2.6 . Así es como se debe hacer ahora: stackoverflow.com/a/31807608/828366
Francesco Casula
1
Tenga en cuenta que desde Symfony 3.0 , debe usar comillas para algunas configuraciones de YAML . Entonces, aquí debe usar factory: ["@doctrine.orm.entity_manager", getRepository], de lo contrario, será recibido por una bonita ParseException.
Czechnology
45

Encontré este enlace y esto funcionó para mí:

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%
ChocoDesarrollador
fuente
6
ADVERTENCIA DE DEPRECACIÓN : No más factory_service y factory_method desde Symfony 2.6
luchaninov
No habrá ninguna fábrica predeterminada, pero Symfony 3.4 admite una forma de crear sus propias fábricas.
Dimitrios Desyllas
40

En caso de que no desee definir cada repositorio como un servicio, a partir de la versión 2.4puede hacer lo siguiente ( defaultes un nombre del administrador de la entidad):

@=service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')
b.b3rn4rd
fuente
3
¿Cómo se vería esto en archivos de servicios XML?
Jonny
1
Esto se basa en el componente de expresión: symfony.com/doc/current/book/…
HenningCash
6
Usando Symfony 2.7, pude obtener el repositorio con una sintaxis más corta:@=service('doctrine').getRepository('AppBundle:EntityX')
mgalic
Esto se traduce perfectamente como "$ this-> get (" doctrine ") -> getRepository (" AppBundle: EntityX ")" en * Container.php ", ¡me encanta este atajo!
Thomas Decaux
@Jonny aquí está la versión xml:<service id="image_manager" class="MyCompany\MainBundle\ImageManager"> <argument type="expression">service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')</argument> </service>
Emilie
17

Symfony 3.3, 4 y 5 lo hace mucho más sencillo.

Revisa mi publicación Cómo usar Repository con Doctrine como servicio en Symfony para obtener una descripción más general.

Para su código, todo lo que necesita hacer es usar la composición sobre la herencia , uno de los patrones SÓLIDOS.

1. Crea tu propio repositorio sin dependencia directa de 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. Agregue el registro de configuración con el registro automático basado en PSR-4

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

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

3. Ahora puede agregar cualquier dependencia en cualquier lugar mediante la inyección del constructor

use MycompanyMainBundle\Repository\ImageRepository;

class ImageService
{
    public function __construct(ImageRepository $imageRepository)
    {
        $this->imageRepository = $imageRepository;
    }
}
Tomáš Votruba
fuente
¿Está todavía actualizado para Symfony 4.1?
Isengo
Sí, la mecánica de inyección de construcción no debería cambiar hasta Symfony 5. ¿Qué problemas tienes?
Tomáš Votruba
Creé un servicio en la carpeta de servicio llamado UserManager y quiero usar mi UsersRepository allí "clase UsersRepository extiende ServiceEntityRepository"
Isengo
Ese es un enfoque diferente del que estoy defendiendo en la publicación. Se trata de crear un enorme bloqueo de proveedor de casi todos sus servicios de entrada relacionados con la base de datos a Symfony y Doctrine a la vez. Vea la publicación para más
Tomáš Votruba
0

En mi caso se basa en la respuesta de @ Tomáš Votruba y esta pregunta propongo los siguientes enfoques:

Enfoque del adaptador

Sin herencia

  1. Cree una clase de adaptador genérica:

    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. Luego, para cada entidad Defina un servicio, por ejemplo, en mi caso para definir un (yo uso php para definir los servicios de Symfony):

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

Con herencia

  1. Mismo paso 1 mencionado anteriormente

  2. Extienda la RepositoryServiceAdapterclase por ejemplo:

    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. Servicio de registro:

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

O en el caso de que tenga una buena forma comprobable de funcionamiento, prueba el comportamiento de su base de datos y también le ayuda a burlarse en caso de que desee realizar una prueba unitaria de su servicio sin la necesidad de preocuparse demasiado por cómo hacerlo. Por ejemplo, supongamos que tenemos el siguiente servicio:

//Namespace definitions etc etc

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

Y RepositoryServiceAdapter adapta el siguiente repositorio:

//Namespace definitions etc etc

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

Pruebas

Por lo tanto, puede simular / codificar / emular fácilmente el comportamiento del método searchdefinido en SomeRepositoryburlándose de laRepositoryServiceAdapter enfoque de no herencia o delContactEmailRepositoryServiceAdapter herencia.

El enfoque de fábrica

Alternativamente, puede definir la siguiente fábrica:

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

Y luego cambie a la anotación del servicio php haciendo lo siguiente:

Coloque esto en un archivo ./app/config/services.php(para Symfony v3.4, .se asume la raíz de su ptoject)

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/*');

Y cambiar el ./app/config/config.yml( .se asume la raíz de su ptoject)

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

#Other Configuration

Luego, puede clasificar el servicio de la siguiente manera (usado en mi ejemplo donde usé una entidad ficticia llamada Item):

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

También como un consejo genérico, cambiar a la phpanotación de servicio le permite realizar una configuración de servicio más avanzada sin problemas. Para los fragmentos de código, use un repositorio especial que hice usando el factorymétodo.

Dimitrios Desyllas
fuente
¿Puedes explicar por qué propones eso? En comparación con una solución en bruto, pierde la ayuda de autocompletado de su IDE, ¿y qué gana?
Nico Haase