Symfony - внедрить хранилище доктрин в сервис

В соответствии с Как внедрить хранилище в службу в 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%'

5 ответов

Решение

Поскольку вы используете Symfony 3.4, вы можете использовать гораздо более простой подход, используя ServiceEntityRepository, Просто внедри свой репозиторий, пусть extend учебный класс ServiceEntityRepository и вы можете просто ввести его. (По крайней мере, при использовании автоматической проводки - я не использовал это с классической конфигурацией 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 будет сброшено, у вас будет ссылка на устаревшее хранилище. Но ИМХО, это риск, который я принимаю, так как в противном случае я не смогу внедрить репозиторий напрямую.

Создайте собственный репозиторий правильно

Во-первых, вам нужно создать собственный класс хранилища, который расширяет хранилище по умолчанию из доктрины:

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"

Проверьте аргументы допустимого класса (с 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

Надеюсь это поможет

Решения, которые я мог видеть здесь, неплохие. Я посмотрел на это под другим углом. Итак, мое решение позволяет вам поддерживать чистые репозитории, sorta обеспечивает согласованную структуру проекта, и вы можете продолжать автоматическое подключение!

Вот как я бы решил это в Symfony 5.

ЦЕЛЬ

Мы хотим, чтобы репозитории были автоматически подключены, и мы хотим, чтобы они были как можно более чистыми. Мы также хотим, чтобы они были очень простыми в использовании.

ПРОБЛЕМА

Нам нужно найти способ сообщить репозиторию о сущности, которую он должен использовать.

РЕШЕНИЕ

Решение простое и состоит из нескольких вещей:

  1. У нас есть собственный класс репозитория, который расширяет 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());
        }
    }
    

Он ЧИСТЫЙ, С АВТОПРОВОДОМ, СУПЕР ПРОСТО И БЫСТРО СОЗДАТЬ!

Как насчет недостатков ServiceEntityRepository?

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

Одна сущность может управляться более чем одним менеджером сущности. Однако это приводит к неожиданному поведению при расширении из ServiceEntityRepository в вашем пользовательском репозитории. ServiceEntityRepository всегда использует настроенный диспетчер сущностей для этой сущности.

Чтобы исправить эту ситуацию, вместо этого расширьте EntityRepository и больше не полагайтесь на автоматическое связывание:

В собственном проекте я видел, что с помощью:

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

Не равно (оба используют одно и то же соединение), вызывая такие проблемы, как:

      $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с помощью нескольких менеджеров сущностей, но не позволяет автоподключение, и что?

Мои два цента (я считаю, что это должно работать в каждом сценарии):

Вручную установите метаданные класса (что-то вроде того, что вам нужно сделать в конструкторе класса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 в конфигурации и обновлять его при создании/редактировании/удалении своих репозиториев. Не самый чистый, но он должен работать.

Другие вопросы по тегам