Repository
An implementation of the repository pattern. This is an alternative to
Doctrine's standard EntityRepository. Unlike Doctrine's, our repository
implements Collection interface, so you can work with the repository like any
other implementation of Collection.
Why?
Why not? A repository is essentially a collection of entities. It makes sense to implement it as just another collection, having the same behavior as any other collection.
Your CitizenRepository will work practically the same way as
$country->getCitizens(), except that the former contains all citizens, while
the latter contains citizens of a specific country.
Your paginated view, batch processing, and other operation will work the same way for both collections. You can simply swap one with the other without changing your code.
Installation
composer require rekalogika/collections-orm
Creating a Repository
Repository interface:
use Rekalogika\Contracts\Collections\Repository;
/**
* @extends Repository<int,Citizen>
*/
interface CitizenRepository extends Repository
{
// you may wish to add custom methods here
}
Repository implementation:
use Rekalogika\Collections\ORM\AbstractRepository;
use Rekalogika\Collections\ORM\Configuration\RepositoryConfiguration;
/**
* @extends AbstractRepository<int,Citizen>
*/
class CitizenRepositoryImplementation extends AbstractRepository implements
CitizenRepository
{
public function __construct(ManagerRegistry $managerRegistry)
{
parent::__construct(
managerRegistry: $managerRegistry,
class: Citizen::class,
);
}
// you may wish to add custom methods here
}
Technically, it is not strictly required to create the interface for the repository. You can just create the implementation class. Creating the interface is a common practice in domain-driven design (DDD). The interface belongs to the domain layer, while the implementation belongs to the infrastructure layer. Other components of the application work with the interface, not the implementation directly.
The Minimal Flavor
If you want to use the minimal version of the repository, you can substitute:
RepositorywithMinimalRepositoryAbstractRepositorywithAbstractMinimalRepositoryRepositoryConfigurationwithMinimalRepositoryConfiguration
Convenience Methods
The base AbstractRepository class provides convenience methods to be called by
the methods in the concrete repository implementation:
getEntityManager()createQueryBuilder()getDoctrineRepository()createCriteriaRecollection()createCriteriaPageable()createQueryRecollection()createQueryPageable()
Migrating from Doctrine's Repository
Persisting an entity
use Doctrine\ORM\EntityManagerInterface;
/**
* @var EntityRepository $repository This is an implementation of the repository
* mentioned in this document.
*/
/** @var EntityManagerInterface $entityManager */
$entity = new Entity();
- $entityManager->persist($entity);
+ $repository->add($entity);
$entityManager->flush();
Retrieving an entity
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
/**
* @var EntityRepository $repository This is an implementation of the repository
* mentioned in this document.
*/
/** @var EntityManagerInterface $entityManager */
/** @var EntityRepository<Entity> $doctrineRepository */
- $entity = $entityManager->find(Entity::class, $id);
- // or
- $entity = $doctrineRepository->find($id);
+ $entity = $repository->get($id);
+ // alternative that throws an exception if the entity is not found:
+ $entity = $repository->fetch($id);
get() and fetch() methods also support an optional lock mode parameter if
you need it.
Removing an entity
use Doctrine\ORM\EntityManagerInterface;
/**
* @var EntityRepository $repository This is an implementation of the repository
* mentioned in this document.
*/
/** @var EntityManagerInterface $entityManager */
/** @var Entity $entity */
- $entityManager->remove($entity);
+ $repository->removeElement($entity);
$entityManager->flush();
Iterating All Entities
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
/**
* @var EntityRepository $repository This is an implementation of the repository
* mentioned in this document.
*/
/** @var EntityManagerInterface $entityManager */
/** @var EntityRepository<Entity> $doctrineRepository */
- $entities = $doctrineRepository->findAll();
- foreach ($entities as $entity) {
- // do something
- }
+ // non-minimal flavor only:
+ foreach ($repository as $entity) {
+ // do something
+ }
+
+ // all flavors, iterating in batches, should never trigger out-of-memory
+ // situation:
+ foreach ($repository->withItemsPerPage(1000)->getPages() as $page) {
+ foreach ($page as $entity) {
+ // do something
+ }
+ $entityManager->clear();
+ }
Coexisting with Doctrine's Standard Repository
If you no longer use the default Doctrine's repository, you can safely skip the
'repository' argument in your entity configuration, and remove Doctrine's
repository class to reduce confusion. The default Doctrine's repository is still
available by calling $entityManager->getRepository(Entity::class), it is just
you no longer have the option to add custom methods to it.
This implementation of repository sits above Doctrine's Entity Manager. The entity manager does not have the knowledge of the repository.
DatabaseSession Service as an Alternative to EntityManager
Doctrine's EntityManager is somewhat a god object. This package provides a
minimal alternative DatabaseSession that provides only the flush() and
clear() methods. With persistence and removal handled by the repository,
DatabaseSession will be sufficient for maybe 99% of use cases.
The idea is that you can disallow the use of EntityManager in controllers or
other frontend objects through architectural constraints. Then programmers (and
especially AI agents) are forced to use the repository for all object retrieval
and persistence. No more QueryBuilder, DQL, or transactions in controllers. If
they need a more complex query, they have to add a custom method to the
repository or move the logic to a service class. This will promote code reuse
and better organization of code.