symfony - generate - Inyectar dependencia en el repositorio de la entidad
generate entity symfony (6)
Acabo de definir mi propia clase RepositoryFactory
- Cree la clase RepositoryFactory y defina el servicio, por ejemplo
my_service.orm_repository.robo_repository_factory
, con include @service_container injection Y agregue control y configure el servicio contenedor, por ejemplo:
private function createRepository(EntityManagerInterface $entityManager, $entityName) { /* @var $metadata /Doctrine/ORM/Mapping/ClassMetadata */ $metadata = $entityManager->getClassMetadata($entityName); $repositoryClassName = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); $result = new $repositoryClassName($entityManager, $metadata); if ($result instanceof ContainerAwareInterface) { $result->setContainer($this->container); } return $result; }
Crear clase de compilador
public function process(ContainerBuilder $container) { $def = $container->getDefinition(''doctrine.orm.configuration''); $def->addMethodCall( ''setRepositoryFactory'', [new Reference(''robo_doctrine.orm_repository.robo_repository_factory'')] ); }
Después de eso, cualquier
EntityRepository
conContainerAwareInterface
tiene @service_container
¿Hay una forma simple de insertar una dependencia en cada instancia de repositorio en Doctrine2?
He intentado escuchar el evento loadClassMetadata
y el uso de setter injection en el repositorio, pero esto naturalmente dio como resultado un ciclo infinito como llamar a getRepository
en el evento que desencadenó el mismo evento.
Después de echar un vistazo al método Doctrine/ORM/EntityManager::getRepository
, parece que los repositorios no están utilizando la inyección de dependencia, sino que se instancian en el nivel de función:
public function getRepository($entityName)
{
$entityName = ltrim($entityName, ''//');
if (isset($this->repositories[$entityName])) {
return $this->repositories[$entityName];
}
$metadata = $this->getClassMetadata($entityName);
$customRepositoryClassName = $metadata->customRepositoryClassName;
if ($customRepositoryClassName !== null) {
$repository = new $customRepositoryClassName($this, $metadata);
} else {
$repository = new EntityRepository($this, $metadata);
}
$this->repositories[$entityName] = $repository;
return $repository;
}
Algunas ideas ?
El problema es que las clases de repositorio no son parte de la base de código de Symfony2 ya que son parte de Doctrine2, por lo que no aprovechan el DIC; esta es la razón por la cual no se puede inyectar en un solo lugar para todos los repositorios.
Te aconsejaría que uses un enfoque diferente. Por ejemplo, puede crear una capa de servicio en la parte superior de los repositorios e inyectar la clase que desea a través de una fábrica en esa capa.
De lo contrario, también podría definir repositorios como servicios de esta manera:
<service id="your_namespace.repository.repos_name"
class="%your_namespace.repository.repos_name%"
factory-service="doctrine" factory-method="getRepository">
<argument>entity_name</argument>
<argument>entity_manager_name</argument>
<call method="yourSetter">
<argument>your_argument</argument>
</call>
</service>
Una solución que podría centralizar la llamada al método set es escribir una etiqueta DIC y un pase de compilación para manejarlo y etiquetar todos los servicios del repositorio.
Esta es una versión de YAML de la respuesta de Aldo, en caso de que esté utilizando configuraciones de YAML en lugar de XML
your_namespace.repository.repos_name:
class: %your_namespace.repository.repos_name%
factory: ["@doctrine", getRepository]
arguments:
- entity_name
- entity_manager_name
calls:
- [setContainer, ["@service_container"]]
Y antes de la versión 2.8:
your_namespace.repository.repos_name:
class: %your_namespace.repository.repos_name%
factory_service: doctrine
factory_method: getRepository
arguments:
- entity_name
- entity_manager_name
calls:
- [setContainer, [@service_container]]
Además, como una nota, entity_manager_name es un parámetro opcional. Quiero el valor predeterminado para mi uso particular, así que lo dejé en blanco (solo en caso de que cambie el nombre del administrador predeterminado).
Puedes crear tu propio DefaultRepository extends EntityRepository
, construirlo con todas las dependencias que necesites y luego establecerlo como Repository
defecto con:
doctrine:
orm:
entity_managers:
default:
default_repository_class: AppBundle/ORM/DefaultRepository
Si usa un EntityManager personalizado, puede anular el método getRepository
. Como esto no implica el evento loadClassMetadata
, no se ejecutará en un bucle infinito.
Primero tendría que pasar la dependencia a su EntityManager personalizado, y luego lo pasaría al objeto del repositorio usando setter injection.
Respondí cómo usar un EntityManager personalizado here , pero replicaré la respuesta a continuación:
1 - Anule el parámetro doctrine.orm.entity_manager.class
para que apunte a su administrador de entidades personalizado (que debe extender Doctrine/ORM/EntityManager
).
2 - Su administrador de entidad personalizado debe anular el método de create
para que devuelva una instancia de su clase. Vea mi ejemplo a continuación y observe la última línea con respecto a MyEntityManager
:
public static function create($conn, Configuration $config, EventManager $eventManager = null) {
if (!$config->getMetadataDriverImpl()) {
throw ORMException::missingMappingDriverImpl();
}
if (is_array($conn)) {
$conn = /Doctrine/DBAL/DriverManager::getConnection($conn, $config, ($eventManager ? : new EventManager()));
} else if ($conn instanceof Connection) {
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
} else {
throw new /InvalidArgumentException("Invalid argument: " . $conn);
}
// This is where you return an instance of your custom class!
return new MyEntityManager($conn, $config, $conn->getEventManager());
}
También necesitarás use
lo siguiente en tu clase:
use Doctrine/ORM/EntityManager;
use Doctrine/ORM/Configuration;
use Doctrine/ORM/ORMException;
use Doctrine/Common/EventManager;
use Doctrine/DBAL/Connection;
Editar
Como el administrador de entidades predeterminado se crea a partir del método create
, no puede simplemente insertar un servicio en él. Pero como está creando un administrador de entidades personalizado, puede conectarlo al contenedor de servicios e inyectar las dependencias que necesite.
Luego, desde el método getRepository
puedes hacer algo como
$repository->setFoo($this->foo)
. Es un ejemplo muy simple: es posible que desee verificar primero si $repository
tiene un método setFoo
antes de llamarlo. La implementación depende de usted, pero esto muestra cómo usar la inyección setter para un repositorio.
Desde Symfony 3.3+ y 2017 , puede hacer uso de los servicios.
En lugar de otras soluciones propuestas aquí que conducen a:
- piratear la fábrica de repositorios
- haciendo la configuración del servicio en YAML
- y crear un código repetitivo que lo perseguirá más tarde
Puedes hacerlo...
Clean Way - Dependencia a través de la inyección de constructores como en cualquier otro servicio
<?php declare(strict_types=1);
namespace App/Repository;
use App/Entity/Post;
use Doctrine/ORM/EntityManager;
use Doctrine/ORM/EntityRepository;
final class PostRepository
{
/**
* @var EntityRepository
*/
private $repository;
/**
* @var YourOwnDependency
*/
private $yourOwnDependency;
public function __construct(YourOwnDependency $YourOwnDependency, EntityManager $entityManager)
{
$this->repository = $entityManager->getRepository(Post::class);
$this->yourOwnDependency = $yourOwnDependency
}
}
Lea más en el post
Puede leer un tutorial más detallado con ejemplos de código claros en Cómo usar el repositorio con Doctrine como servicio en la publicación de Symfony .