Как внедрить репозиторий в сервис в 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 для внедрения зависимости:
- ввести Entity Manager в конструктор службы
private $em; public function __construct(EntityManagerInterface $em) { $this->em = $em; }
- Затем получите репозиторий:
$this->em->getRepository(Имя класса :: класс)
заменив ClassName на имя вашей сущности.
В моем случае я опираюсь на ответ @Tomáš Votruba и на этот вопрос я предлагаю следующие подходы:
Адаптерный подход
Без наследования
Создайте универсальный класс адаптера:
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); } } }
Затем foreach entity. Определите сервис, например, в моем случае, чтобы определить (я использую php для определения сервисов Symfony):
$container->register('ellakcy.db.contact_email',AppBundle\Services\Adapters\RepositoryServiceAdapter::class) ->serArguments([new Reference('doctrine'),AppBundle\Entity\ContactEmail::class]);
С наследованием
Тот же шаг 1, упомянутый выше
Расширить
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); } }
Регистрация службы:
$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
метод.