Як ввести сховище в службу в Symfony?


78

Мені потрібно ввести два об’єкти 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);
    }
}

Загляньте на blog.code4hire.com/2011/08/…
simshaun

@simshaun Дякую, це допомогло мені знайти, як це зробити в yml: symfony.com/doc/master/components/dependency_injection/…
ChocoDeveloper

Відповіді:


105

Ось розчищене рішення для тих, хто надходить від 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
Під час використання MongoDB використовуйте doctrine.odm.mongodb.document_managerяк factory_service
Пратюш

Це чудово працює, але робить будь-які сховища, які ви додаєте таким чином, доступними через контролери $this->get('myrepository'). Чи є спосіб визначити / передати сховище як аргумент, myserviceне визначаючи його як саму послугу?
Енді,

1
@Andy, ви можете визначити послуги як private, що означає, що їх можна вводити (у конфігурації YAML), але не отримувати за допомогою->get()
Matthieu Napoli,

2
ПОПЕРЕДЖЕННЯ ПРО ВИКОРИСТАННЯ: Більше factory_serviceі factory_methodпісля Symfony 2.6 . Ось як це слід робити зараз: stackoverflow.com/a/31807608/828366
Франческо Касула

1
Зверніть увагу, що починаючи з Symfony 3.0 , ви повинні використовувати лапки для деяких конфігурацій YAML . Тож тут вам слід скористатися factory: ["@doctrine.orm.entity_manager", getRepository], інакше вас зустріне симпатичний ParseException.
Чехологія

45

Я знайшов це посилання, і це працювало для мене:

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%

6
ЗНИЖЕННЯ ПОПЕРЕДЖЕННЯ : Більше factory_service і factory_method немає з часів Symfony 2.6
Лучанінов,

Заводів за замовчуванням не буде, але Symfony 3.4 підтримує спосіб створення власних заводів.
Dimitrios Desyllas

40

Якщо ви не хочете визначати кожне сховище як послугу, починаючи з версії, 2.4ви можете зробити наступне, ( defaultце ім'я менеджера сутності):

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

3
Як це виглядатиме у файлах служб XML?
Джонні,

1
Це базується на компоненті виразу: symfony.com/doc/current/book/…
HenningCash

6
Використовуючи Symfony 2.7, я зміг отримати сховище з більш коротким синтаксисом:@=service('doctrine').getRepository('AppBundle:EntityX')
mgalic

Це чудово перекладено як "$ this-> get (" doctrine ") -> getRepository (" AppBundle: EntityX ")" in * Container.php ", люблю цей ярлик!
Томас Деко,

@Jonny ось версія xml:<service id="image_manager" class="MyCompany\MainBundle\ImageManager"> <argument type="expression">service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')</argument> </service>
Емілі

17

Symfony 3.3, 4 і 5 робить це набагато простішим.

Перевірте мій допис Як використовувати сховище з Доктриною як послугою в Symfony для отримання більш загального опису.

Для вашого коду все, що вам потрібно зробити, це використовувати композицію над успадкуванням - один із твердих шаблонів.

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 4.1?
Isengo

Так, механіка будівельних впорскувань не повинна змінюватися до Symfony 5. Які у вас проблеми?
Томаш Вотруба

Я створив службу у службовій папці під назвою UserManager і хочу використовувати там моє UsersRepository "клас UsersRepository розширює ServiceEntityRepository"
Isengo,

Це інший підхід, проти якого я виступаю в пості. Це створює величезний замок постачальника майже всіх ваших служб введення даних, пов’язаних з базами даних, до Symfony та Doctrine одночасно. Більше дивіться в дописі
Томаш Вотруба

0

У моєму випадку на основі відповіді @ 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 Визначте послугу, наприклад у моєму випадку для визначення a (я використовую 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методу.


Чи можете ви пояснити, чому ви це пропонуєте? Порівняно з необробленим рішенням, ви втрачаєте допомогу з автозавершення від своєї IDE - і що ви отримуєте?
Ніко Хаасе,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.