【问题标题】:How to inject a repository into a service in Symfony?如何将存储库注入 Symfony 中的服务?
【发布时间】:2012-08-26 17:36:45
【问题描述】:

我需要将两个对象注入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);
    }
}

【问题讨论】:

标签: php symfony dependency-injection


【解决方案1】:

对于 Symfony 5 来说真的很简单,不需要 services.yml 来注入依赖:

  1. 在服务构造函数中注入实体管理器
private $em;

public function __construct(EntityManagerInterface $em)
{
    $this->em = $em;
}
  1. 然后获取存储库:

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

将 ClassName 替换为您的实体名称。

【讨论】:

    【解决方案2】:

    Symfony 3.3、4 和 5 使这变得更简单。

    查看我的帖子 How to use Repository with Doctrine as Service in Symfony 以获得更一般的描述。

    对于您的代码,您需要做的就是使用组合而不是继承 - SOLID 模式之一。

    1。创建自己的存储库而不直接依赖于 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。添加配置注册PSR-4 based autoregistration

    # 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 来说仍然是最新的吗?
    • 是的,构造注入机制不应该改变到 Symfony 5。你有什么麻烦?
    • 我在服务文件夹中创建了一个名为 UserManager 的服务,并想在那里使用我的 UsersRepository "class UsersRepository extends ServiceEntityRepository"
    • 这是我在帖子中提倡反对的另一种方法。它一次为 Symfony 和 Doctrine 创建了几乎所有与数据库相关的入口服务的巨大供应商锁定。更多内容见帖子
    【解决方案3】:

    在我的案例中,基于@Tomáš Votruba 的回答和这个question,我提出了以下方法:

    适配器方法

    没有继承

    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实体定义一个服务,例如我的例子定义一个(我用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
       }
    }
    

    测试

    因此,您可以通过在非继承方法中模拟 RepositoryServiceAdapter 或在继承方法中模拟 ContactEmailRepositoryServiceAdapter 来轻松模拟/硬编码/模拟在 SomeRepository 中定义的方法 search 的行为。

    工厂方法

    或者,您可以定义以下工厂:

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

    并且 cange ./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 服务注解可以让您轻松完成上述更高级的服务配置。对于代码 sn-ps,请使用我使用 factory 方法制作的特殊 repository

    【讨论】:

    • 你能解释一下为什么你提出这个建议吗?与原始解决方案相比,您失去了 IDE 的自动完成帮助 - 您会获得什么?
    【解决方案4】:

    对于像我这样来自 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"
    

    【讨论】:

    • 使用 MongoDB 时,使用 doctrine.odm.mongodb.document_manager 作为 factory_service
    • 这项工作非常棒,但它使您以这种方式添加的任何存储库都可以通过带有$this-&gt;get('myrepository') 的控制器访问。有什么方法可以将存储库作为参数定义/传递给myservice,而不必将其定义为服务本身?
    • @Andy 你可以将服务定义为private,这意味着它们可以被注入(在YAML配置中)但不能使用-&gt;get()获取
    • 弃用警告:Symfony 2.6 起不再有 factory_servicefactory_method。现在应该这样做:stackoverflow.com/a/31807608/828366
    • 请注意,从 Symfony 3.0 开始,您应该为 some YAML configurations 使用引号。所以在这里你应该使用factory: ["@doctrine.orm.entity_manager", getRepository],否则你会收到一个漂亮的 ParseException。
    【解决方案5】:

    如果不想将每个存储库定义为服务,从版本 2.4 开始,您可以执行以下操作,(default 是实体管理器的名称):

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

    【讨论】:

    • 这在 XML 服务文件中看起来如何?
    • 这是基于表达式组件:symfony.com/doc/current/book/…
    • 使用 Symfony 2.7,我能够以更短的语法获得存储库:@=service('doctrine').getRepository('AppBundle:EntityX')
    • 这在 *Container.php" 中完美翻译为 "$this->get("doctrine")->getRepository("AppBundle:EntityX")",喜欢这个快捷方式!
    • @Jonny 这是 xml 版本:&lt;service id="image_manager" class="MyCompany\MainBundle\ImageManager"&gt; &lt;argument type="expression"&gt;service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')&lt;/argument&gt; &lt;/service&gt;
    【解决方案6】:

    我找到了这个link,这对我有用:

    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%
    

    【讨论】:

    • 弃用警告:自 Symfony 2.6 起不再有 factory_service 和 factory_method
    • 不会有任何默认工厂,但 Symfony 3.4 支持创建自己的工厂。
    猜你喜欢
    • 2017-12-05
    • 2018-08-15
    • 2020-06-06
    • 2018-01-23
    • 2015-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    相关资源
    最近更新 更多