【问题标题】:Symfony - inject doctrine repository in serviceSymfony - 在服务中注入学说存储库
【发布时间】:2018-08-15 23:17:18
【问题描述】:

根据How to inject a repository into a service in Symfony2? 就像

acme.custom_repository:
    class: Doctrine\ORM\EntityRepository
    factory: ['@doctrine.orm.entity_manager', getRepository]
    arguments:
        - 'Acme\FileBundle\Model\File'

但我得到一个异常

无效的服务“acme.custom_repository”:类 “EntityManager5aa02de170f88_546a8d27f194334ee012bfe64f629947b07e4919__CG__\Doctrine\ORM\EntityManager” 不存在。

如何在 Symfony 3.4 中做到这一点?

更新:

EntityClass 实际上是一个有效的 FQCN 类(肯定也使用了 phpstorm 上的复制引用),只是将其重命名,因为其中包含公司名称 :)。无论如何更新它。

解决方案

BlueM 的solution 完美运行。 如果您不使用自动装配,这里是服务定义:

Acme\AcmeBundle\Respository\MyEntityRepository:
    arguments:
        - '@Doctrine\Common\Persistence\ManagerRegistry'
        - Acme\AcmeBundle\Model\MyEntity # '%my_entity_class_parameter%'

【问题讨论】:

标签: php symfony doctrine-orm symfony-3.4


【解决方案1】:

到目前为止,我在这里看到的解决方案还不错。我从另一个角度看它。所以我的解决方案允许你保持干净的存储库,排序强制一致的项目结构,你可以继续自动装配!

这就是我在 Symfony 5 中解决它的方法。

目标

我们希望拥有自动装配的存储库,并且希望它们尽可能保持干净。我们还希望它们超级好用。

问题

我们需要想办法告诉 Repository 它应该使用的实体。

解决方案

解决方案很简单,包括几件事:

  1. 我们有自定义 Repository 类,它扩展了 Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository 类。
  2. 我们的自定义类有 public string $entity 属性。
  3. 当我们创建新的存储库并扩展我们的自定义存储库类时,我们有两个选择:在我们的新存储库上,我们可以像这样指向类

    namespace App\Database\Repository\Post;
    
    use App\Database\Repository\Repository;
    use App\Entity\Blog\Post;
    
    /**
     * Class PostRepository
     * @package App\Database\Repository
     */
    class PostRepository extends Repository
    {
        public string $entity = Post::class;
    
        public function test()
        {
            dd(99999, $this->getEntityName());
        }
    }
    

或者我们可以省略该属性,让我们的新基础 Repository 类自动找到它! (稍后会详细介绍。)

代码

那么让我们从代码开始,然后我会解释它:

<?php

namespace App\Database\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Laminas\Code\Reflection\ClassReflection;
use Symfony\Component\Finder\Finder;

/**
 * Class Repository
 * @package App\Database\Repository
 */
abstract class Repository extends ServiceEntityRepository
{
    /** @var string  */
    private const REPOSITORY_FILE = 'repository';

    /** @var string */
    public string $entity = '';
    /** @var string */
    public string $defaultEntitiesLocation;
    /** @var string */
    public string $defaultEntitiesNamespace;

    /**
     * Repository constructor.
     *
     * @param ManagerRegistry $registry
     * @param $defaultEntitiesLocation
     * @param $defaultEntitiesNamespace
     * @throws \Exception
     */
    public function __construct(
        ManagerRegistry $registry,
        $defaultEntitiesLocation,
        $defaultEntitiesNamespace
    ) {
        $this->defaultEntitiesLocation = $defaultEntitiesLocation;
        $this->defaultEntitiesNamespace = $defaultEntitiesNamespace;
        $this->findEntities();
        parent::__construct($registry, $this->entity);
    }

    /**
     * Find entities.
     *
     * @return bool
     * @throws \ReflectionException
     */
    public function findEntities()
    {
        if (class_exists($this->entity)) {
            return true;
        }
        $repositoryReflection = (new ClassReflection($this));
        $repositoryName = strtolower(preg_replace('/Repository/', '', $repositoryReflection->getShortName()));
        $finder = new Finder();
        if ($finder->files()->in($this->defaultEntitiesLocation)->hasResults()) {
            foreach ($finder as $file) {
                if (strtolower($file->getFilenameWithoutExtension()) === $repositoryName) {
                    if (!empty($this->entity)) {
                        throw new \Exception('Entity can\'t be matched automatically. It looks like there is' .
                            ' more than one ' . $file->getFilenameWithoutExtension() . ' entity. Please use $entity 
                            property on your repository to provide entity you want to use.');
                    }
                    $namespacePart = preg_replace(
                        '#' . $this->defaultEntitiesLocation . '#',
                        '',
                        $file->getPath() . '/' . $file->getFilenameWithoutExtension()
                    );
                    $this->entity = $this->defaultEntitiesNamespace . preg_replace('#/#', '\\', $namespacePart);
                }
            }
        }
    }
}

好的,那么这里发生了什么?我已经将一些值绑定到services.yml 中的容器:

 services:
        # default configuration for services in *this* file
        _defaults:
            autowire: true      # Automatically injects dependencies in your services.
            autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
            bind:
                $defaultEntitiesLocation: '%kernel.project_dir%/src/Entity'
                $defaultEntitiesNamespace: 'App\Entity'
  1. 然后在我们的新扩展类中,我知道默认情况下在哪里查找我的实体(这会强制保持一定的一致性)。

  2. 非常重要的一点 - 我假设我们将使用完全相同的名称命名存储库和实体,例如:Post 将是我们的实体,PostRepository 是我们的存储库。请注意,Repository 这个词不是强制性的。如果它在那里,它将被删除。

  3. 一些聪明的逻辑会为你创建命名空间——我假设你会遵循一些好的实践并且它们都是一致的。

  4. 完成了!要让您的存储库自动装配,您需要做的就是扩展新的基本存储库类并将 Entity 命名为与存储库相同。所以最终结果如下所示:

    <?php
    
    namespace App\Database\Repository\Post;
    
    use App\Database\Repository\Repository;
    use App\Entity\Blog\Post;
    
    /**
     * Class PostRepository
     * @package App\Database\Repository
     */
    class PostRepository extends Repository
    {
        public function test()
        {
            dd(99999, $this->getEntityName());
        }
    }
    

它干净、自动连线、超级容易和快速创建!

【讨论】:

    【解决方案2】:

    当您使用 Symfony 3.4 时,您可以使用更简单的方法,使用 ServiceEntityRepository。只需实现您的存储库,让它extendServiceEntityRepository 并且您可以简单地注入它。 (至少在使用自动装配时——我没有在经典的 DI 配置中使用它,但我认为它应该也可以工作。)

    换句话说:

    namespace App\Repository;
    
    use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
    use Doctrine\Common\Persistence\ManagerRegistry;
    
    class ExampleRepository extends ServiceEntityRepository
    {
        /**
         * @param ManagerRegistry $managerRegistry
         */
        public function __construct(ManagerRegistry $managerRegistry)
        {
            parent::__construct($managerRegistry, YourEntity::class);
        }
    }
    

    现在,无需任何 DI 配置,您可以在任意位置注入存储库,包括控制器方法。

    一个警告(这同样适用于您尝试注入存储库的方式):如果 Doctrine 连接被重置,您将获得对陈旧存储库的引用。但是恕我直言,这是我接受的风险,否则我将无法直接注入存储库..

    【讨论】:

    • 如果您碰巧使用多个实体管理器,您还需要小心一点。如果同一个实体类属于多个管理器,那么您无法预测最终会使用哪个管理器。
    • 我现在如何使用存储库?你能扩展这个答案吗?
    • 典型用法:构造函数注入。只需在代码中定义“public function __construct(ExampleRepository $repo) { }”,无需配置即可解决依赖关系。
    【解决方案3】:

    正确创建自定义存储库

    首先,您需要创建存储库自定义类,该类扩展自学说的默认存储库:

    use Doctrine\ORM\EntityRepository;
    
    class UserRepository extends EntityRepository
    {
       // your own methods
    }
    

    那么在实体类中需要这个注解:

    /**
     * @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
     */
    

    然后在 .yml 文件中定义存储库:

    custom_repository:
            class: MyDomain\Model\UserRepository
            factory: ["@doctrine", getRepository]
            arguments:
              - Acme\FileBundle\Model\File
    

    确保在您的存储库的定义中class 指向您的自定义存储库类,而不是Doctrine\ORM\EntityRepository

    将自定义服务注入您的自定义存储库:

    在您的自定义存储库上为您的服务创建自定义设置器

    使用 Doctrine\ORM\EntityRepository;

    class UserRepository extends EntityRepository
    {
        protected $paginator;
    
        public function setPaginator(PaginatorInterface $paginator)
        {
            $this->paginator = $paginator;
        }
    }
    

    然后像这样注入它们:

    custom_repository:
            class: MyDomain\Model\UserRepository
    
            factory: ["@doctrine", getRepository]
            arguments:
              - Acme\FileBundle\Model\File
            calls:
              - [setPaginator, ['@knp_paginator']]
    

    将您的存储库注入服务:

    my_custom_service:
        class: Acme\FileBundle\Services\CustomService
        arguments:
            - "@custom_repository"
    

    【讨论】:

      【解决方案4】:

      检查参数是一个有效的类(使用 FQCN 或使用捆绑简化)例如:

      acme.custom_repository:
          class: Doctrine\ORM\EntityRepository
          factory: 
              - '@doctrine.orm.entity_manager'
              - getRepository
          arguments:
              - Acme\MainBundle\Entity\MyEntity
      

      acme.custom_repository:
          class: Doctrine\ORM\EntityRepository
          factory: 
              - '@doctrine.orm.entity_manager'
              - getRepository
          arguments:
              - AcmeMainBundle:MyEntity
      

      希望有帮助

      【讨论】:

        猜你喜欢
        • 2017-12-05
        • 2012-08-26
        • 1970-01-01
        • 2011-06-16
        • 2018-01-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-06
        相关资源
        最近更新 更多