setono / doctrine-orm-batcher
A library for processing large collections in Doctrine
Installs: 498 185
Dependents: 6
Suggesters: 0
Security: 0
Stars: 28
Watchers: 3
Forks: 7
Open Issues: 7
Requires
- php: >=7.4
- doctrine/collections: ^1.6 || ^2.0
- doctrine/orm: ^2.8 || ^3.0
- doctrine/persistence: ^1.3 || ^2.1 || ^3.0
- symfony/property-access: ^5.4 || ^6.0
- webmozart/assert: ^1.10
Requires (Dev)
- doctrine/annotations: ^2.0
- doctrine/data-fixtures: ^1.5
- phpunit/phpunit: ^9.5.10
- psalm/plugin-phpunit: ^0.19.0
- setono/code-quality-pack: ^2.1.3
- symfony/cache: ^5.4 || ^6.0
- weirdan/doctrine-psalm-plugin: ^2.0
- dev-master / 1.0.x-dev
- 0.7.x-dev
- 0.6.x-dev
- v0.6.7
- v0.6.6
- v0.6.5
- v0.6.4
- v0.6.3
- v0.6.2
- v0.6.1
- v0.6.0
- v0.5.3
- v0.5.2
- v0.5.1
- v0.5.0
- v0.4.1
- v0.4.0
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.0
- v0.1.1
- v0.1.0
- dev-renovate/configure
- dev-dependabot/composer/setono/code-quality-pack-tw-2.4.0
- dev-dependabot/composer/phpunit/phpunit-tw-9.5.28
- dev-dependabot/composer/weirdan/doctrine-psalm-plugin-tw-2.8
- dev-dependabot/composer/psalm/plugin-phpunit-tw-0.18.4
- dev-dependabot/composer/symfony/cache-tw-5.4
- dev-rewrite
This package is auto-updated.
Last update: 2024-10-25 07:12:04 UTC
README
Use this library when you need to process large amounts of entities and maybe in an asynchronous way.
Why do we need this library? Why not just use a paginator library like Pagerfanta or normal batch processing in Doctrine?
Well, because MySQL is not very good with LIMIT and OFFSET when the tables become too large. As for Doctrine batch processing capabilities the difference is that this library is very opinionated. It will work very well in a message based architecture where large processing will likely be done in an asynchronous way.
How does it work then? It uses the seek method to paginate results instead.
Installation
$ composer require setono/doctrine-orm-batcher
Usage
There are two ways to get results: Getting a range of ids or getting a collection (either of ids or entities).
Range of ids
A range is a lower and upper bound of ids. This is typically intended to be used in an asynchronous environment where you will dispatch a message with the lower and upper bounds so that the consumer of that message will be able to easily fetch the respective entities based on these bounds.
Example
You want to process all your Product
entities. A query builder for that would look like:
<?php use Doctrine\ORM\EntityManagerInterface; /** @var EntityManagerInterface $em */ $qb = $em->createQueryBuilder(); $qb->select('o')->from(Product::class, 'o'); # OR even simpler: # $qb = $productRepository->createQueryBuilder('o');
Now inject that query builder into the id range batcher and dispatch a message:
<?php use Setono\DoctrineORMBatcher\Batch\RangeBatch; use Setono\DoctrineORMBatcher\Batcher\Collection\ObjectCollectionBatcher; use Setono\DoctrineORMBatcher\Batcher\Collection\IdCollectionBatcher; use Setono\DoctrineORMBatcher\Batcher\Range\NaiveIdRangeBatcher; use Setono\DoctrineORMBatcher\Batcher\Range\IdRangeBatcher; use Setono\DoctrineORMBatcher\Factory\BatcherFactory; class ProcessProductBatchMessage { private $batch; public function __construct(RangeBatch $batch) { $this->batch = $batch; } public function getBatch(): RangeBatch { return $this->batch; } } $factory = new BatcherFactory( ObjectCollectionBatcher::class, IdCollectionBatcher::class, NaiveIdRangeBatcher::class, IdRangeBatcher::class ); $idRangeBatcher = $factory->createIdRangeBatcher($qb); /** @var RangeBatch[] $batches */ $batches = $idRangeBatcher->getBatches(50); foreach ($batches as $batch) { $commandBus->dispatch(new ProcessProductBatchMessage($batch)); }
Then sometime somewhere a consumer will receive that message and process the products:
<?php use Setono\DoctrineORMBatcher\Query\QueryRebuilderInterface; class ProcessProductBatchMessageHandler { public function __invoke(ProcessProductBatchMessage $message) { /** @var QueryRebuilderInterface $queryRebuilder */ $q = $queryRebuilder->rebuild($message->getBatch()); $products = $q->getResult(); foreach ($products as $product) { // process $product } } }
This approach is extremely fast, but if you have complex queries it may be easier to use the collection batchers.
Collection of ids
Should be used for async handling of sets that selected with complex queries.
Example
You want to process only enabled Product
entities.
<?php use Doctrine\ORM\EntityManagerInterface; use Setono\DoctrineORMBatcher\Factory\BatcherFactory; use Setono\DoctrineORMBatcher\Batch\CollectionBatch; use Setono\DoctrineORMBatcher\Batcher\Collection\ObjectCollectionBatcher; use Setono\DoctrineORMBatcher\Batcher\Collection\IdCollectionBatcher; use Setono\DoctrineORMBatcher\Batcher\Range\NaiveIdRangeBatcher; use Setono\DoctrineORMBatcher\Batcher\Range\IdRangeBatcher; class ProcessEnabledProductBatchMessage { /** @var CollectionBatch */ private $batch; public function __construct(CollectionBatch $batch) { $this->batch = $batch; } public function getBatch(): CollectionBatch { return $this->batch; } } /** @var EntityManagerInterface $em */ $qb = $em->createQueryBuilder(); $qb->select('o') ->from(Product::class, 'o') ->where('o.enabled = 1') ; $factory = new BatcherFactory( ObjectCollectionBatcher::class, IdCollectionBatcher::class, NaiveIdRangeBatcher::class, IdRangeBatcher::class ); $idCollectionBatcher = $factory->createIdCollectionBatcher($qb); /** @var CollectionBatch[] $batches */ $batches = $idCollectionBatcher->getBatches(50); foreach ($batches as $batch) { $commandBus->dispatch(new ProcessEnabledProductBatchMessage($batch)); }
Then sometime somewhere a consumer will receive that message and process the products:
<?php use Setono\DoctrineORMBatcher\Query\QueryRebuilderInterface; class ProcessProductBatchMessageHandler { public function __invoke(ProcessEnabledProductBatchMessage $message) { /** @var QueryRebuilderInterface $queryRebuilder */ $q = $queryRebuilder->rebuild($message->getBatch()); $products = $q->getResult(); foreach ($products as $product) { // process $product } } }
Collection of objects
Should be used for immediate handing objects that selected with complex queries.
Example
You want to immediately process only enabled Product
entities.
<?php use Doctrine\ORM\EntityManagerInterface; use Setono\DoctrineORMBatcher\Factory\BatcherFactory; use Setono\DoctrineORMBatcher\Batch\CollectionBatch; use Setono\DoctrineORMBatcher\Batcher\Collection\ObjectCollectionBatcher; use Setono\DoctrineORMBatcher\Batcher\Collection\IdCollectionBatcher; use Setono\DoctrineORMBatcher\Batcher\Range\NaiveIdRangeBatcher; use Setono\DoctrineORMBatcher\Batcher\Range\IdRangeBatcher; /** @var EntityManagerInterface $em */ $qb = $em->createQueryBuilder(); $qb->select('o') ->from(Product::class, 'o') ->where('o.enabled = 1') ; $factory = new BatcherFactory( ObjectCollectionBatcher::class, IdCollectionBatcher::class, NaiveIdRangeBatcher::class, IdRangeBatcher::class ); $collectionBatcher = $factory->createObjectCollectionBatcher($qb); /** @var CollectionBatch[] $batches */ $batches = $collectionBatcher->getBatches(50); foreach ($batches as $batch) { /** @var Product $product */ foreach ($batch->getCollection() as $product) { // process $product } }