Entity Repositories as a Services
EntityBundle provides possibility to define entity repositories as a Symfony DI container services. As a consequence developer can use entity repositories as regular services - e.g. inject them into other services or inject additional services into repositories.
To define entity repositories as a service, define the service with or without a class of an appropriate
repository, use service
oro_entity.abstract_repository as a parent and the pass entity class name as an argument.
To work properly the repository class must have the default constructor signature
public function __construct($em, Mapping\ClassMetadata $class).
Here is example of repository service definition:
services: oro_product.repository.product: class: Oro\Bundle\ProductBundle\Entity\Repository\ProductRepository parent: oro_entity.abstract_repository arguments: - 'Oro\Bundle\ProductBundle\Entity\Product'
This is repository for
Oro\Bundle\ProductBundle\Entity\Product entity, and there are several ways to get this
repository. You can get it just as a regular service from the container:
$productRepository = $this->container->get('oro_product.repository.product');
or get it via ManagerRegistry or DoctrineHelper:
$productRepository = $this->container->get('doctrine') ->getManagerForClass('Oro\Bundle\ProductBundle\Entity\Product') ->getRepository('Oro\Bundle\ProductBundle\Entity\Product'); $productRepository = $this->container->get('oro_entity.doctrine_helper') ->getEntityRepository('Oro\Bundle\ProductBundle\Entity\Product');
All these calls will return the same instance of the entity repository created via Symfony DI container.
Extension of repositories is a very common case for application customization. For example, after a new package was installed, the repository has to take into account additional conditions, such as filtering. The best practice for this case is using the repository service decoration.
Here is example of what repository decoration can look like:
services: oro_product.repository.product.new: class: Oro\Bundle\ProductBundle\Entity\Repository\NewProductRepository parent: oro_entity.abstract_repository decorates: oro_product.repository.product decoration_inner_name: oro_product.repository.product.original arguments: - 'Oro\Bundle\ProductBundle\Entity\Product' calls: - ['setDecoratedRepository', ['@oro_product.repository.product.original']]
Here, repository service oro_product.repository.product (class
is decorated with repository service oro_product.repository.product.new (class
Oro\Bundle\ProductBundle\Entity\Repository\NewProductRepository), and original repository is injected into decorator
via the setDecoratedRepository method. Now every time application requests the original repository (as a service
oro_product.repository.product or by entity class name
Oro\Bundle\ProductBundle\Entity\Product), an instance of
repository decorator will be returned instead of the original repository.
How It Works
Everything starts with abstract service for entity repositories:
services: oro_entity.abstract_repository: class: Doctrine\ORM\EntityRepository factory: ['@oro_entity.repository.factory', 'getDefaultRepository'] abstract: true
This abstract service is used to create repository instances using entity repository factory service. Also this service is used as a mark to collect all repository services.
The next important component is compiler pass
Oro\Bundle\EntityBundle\DependencyInjection\Compiler\EntityRepositoryCompilerPass. This compiler pass is used to
collect all repository services by their parent service and build a match between the entity class name (extracted from the
service argument) and appropriate service ID. An array that contains this match is injected into the entity repository
And the final important component is entity repository factory
Oro\Bundle\EntityBundle\ORM\Repository\EntityRepositoryFactory (represented by the service ID
oro_entity.repository.factory). This factory implements Doctrine entity repository interface
Doctrine\ORM\Repository\RepositoryFactory and it is injected into Doctrine configuration service
doctrine.orm.configuration instead of the default entity repository. Repository factory accepts Symfony DI container and
array with class names and service IDs as an arguments.
Repository factory has two methods to build repositories - getDefaultRepository and getRepository.
First method getDefaultRepository is called to build original instances of repositories and used in abstract repository as a factory method. The first argument of the method is entity class name (required), the second argument is custom repository class name (optional) and the third argument is entity manager (optional). Repository service class name is automatically passed as a second argument to this method if repository service has custom class name. Entity manager is passed only during the creation of not service repository.
Second method getRepository is an entry point to request a repository and it’s used by Doctrine entity managers to get instances of repositories. According to the repository factory interface is accepts entity manager and entity class name. This method is responsible for internal caching of repositories and actual repository creation requests. If entity class name exists in the array of repositories defined as a services then factory gets this repository from DI container, otherwise it calls method getDefaultRepository to get default instance of repository.