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.
ЦЕЛЬ
Мы хотим, чтобы репозитории были автоматически подключены, и мы хотим, чтобы они были как можно более чистыми. Мы также хотим, чтобы они были очень простыми в использовании.
ПРОБЛЕМА
Нам нужно найти способ сообщить репозиторию о сущности, которую он должен использовать.
РЕШЕНИЕ
Решение простое и состоит из нескольких вещей:
- У нас есть собственный класс репозитория, который расширяет
Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository
учебный класс. - В нашем пользовательском классе есть
public string $entity
собственность на нем. Когда мы создаем наш новый репозиторий и расширяем наш собственный класс репозитория, у нас есть два варианта: в нашем новом репозитории мы можем просто указать на такой класс
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'
Затем в нашем новом классе расширения я знаю, где по умолчанию искать мои сущности (это обеспечивает некоторую согласованность).
ОЧЕНЬ ВАЖНАЯ ИНФОРМАЦИЯ - я предполагаю, что мы назовем репозитории и объекты одинаковыми именами, например:
Post
будет нашей сущностью иPostRepository
это наш репозиторий. Сразу отметим, что словоRepository
не обязательно. Если он есть, он будет удален.Некоторая умная логика создаст для вас пространства имен - я предполагаю, что вы будете следовать некоторым передовым методам, и все они будут согласованными.
Это сделано! Чтобы ваш репозиторий был автоматически подключен, все, что вам нужно сделать, это расширить ваш новый базовый класс репозитория и назвать 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 в конфигурации и обновлять его при создании/редактировании/удалении своих репозиториев. Не самый чистый, но он должен работать.