繁体   English   中英

Symfony - 在服务中注入原则存储库

[英]Symfony - inject doctrine repository in service

根据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 的解决方案非常有效。 如果您不使用自动装配,这里是服务定义:

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

当您使用 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 连接被重置,您将拥有对陈旧存储库的引用。 但恕我直言,这是我接受的风险,否则我将无法直接注入存储库..

检查参数是一个有效的类(使用 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

希望这有帮助

正确创建自定义存储库

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

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"

到目前为止,我在这里看到的解决方案还不错。 我从不同的角度看它。 所以我的解决方案允许您保持干净的存储库,sorta 强制执行一致的项目结构,并且您可以保持自动装配!

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

目标

我们希望拥有自动装配的 Repositories,我们希望尽可能保持它们的干净。 我们还希望它们超级易于使用。

问题

我们需要想办法告诉 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. 完成了! 要自动装配您的存储库,您需要做的就是扩展新的基本存储库类并将实体命名为与存储库相同的名称。 所以最终结果是这样的:

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

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

ServiceEntityRepository 的缺点是什么?

https://symfony.com/doc/current/doctrine/multiple_entity_managers.html

一个实体可以由多个实体管理者管理。 但是,当从自定义存储库中的 ServiceEntityRepository 扩展时,这会导致意外行为。 ServiceEntityRepository 始终使用为该实体配置的实体管理器。

为了解决这种情况,改为扩展 EntityRepository 并且不再依赖自动装配:

在自己的项目中,我已经看到使用:

$repository = $entityManager->getRepository(MyEntity:class)

$repository->_em不等于$entityManager (两者使用相同的连接),导致如下问题:

$entity = $entityManager->getRepository(MyEntity:class)->find($id);
$entityManager->refresh($entity); // throws 'entity is not managed'

这就是为什么使用$repository->_em获取实体并且使用$entityManager进行刷新(或持久化、刷新等)的原因。

这里描述了这个问题: https ://github.com/symfony/symfony-docs/issues/9878

所以...您不能依赖使用多个实体管理器的ServiceEntityRepository ,但是EntityRepository不允许自动装配,那么,怎么办?

我的两分钱(我相信这应该适用于所有情况):

手动设置类元数据(就像您需要在ServiceEntityManager的构造函数中做的那样),这样我就可以:

删除 services.yaml 中存储库的自动装配:

    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Repository,Tests,Kernel.php,Client}'

(您还可以在services.yaml中添加以下存储库)

并创建另一个/config/packages/repositories.yaml并添加:

my.entity.metadata:
    class: App\Entity\Metadata
    arguments: 
        $entityName: App\Entity\MyEntity

App\Repository\MyEntityRepository:
    arguments:
        [$class: my.entity.metadata]

现在你有了一个能够自动装配的EntityRepository 您可以在配置中创建一个 repositories.yaml 文件,并在您创建/编辑/删除存储库时保持更新。 不是最干净的,但应该可以。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM