Как внедрить репозиторий в сервис в Symfony?

Мне нужно ввести два объекта в ImageService, Один из них является примером Repository/ImageRepository, который я получаю так:

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

Итак, как мне объявить это в моих services.yml? Вот услуга:

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

6 ответов

Решение

Я нашел эту ссылку, и это сработало для меня:

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%

Вот решение для тех, кто пришел из Google, как я:

Обновление: вот решение Symfony 2.6 (и выше):

services:

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

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

Устаревшее решение (Symfony 2.5 и менее):

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"

Если вы не хотите определять каждый репозиторий как сервис, начиная с версии 2.4 Вы можете сделать следующее, (default имя руководителя организации):

@=service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')

2017 и Symfony 3.3+ сделали это намного проще. То же самое в Symfony 4.x.

Посмотрите мой пост Как использовать репозиторий с Doctrine как Service в Symfony для более общего описания.

В вашем коде все, что вам нужно сделать, это использовать композицию вместо наследования - один из шаблонов SOLID.

1. Создать собственный репозиторий без прямой зависимости от Доктрины.

<?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. Добавьте регистрацию конфигурации с помощью автоматической регистрации на основе PSR-4.

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

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

3. Теперь вы можете добавить любую зависимость в любом месте с помощью конструктора

use MycompanyMainBundle\Repository\ImageRepository;

class ImageService
{
    public function __construct(ImageRepository $imageRepository)
    {
        $this->imageRepository = $imageRepository;
    }
}

Для Symfony 5 это действительно просто, без необходимости в services.yml для внедрения зависимости:

  1. ввести Entity Manager в конструктор службы
       private $em;

public function __construct(EntityManagerInterface $em)
{
    $this->em = $em;
}
  1. Затем получите репозиторий:

$this->em->getRepository(Имя класса :: класс)

заменив ClassName на имя вашей сущности.

В моем случае я опираюсь на ответ @Tomáš Votruba и на этот вопрос я предлагаю следующие подходы:

Адаптерный подход

Без наследования

  1. Создайте универсальный класс адаптера:

    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. Затем foreach entity. Определите сервис, например, в моем случае, чтобы определить (я использую php для определения сервисов Symfony):

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

С наследованием

  1. Тот же шаг 1, упомянутый выше

  2. Расширить RepositoryServiceAdapter класс например:

    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. Регистрация службы:

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

Либо в том случае, если у вас есть хороший тестируемый способ для функционального тестирования работы вашей базы данных, это также поможет вам при издевательствах на случай, если вы захотите провести модульное тестирование своего сервиса без необходимости слишком беспокоиться о том, как это сделать. Например, предположим, у нас есть следующий сервис:

//Namespace definitions etc etc

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

А RepositoryServiceAdapter адаптирует следующий репозиторий:

//Namespace definitions etc etc

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

тестирование

Таким образом, вы можете легко смоделировать / жестко закодировать / эмулировать поведение метода search определяется в SomeRepository высмеивая RepositoryServiceAdapter в подходе без наследования или ContactEmailRepositoryServiceAdapter в наследство один.

Фабричный подход

В качестве альтернативы вы можете определить следующую фабрику:

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

А затем переключитесь на аннотацию службы php, выполнив следующие действия:

Поместите это в файл ./app/config/services.php (для Symfony v3.4, . предполагается, что корень вашего 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/*');

И могу ./app/config/config.yml (. предполагается, что корень вашего ptoject)

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

#Other Configuration

Затем вы можете указать сервис следующим образом (используется из моего примера, где я использовал фиктивную сущность с именем Item):

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

Также в качестве общего совета, переход на php сервисная аннотация позволяет вам без проблем выполнить более продвинутую конфигурацию сервиса. Для фрагментов кода используйте специальный репозиторий, который я сделал, используя factory метод.

Другие вопросы по тегам