setono/doctrine-orm-batcher
Opinionated batch processing for Doctrine ORM using seek pagination (no slow LIMIT/OFFSET) to handle large datasets efficiently. Create ID ranges or collections of IDs/entities for synchronous or message-driven async processing.
Installation Add the package via Composer:
composer require setono/doctrine-orm-batcher
Ensure your Laravel app uses Doctrine ORM (e.g., via ocramius/doctrine-laravel).
Basic Setup
Register the batcher service in Laravel’s config/services.php:
'doctrine-batcher' => [
'batch_size' => 50, // Default batch size
'flush_after' => true, // Auto-flush after each batch
],
Bind the Batcher class in a service provider:
$this->app->bind(\Setono\DoctrineORMBatcher\Batcher::class, function ($app) {
return new \Setono\DoctrineORMBatcher\Batcher(
$app['doctrine.orm.entity_manager'],
$app['config']['services.doctrine-batcher']
);
});
First Use Case: Bulk Insert
Inject the Batcher into a service and use it to insert entities in batches:
use Setono\DoctrineORMBatcher\Batcher;
public function __construct(private Batcher $batcher) {}
public function bulkInsertUsers(array $usersData): void
{
$batch = $this->batcher->getBatch();
foreach ($usersData as $data) {
$user = new User();
$user->setName($data['name']);
$user->setEmail($data['email']);
$batch->addEntity($user);
}
$this->batcher->flush();
}
Batched CRUD Operations
getBatch() to start a batch, add entities with addEntity(), then flush().EntityRepository::findBy()), modify, and batch-update:
$batch = $this->batcher->getBatch();
$users = $userRepo->findBy(['active' => false], null, 100);
foreach ($users as $user) {
$user->setActive(true);
$batch->addEntity($user);
}
$this->batcher->flush();
addDelete() for bulk deletes:
$batch = $this->batcher->getBatch();
$batch->addDelete($userRepo->findBy(['deleted' => true]));
$this->batcher->flush();
Transaction Management Wrap batches in transactions for atomicity:
$entityManager = $this->batcher->getEntityManager();
$entityManager->beginTransaction();
try {
$this->batcher->flush();
$entityManager->commit();
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
Dynamic Batch Sizing Adjust batch size per operation:
$this->batcher->setBatchSize(200); // Override default
Integration with Queues Offload batch processing to queues (e.g., Laravel Queues) to avoid timeouts:
dispatch(new ProcessBatchJob($usersData))->onQueue('batch-processing');
onFlush or postFlush to log batch operations:
$eventManager->addEventListener(
\Doctrine\ORM\Events::postFlush,
[$this, 'logBatchOperations']
);
Batcher to add pre/post-flush logic:
$this->batcher->setPostFlushCallback(function () {
Log::info('Batch processed successfully');
});
DoctrineExtensions (e.g., beberlei/DoctrineExtensions) to bridge Eloquent and Doctrine entities if needed.Memory Leaks
memory_get_usage().Transaction Timeouts
flushAfter to auto-flush smaller batches.Entity State Conflicts
EntityNotFoundException.EntityManager or refresh them:
$entityManager->refresh($entity);
Batch Ordering
$entityManager->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
$batch = $this->batcher->getBatch();
dump($batch->getEntities()); // Debug added entities
flushAfter Behavior:
Setting flush_after: true auto-flushes after each batch, which may impact performance for large datasets. Disable it for fine-grained control.max_allowed_packet limits. Test with your batch size.\Setono\DoctrineORMBatcher\Batcher to add domain-specific logic:
class CustomBatcher extends Batcher {
public function addCustomEntity(Entity $entity) {
$this->batch->addEntity($entity);
// Add custom validation
}
}
$eventManager->addEventSubscriber(new class implements EventSubscriber {
public function getSubscribedEvents() {
return [Events::onFlush];
}
public function onFlush(OnFlushEventArgs $args) {
// Modify batch entities here
}
});
Batcher:
$this->app->decorate(
\Setono\DoctrineORMBatcher\Batcher::class,
function ($batcher, $app) {
return new CustomBatcher($batcher->getEntityManager());
}
);
How can I help you explore Laravel packages today?