tourze/doctrine-async-insert-bundle
A Symfony bundle that adds asynchronous insert support to Doctrine ORM, letting you queue entity inserts and flush them in batches to reduce database round trips and speed up bulk writes in your applications.
tourze/doctrine-async-insert-bundle is a Symfony bundle that buffers Doctrine INSERT statements and flushes them as a batch instead of issuing one round-trip per row. There is no Laravel service provider, no facade, and no config/ publish tag — so treat it as either (a) something you wire manually into a Laravel app that already runs Doctrine DBAL alongside Eloquent, or (b) a reference implementation for the buffered-insert pattern you reimplement with the Laravel queue.
Install and inspect before trusting it:
composer require tourze/doctrine-async-insert-bundle:^1.0
Look at these first, in order:
src/DependencyInjection/Configuration.php — the real contract. Config key, default buffer size, flush strategy, whether a transport/queue is required.Doctrine\DBAL\Connection (usually an AsyncInsertConnection or a middleware) — this tells you exactly which calls are deferred and which still pass through synchronously.kernel.terminate / event subscriber that triggers the flush — that is your durability boundary.First use case: append-only, high-volume writes where read-after-write is not required — request logs, audit trails, metrics samples, feed fan-out rows. Anything where a user hits "save" and immediately reads the row back is a bad first candidate.
Version 1.0.3 ships with no changelog beyond a compare link, so treat it as a drop-in patch on 1.0.x and verify by diffing the tag rather than reading notes.
Buffer, then flush at a known boundary. The bundle's core loop is: intercept insert → append to in-memory buffer → flush on terminate (or when the buffer hits a size threshold). In a Laravel app the equivalent boundary is terminate() middleware, App\Console\Kernel command completion, or a queued job's handle() tail. Never leave a flush dependent on something that might not run.
Pair it with a transaction, not against it. If a deferred insert is part of a unit of work that can roll back, the buffer must be drained inside the same transaction or dropped on rollback. Confirm which the bundle does before relying on it; if it flushes at terminate, you have decoupled the insert from the transaction and lost atomicity.
Chunk your flushes. A single INSERT ... VALUES (...), (...), ... with 10k rows will blow past max_allowed_packet / Postgres's 65535 parameter limit. Set the buffer size to something like 100–500 rows and let the bundle issue multiple statements rather than one giant one.
Laravel-native analogue if you skip the bundle:
// deferred, durable, and observable — unlike a bare in-memory buffer
LogEventJob::dispatch($rows)->onQueue('inserts')->afterCommit();
// inside the job
collect($rows)->chunk(500)->each(fn ($chunk) => DB::table('events')->insert($chunk->all()));
This gives you retries, backoff, dead-lettering, and a dashboard for free. The bundle gives you lower latency and no queue infrastructure. Choose deliberately.
Testing pattern. Because inserts are deferred, the assertion you want is on the buffer, not the table:
// flush deterministically in tests instead of relying on terminate
$this->app->make(InsertBuffer::class)->flush();
$this->assertDatabaseCount('events', 3);
If the bundle exposes no flush entry point, that is a red flag for testability and a reason to wrap it behind your own repository method.
Integration tip. Isolate deferred writes behind a repository or dedicated model so the rest of the codebase never calls a deferred insert by accident. One ->insert() call that silently buffers is the kind of bug that shows up in production as "the row appeared 40 seconds later."
CHANGELOG.md entry — only a compare URL. Pin to 1.0.2 or 1.0.3 explicitly and run composer diff / review the GitHub compare before upgrading a production app. Silent patch releases on a package that mutates your write path deserve paranoia.DB::getLastInsertId() and any insert()-then-select() sequence are unreliable once buffering is on. Auto-increment IDs may not exist when your code returns.queue:work daemon the container is not rebuilt per request, so an in-memory buffer is not cleared by a fresh request lifecycle. Flush at the end of every job/request loop iteration or the buffer grows unbounded and leaks into the next unit of work.terminate/shutdown that throws will often be suppressed by the runtime. Wrap it, log it, and consider a fallback persistence path so rows are never silently dropped.extra.laravel.providers, so composer require alone will do nothing in a Laravel app. You must register the connection decorator and config yourself — and the Symfony config schema (tourze_doctrine_async_insert.*) won't be loaded by Laravel's config system at all.How can I help you explore Laravel packages today?