Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Direct Insert Bundle Laravel Package

tourze/doctrine-direct-insert-bundle

A Symfony bundle that adds high-performance direct insert support to Doctrine, letting you bypass ORM overhead when bulk-loading rows. Simple service integration for fast, efficient writes.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: this is a Symfony bundle, not a Laravel package. The Bundle suffix, the tourze/* vendor, and the Doctrine dependency all point to Symfony's bundle registration model. Laravel has no bundle autodiscovery, no config/bundles.php, and no Kernel::registerBundles(). So "getting started" in a Laravel app means one of two paths:

  1. Use the underlying library, not the bundle. The bundle is almost certainly a thin DI wrapper around a tourze/doctrine-direct-insert (or similarly named) core package. Install that, skip the Symfony wiring.
  2. Keep the bundle but construct the service yourself in a Laravel service provider, passing a Doctrine DBAL Connection built from Laravel's existing PDO.
composer require tourze/doctrine-direct-insert-bundle:^1.0

Pin to ^1.0 rather than *. The release notes for 1.0.0 contain nothing but a compare link between 0.0.1 and 1.0.0, so treat the 0.x → 1.0 jump as possibly breaking until you have read the diff yourself: https://github.com/tourze/doctrine-direct-insert-bundle/compare/0.0.1...1.0.0.

Where to look first, in order:

  1. composer.json of the package — check whether it declares symfony/framework-bundle as a hard requirement. If it does, installing it in Laravel drags in Symfony kernel machinery you don't want.
  2. The bundle's DependencyInjection/*Extension.php and Resources/config/services.yaml — this tells you the real service ID and constructor arguments, which is what you actually need to wire manually.
  3. The core insert service class — confirm whether it exposes multi-row insert or loops single-row Connection::insert() internally.
  4. The compare link above, to see what actually changed at 1.0.0.

First use case: high-volume append-only writes — audit/log tables, telemetry, staging imports, CSV ingestion. The value proposition of "direct insert" is skipping the ORM's UnitOfWork: no entity hydration, no change-set computation, no per-entity flush() bookkeeping. If your write path is 10k+ rows into a table with no business logic, that overhead dominates.

Sanity-check the win before adopting, with a throwaway benchmark against plain Laravel DB::table('x')->insert($chunk). Laravel's query builder already bypasses Eloquent entirely, so the bundle may be solving a problem you don't have. Adopt it only if it offers something the query builder doesn't — typed column mapping, automatic chunking, or driver-specific multi-row SQL.

Implementation Patterns

Wire the connection from Laravel, don't create a second one. The single most common integration bug is constructing a fresh DBAL connection from config, which gives you a separate PDO — separate transaction scope, separate DB::listen() invisibility, separate connection-pool slot.

// AppServiceProvider::register()
$this->app->singleton(DirectInserter::class, function ($app) {
    $pdo = $app['db']->connection()->getPdo();

    return new DirectInserter(new Doctrine\DBAL\Connection([], $pdo));
});

Reusing Laravel's PDO means DB::transaction() and the inserter share a transaction, and Laravel's query log still has a chance of seeing the statements.

Keep the boundary explicit. A workable convention: Eloquent owns reads and low-volume writes with business rules; the direct inserter owns bulk append-only writes only. Enforce it by putting all direct-insert calls behind a small repository class per table, so a grep for the inserter tells you every place ORM behaviour is bypassed.

Chunk, and chunk by parameter count, not row count. Multi-row inserts hit hard driver limits: PostgreSQL caps bind parameters at 65535 per statement, SQLite at 999 (older builds), and MySQL at max_allowed_packet. With 8 columns per row, 1000 rows is 8000 parameters — fine on Postgres, still fine on MySQL, but a 50k-row single statement will fail somewhere. Compute chunk size from column count:

$columnsPerRow = count($rows[0]);
$chunk = (int) floor(60000 / max(1, $columnsPerRow));
foreach (array_chunk($rows, $chunk) as $batch) {
    // insert $batch
}

Wrap the whole load in one transaction where the driver allows it. For MySQL InnoDB and Postgres, a single transaction around all chunks is dramatically faster than autocommit per chunk, and gives you all-or-nothing semantics for an import. For very large loads, consider LOAD DATA / COPY instead — a direct-insert helper is not a bulk loader and won't beat the native paths.

Fill in what the ORM was doing for you. Direct insert bypasses created_at/updated_at, $casts, UUID/ULID generation, observers, and booted() hooks. Do it at the call site:

$now = now();
$rows = array_map(fn ($r) => $r + ['created_at' => $now, 'updated_at' => $now], $rows);

Queue workers: build the inserter inside the job, not as a constructor-injected singleton captured at boot, if you rely on DB::reconnect() after long idle periods — otherwise you hold a stale PDO across a MySQL wait_timeout.

Gotchas and Tips

  • The 1.0.0 release notes are empty. All you get is a compare URL. A 0.0.1 → 1.0.0 jump signals an API-freeze decision, and freezes are where authors rename things. Read the diff before upgrading; assume the service ID or constructor signature changed until proven otherwise.
  • No Laravel service provider. Even with laravel/framework-style package discovery, a Symfony bundle ships no Laravel provider, so there is nothing to autoregister. You are writing the wiring yourself, every time.
  • Symfony dependency weight. If symfony/framework-bundle is a hard require, you're adding a chunk of Symfony's DI container to a Laravel app that already has one. Check composer why symfony/framework-bundle after install; if it appears and you didn't want it, this package is the wrong tool.
  • Silent bypass of Eloquent guarantees. No model events, no $fillable protection, no cast round-trip, no soft-delete scoping, no optimistic locking. If anything downstream relies on an observer firing (search indexing, webhook dispatch, cache invalidation), direct insert will silently break it. Audit observers on the target table before adopting.
  • No validation tier. You are interpolating arrays straight into SQL. Ensure the inserter parameterises values, and never pass user-controlled column names — those are identifiers and cannot be bound. Whitelist column keys against Schema::getColumnListing($table).
  • Type coercion differs per driver. Booleans as true/1/'t', empty strings vs null, and DateTime objects all round-trip differently across MySQL, Postgres, and SQLite. If your test suite uses SQLite in-memory and production is MySQL, direct-insert code is exactly the layer where that mismatch bites. Run integration tests against the production engine.
  • Returning generated IDs is driver-dependent. Postgres wants RETURNING, MySQL wants lastInsertId() (only reliable for single-row or with LAST_INSERT_ID(expr) tricks). Don't assume the helper hands you IDs for a batched insert.
  • Debuggability drops. If the package builds its own DBAL connection, Laravel Telescope, DB::listen(), and QueryExecuted events won't fire for these statements — you lose query visibility at precisely the layer with the most volume. Reuse Laravel's PDO (above) or attach a DBAL middleware/logger.
  • Extension point to prefer: if the bundle exposes a DBAL middleware or a Connection factory service, hook there rather than subclassing the inserter. Subclassing ties you to internals that just changed at 1.0.0.
  • Re-evaluate the need. For plain Laravel, DB::table(...)->insert($chunked) is already ORM-free, already chunkable, already logged, and already covered by every debugging tool you have. The bundle earns its place only if it adds typed mapping or driver-specific optimisations that measurably beat that. Benchmark first; the dependency cost is real and the changelog gives you nothing to justify it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo