简体   繁体   English

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

[英]Symfony - inject doctrine repository in service

according to How to inject a repository into a service in Symfony2?根据How to inject a repository into a service in Symfony2? it's like就像是

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

but I get an Exception但我得到一个例外

Invalid service "acme.custom_repository": class "EntityManager5aa02de170f88_546a8d27f194334ee012bfe64f629947b07e4919__CG__\Doctrine\ORM\EntityManager" does not exist.无效服务“acme.custom_repository”:类“EntityManager5aa02de170f88_546a8d27f194334ee012bfe64f629947b07e4919__CG__\Doctrine\ORM\EntityManager”不存在。

How can I do this in Symfony 3.4?我怎样才能在 Symfony 3.4 中做到这一点?

update:更新:

EntityClass is actually a valid class FQCN (also used copy reference on phpstorm to be sure), just renamed it because a companies name is in it:). EntityClass 实际上是一个有效的类 FQCN(当然也使用了 phpstorm 上的复制引用),只是重命名了它,因为其中有一个公司名称:)。 updated it anyway.无论如何更新它。

solution解决方案

BlueM's solution works perfectly. BlueM 的解决方案非常有效。 In case you are not using autowiring here's the service defintion:如果您不使用自动装配,这里是服务定义:

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

As you are using Symfony 3.4, you can use a much simpler approach, using ServiceEntityRepository .当您使用 Symfony 3.4 时,您可以使用更简单的方法,使用ServiceEntityRepository Simply implement your repository, let it extend class ServiceEntityRepository and you can simply inject it.只需实现您的存储库,让它extendServiceEntityRepository ,您就可以简单地注入它。 (At least when using autowiring – I haven't used this with classic DI configuration, but would assume it should also work.) (至少在使用自动装配时——我没有在经典 DI 配置中使用它,但假设它也应该工作。)

In other words:换句话说:

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

Now, without any DI configuration , you can inject the repository wherever you want, including controller methods.现在,无需任何 DI 配置,您就可以在任何地方注入存储库,包括控制器方法。

One caveat (which equally applies to the way you try to inject the repository): if the Doctrine connection is reset, you will have a reference to a stale repository.一个警告(同样适用于您尝试注入存储库的方式):如果 Doctrine 连接被重置,您将拥有对陈旧存储库的引用。 But IMHO, this is a risk I accept, as otherwise I won't be able to inject the repository directly..但恕我直言,这是我接受的风险,否则我将无法直接注入存储库..

Check the arguments is a valid class (with FQCN or with a bundle simplification) as example:检查参数是一个有效的类(使用 FQCN 或使用包简化)作为示例:

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

or

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

Hope this help希望这有帮助

Create the custom repository properly正确创建自定义存储库

First, you need to create the repository custom class that extends the default repository from doctrine:首先,您需要创建存储库自定义类,该类从学说扩展默认存储库:

use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository
{
   // your own methods
}

Then you need this annotation in the entity class:那么你需要在实体类中使用这个注解:

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

Then you define the repository in the .yml file:然后在 .yml 文件中定义存储库:

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

Make sure that in the definition of your repository class points to your custom repository class and not to Doctrine\\ORM\\EntityRepository .确保在您的存储库class的定义中指向您的自定义存储库类而不是Doctrine\\ORM\\EntityRepository

Inject custom services into your custom repository:将自定义服务注入您的自定义存储库:

On your custom repository create custom setters for your services在您的自定义存储库上为您的服务创建自定义设置器

use Doctrine\\ORM\\EntityRepository;使用 Doctrine\\ORM\\EntityRepository;

class UserRepository extends EntityRepository
{
    protected $paginator;

    public function setPaginator(PaginatorInterface $paginator)
    {
        $this->paginator = $paginator;
    }
}

Then inject them like this:然后像这样注入它们:

custom_repository:
        class: MyDomain\Model\UserRepository

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

Inject your repository into a service:将您的存储库注入服务:

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

Solutions I could see here so far are not bad.到目前为止,我在这里看到的解决方案还不错。 I looked at it from a different angle.我从不同的角度看它。 So my solution allows you to keep clean repositories, sorta enforces consistent project structure and you get to keep autowiring!所以我的解决方案允许您保持干净的存储库,sorta 强制执行一致的项目结构,并且您可以保持自动装配!

This is how I would solve it in Symfony 5.这就是我在 Symfony 5 中解决它的方法。

GOAL目标

We want to have autowired Repositories and we want to keep them as clean as possible.我们希望拥有自动装配的 Repositories,我们希望尽可能保持它们的干净。 We also want them to be super easy to use.我们还希望它们超级易于使用。

PROBLEM问题

We need to figure out a way to tell Repository about the entity it should use.我们需要想办法告诉 Repository 它应该使用的实体。

SOLUTION解决方案

The solution is simple and consists of a few things:解决方案很简单,包括以下几点:

  1. We have custom Repository class which extends Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityRepository class.我们有自定义 Repository 类,它扩展了Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityRepository类。
  2. Our custom class has public string $entity property on it.我们的自定义类具有public string $entity属性。
  3. When we create our new repository and extend our custom repository class we have two choices: on our new repository we can just point to the class like this当我们创建我们的新存储库并扩展我们的自定义存储库类时,我们有两个选择:在我们的新存储库中,我们可以像这样指向这个类

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

or we could omit that property and let our new base Repository class find it automatically!或者我们可以省略该属性并让我们的新基础 Repository 类自动找到它! (More about that later.) (稍后会详细介绍。)

CODE代码

So let's start with the code and then I will explain it:那么让我们从代码开始,然后我将解释它:

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

Ok, so what is happening here?好的,那么这里发生了什么? I have bound some values to the container in services.yml :我已将一些值绑定到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. Then in our new extension class, I know where by default to look for my Entities (this enforces some consistency).然后在我们的新扩展类中,我知道默认情况下在哪里寻找我的实体(这会强制执行一些一致性)。

  2. VERY IMPORTANT BIT - I assume that we will name Repositories and Entities with exactly the same so for example: Post will be our Entity and PostRepository is our repository.非常重要的一点 - 我假设我们将以完全相同的方式命名存储库和实体,例如: Post将是我们的实体,而PostRepository是我们的存储库。 Just note that the word Repository is not obligatory.请注意, Repository这个词不是强制性的。 If it is there it will be removed.如果存在,它将被删除。

  3. Some clever logic will create namespaces for you - I assume that you will follow some good practices and that it will all be consistent.一些聪明的逻辑会为你创建命名空间——我假设你会遵循一些好的实践,并且它们都是一致的。

  4. It's done!完成了! To have your repository autowired all you need to do is extend your new base repository class and name Entity the same as the repository.要自动装配您的存储库,您需要做的就是扩展新的基本存储库类并将实体命名为与存储库相同的名称。 so End result looks like this:所以最终结果是这样的:

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

It is CLEAN, AUTOWIRED, SUPER EASY AND QUICK TO CREATE!它是干净的、自动布线的、超级容易和快速创建的!

What about the drawbacks about the ServiceEntityRepository? ServiceEntityRepository 的缺点是什么?

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

One entity can be managed by more than one entity manager.一个实体可以由多个实体管理者管理。 This however results in unexpected behavior when extending from ServiceEntityRepository in your custom repository.但是,当从自定义存储库中的 ServiceEntityRepository 扩展时,这会导致意外行为。 The ServiceEntityRepository always uses the configured entity manager for that entity. ServiceEntityRepository 始终使用为该实体配置的实体管理器。

In order to fix this situation, extend EntityRepository instead and no longer rely on autowiring:为了解决这种情况,改为扩展 EntityRepository 并且不再依赖自动装配:

In an own project I've seen that using:在自己的项目中,我已经看到使用:

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

The $repository->_em is not equals to $entityManager (with both using the same connection), causing problems like: $repository->_em不等于$entityManager (两者使用相同的连接),导致如下问题:

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

That's why the entity is fetched with $repository->_em and the refresh (or persist, flush, etc.) is using $entityManager .这就是为什么使用$repository->_em获取实体并且使用$entityManager进行刷新(或持久化、刷新等)的原因。

This problem is described here: https://github.com/symfony/symfony-docs/issues/9878这里描述了这个问题: https ://github.com/symfony/symfony-docs/issues/9878

So... You can't rely in ServiceEntityRepository using multiple entity managers, but the EntityRepository doesn't allow autowire, so, what?所以...您不能依赖使用多个实体管理器的ServiceEntityRepository ,但是EntityRepository不允许自动装配,那么,怎么办?

My two cents (I believe this should be works in every scenario):我的两分钱(我相信这应该适用于所有情况):

Manually set the class metadata (something like you need to do in the constructor of the ServiceEntityManager ), so I can:手动设置类元数据(就像您需要在ServiceEntityManager的构造函数中做的那样),这样我就可以:

Remove the autowire of repositories in services.yaml:删除 services.yaml 中存储库的自动装配:

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

(you also add the repositories below in services.yaml ) (您还可以在services.yaml中添加以下存储库)

And create another /config/packages/repositories.yaml and add:并创建另一个/config/packages/repositories.yaml并添加:

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

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

Now you have a EntityRepository that is capable of being autowireable.现在你有了一个能够自动装配的EntityRepository You can make a repositories.yaml file in the config and keep updated when you create/edit/delete your repositories.您可以在配置中创建一个 repositories.yaml 文件,并在您创建/编辑/删除存储库时保持更新。 Is not cleanest but it should be works.不是最干净的,但应该可以。

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

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