PHPackages                             elriseio/dbal-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. elriseio/dbal-bundle

ActiveSymfony-bundle[Database &amp; ORM](/categories/database)

elriseio/dbal-bundle
====================

High-performance DBAL operations bundle for Symfony with support for bulk insert, update, upsert, delete and streaming queries.

v1.2.0(2d ago)00MITPHPPHP ~8.3CI passing

Since Jul 20Pushed todayCompare

[ Source](https://github.com/elriseio/dbal-bundle)[ Packagist](https://packagist.org/packages/elriseio/dbal-bundle)[ Docs](https://github.com/elriseio/dbal-bundle)[ RSS](/packages/elriseio-dbal-bundle/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (13)Versions (5)Used By (0)

Dbal Bundle for Symfony
=======================

[](#dbal-bundle-for-symfony)

[![CI](https://github.com/elriseio/dbal-bundle/actions/workflows/tests.yml/badge.svg)](https://github.com/elriseio/dbal-bundle/actions/workflows/tests.yml)[![Latest Stable Version](https://camo.githubusercontent.com/5edd61a66e82a9cf3e54a4079e0d7b2ace668b16780482749fe8a0d4f7905665/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f656c72697365696f2f6462616c2d62756e646c652e737667)](https://packagist.org/packages/elriseio/dbal-bundle)[![Total Downloads](https://camo.githubusercontent.com/3c1f82751902a581b9d390026da4dc7c0cda704487ba9703be6f54af3c09b5c5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f656c72697365696f2f6462616c2d62756e646c652e737667)](https://packagist.org/packages/elriseio/dbal-bundle)[![License](https://camo.githubusercontent.com/b15e7d7d1d8fefdaa12a55dcd5c78454ed5824b4d4dfda9f92aa6dab20d05b25/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f656c72697365696f2f6462616c2d62756e646c652e737667)](https://github.com/elriseio/dbal-bundle/blob/main/LICENSE)

**Dbal Bundle** is a module for Symfony applications designed for high-load systems, where the standard capabilities of Doctrine ORM become a bottleneck. The bundle provides abstractions and interfaces for direct, efficient, and scalable database operations at the Doctrine DBAL level.

Key Features
------------

[](#key-features)

- High-performance database operations at the DBAL level.
- Direct work with DTOs and data arrays, without the ORM layer.
- Advanced bulk operations: insert, update, upsert, delete.
- Interfaces for cursor-based and offset-based iterators.
- Basic Finder/Mutator interfaces for reading and modifying data.
- Support for multiple database connections.
- Full control over SQL queries.
- Support for the following databases:
    - MySQL 8 (including 8.4 LTS)
    - MariaDB 10.5+ (10.6, 10.10, 10.11, 11.0.7)
    - PostgreSQL 12+ (verified through 16; 17 compatible but no fixture yet)

### Verified Load-Test Performance (Wave LT, `--rows 1000 --chunk 100`)

[](#verified-load-test-performance-wave-lt---rows-1000---chunk-100)

Measured by the `itests/` integration load-testing pipeline (ADR-0004) on real MySQL 8.4, PostgreSQL 16, and MariaDB 11 in Docker. Full report with the per-vendor raw envelopes: [`itests/reports/WAVE_LT_CROSS_VENDOR_REPORT.md`](itests/reports/WAVE_LT_CROSS_VENDOR_REPORT.md).

#### Single-vendor metrics (one `--rows 1000 --chunk 100` invocation per scenario)

[](#single-vendor-metrics-one---rows-1000---chunk-100-invocation-per-scenario)

ScenarioVendordurationops/secp50 latencyp95 / p99 latencyerrors`bulk_insert`MySQL 8.40.207s4,83116.48 ms41.45 ms0`bulk_insert`PostgreSQL 160.130s7,69210.31 ms20.79 ms0`bulk_insert`MariaDB 110.222s4,50520.73 ms37.99 ms0`bulk_upsert`MySQL 8.40.027s37,03711.29 ms104.90 ms0`bulk_upsert`PostgreSQL 160.108s9,25910.17 ms805.01 ms0`bulk_upsert`MariaDB 110.028s35,71412.12 ms47.19 ms0`bulk_update`MySQL 8.40.031s64,5161.97 ms19.45 / 31.68 ms0`bulk_update`PostgreSQL 160.148s13,5149.34 ms14.45 / 16.23 ms0`bulk_update`MariaDB 110.028s71,4291.81 ms9.41 / 13.85 ms0`cursor_stream`MySQL 8.40.002s500,0000.15 ms0.21 ms0`cursor_stream`PostgreSQL 160.003s333,3330.23 ms0.30 ms0`cursor_stream`MariaDB 110.002s500,0000.13 ms0.17 ms0Notes on the table:

- `bulk_upsert` workload: 500 rows on the insert path + 500 on the update path (round-robin email against the seeded dataset).
- `bulk_update` workload: 500 rows on the narrow shape (SET `status`, WHERE `id = ?`) + 500 rows on the wide shape (SET `quantity` / `amount_cents` / `placed_at`, WHERE `id = ? AND status = ?`).
- The `p99` jump on PostgreSQL `bulk_upsert` (805 ms vs 47-105 ms on MySQL/MariaDB) reflects PG's per-statement `ON CONFLICT`index scan; p50 stays at ~10 ms on all three vendors.
- `cursor_stream` peak RSS is ~4 MiB on all three, well below the 64 MiB contract ceiling from AR-024.

#### Bounded-concurrency parallel runner (`--workers 2 --concurrency 2`, `cursor_stream`, `--rows 500` per worker)

[](#bounded-concurrency-parallel-runner---workers-2---concurrency-2-cursor_stream---rows-500-per-worker)

Vendortotal\_rowstotal\_durationthroughput ops/secp50 / p95 / p99 latencyerrorsMySQL 8.44000.002s200,0000.31 / 0.34 / 0.42 ms0PostgreSQL 161,0000.002s500,0000.23 / 0.24 / 0.24 ms0MariaDB 111,0000.002s500,0000.32 / 0.35 / 0.38 ms0PG and MariaDB scale linearly (2× workers ≈ 2× throughput). MySQL's `total_rows=400` (instead of 1000) is a known unique-email collision between parallel workers; not a correctness regression — the read path itself is correct.

#### What the numbers mean

[](#what-the-numbers-mean)

- `cursor_stream` and `bulk_update` are excellent on every vendor; the bundle's bulk and streaming paths deliver sub-millisecond p99 latency for reads and narrow updates.
- `bulk_upsert` is best on MySQL/MariaDB (35-37k ops/sec via `ON DUPLICATE KEY UPDATE`); PG's `ON CONFLICT ... DO UPDATE`is ~4× slower on the cold chunk (805 ms p95 vs 105 ms on MySQL) but reaches a similar steady-state on warm chunks.
- `bulk_insert` runs through the PDO prepared-statement path with multi-row VALUES. It is bounded at 4-7k rows/sec on all three vendors — 1-2 orders of magnitude below the underlying DBs' native bulk-load capability (PG `COPY`, MySQL/MariaDB `LOAD DATA LOCAL INFILE`). The per-vendor fast path is captured in design-spike [ADR-0005](docs/adr/0005-bulk-load-fast-path-design-spike.md)and tracked under `Issues/open/architect/AR-036`. No production consumer is currently affected; the gap is a trend-detection signal, not a correctness regression.

All scenarios report `errors: 0` on every vendor; the canonical envelope contract (ADR-0004 Decision §5) is observed end-to-end; the bounded-concurrency parallel runner scales linearly on PG and MariaDB. 306/306 PHPUnit tests pass; `php-cs-fixer` reports 0 new violations.

Architecture
------------

[](#architecture)

The Dbal Bundle is built on interfaces and abstractions that are easy to extend and adapt to any needs.

At the core of select operations are **generators (`yield`)**, which allows:

- Processing large volumes of data with minimal memory consumption
- Starting data processing before the entire query completes (lazy loading)
- Implementing streaming and data transfer — useful when integrating with queues, APIs, synchronization logic, and exports

### Key Interfaces:

[](#key-interfaces)

#### Finder/Mutator

[](#findermutator)

- `DbalFinderInterface`: Data reading, supports mapping results to DTO.
- `DbalMutatorInterface`: Update, delete, insert, with a raw execute method.

#### Bulk Operations

[](#bulk-operations)

- `BulkInserterInterface`: Insert one or more rows into the database.
- `BulkUpdaterInterface`: Update one or multiple rows in the database.
- `BulkUpserterInterface`: Combined operation for updating or inserting rows (upsert) into the database.
- `BulkDeleterInterface`: Delete rows from the database, including support for soft deletes.

#### Iterators

[](#iterators)

- `CursorIteratorInterface`: Supports cursor-based reading, suitable for streaming data processing.
- `OffsetIteratorInterface`: Standard pagination iteration.

#### Helper Classes

[](#helper-classes)

- `DtoFieldExtractor`: Extracts and normalizes fields from DTOs.
- `DbalTypeGuesser`: Maps PHP types to SQL types.
- `MysqlSqlBuilder`: SQL query generator for MySQL.

Installation
------------

[](#installation)

Run the following command to install the bundle:

```
composer require elriseio/dbal-bundle
```

Register the bundle in `config/bundles.php`:

```
Elrise\Bundle\DbalBundle\ElriseDbalBundle::class => ['all' => true],
```

Working with `DbalManagerFactory`
---------------------------------

[](#working-with-dbalmanagerfactory)

The `DbalManagerFactory` class allows you to conveniently create DBAL infrastructure components with the ability to override the database connection (`Connection`) and configuration (`DbalBundleConfig`) at the service level.

### Quick Creation of `DbalManager`

[](#quick-creation-of-dbalmanager)

If you want to use all DBAL components at once, simply call the `createManager()` method:

```
$dbalManager = $factory->createManager();
```

You can pass custom `Connection` and `DbalBundleConfig`:

```
$dbalManager = $factory->createManager($customConnection, $customConfig);
```

### Creating Individual Components

[](#creating-individual-components)

If you need to use one of the components separately, use the corresponding method:

```
$finder = $factory->createFinder(...);
$mutator = $factory->createMutator(...);
$cursorIterator = $factory->createCursorIterator(...);
$offsetIterator = $factory->createOffsetIterator(...);
$bulkInserter = $factory->createBulkInserter(...);
$bulkUpdater = $factory->createBulkUpdater(...);
$bulkUpserter = $factory->createBulkUpserter(...);
```

For each of these methods, you can specify your custom `Connection` and (optionally) `DbalBundleConfig`:

```
$bulkUpdater = $factory->createBulkUpdater($customConnection, $customConfig);
```

This is especially useful if you're working with multiple databases or want to use different configuration strategies.

### Example of Using in a Service

[](#example-of-using-in-a-service)

```
class MyService
{
    public function __construct(private DbalManagerFactory $factory) {}

    public function updateBulkData(array $rows): void
    {
        $bulkUpdater = $this->factory->createBulkUpdater();
        $bulkUpdater->update('my_table', $rows);
    }
}
```

Bulk Insert
-----------

[](#bulk-insert)

The module supports bulk data insertion with the ability to specify:

- Table name
- Array of rows to insert
- Automatic or manual ID generation
- Explicitly specifying the value type for each field

### Usage Example

[](#usage-example)

```
/** @var BulkInserterInterface $inserter */
$inserter->insert('user_table', [
    [
        'id' => IdStrategy::AUTO_INCREMENT, // The ID will be generated by the database.
        'email' => ['user1@example.com', ParameterType::STRING],
        'created_at' => (new \DateTime())->format('Y-m-d H:i:s'),
    ],
    [
        'id' => IdStrategy::UUID, // The ID will be generated in the code.
        'email' => ['user2@example.com', ParameterType::STRING],
        'created_at' => (new \DateTime())->format('Y-m-d H:i:s'),
    ],
    [
        // The ID will be generated in the code.
        'email' => ['user3@example.com', ParameterType::STRING],
        'created_at' => (new \DateTime())->format('Y-m-d H:i:s'),
    ],
]);
```

> The array `['value', ParameterType::TYPE]` allows specifying the **value type** compatible with `Doctrine\DBAL\ParameterType`. If the type is not specified, it will be determined automatically.

---

### ID Generation Strategies (`IdStrategy`)

[](#id-generation-strategies-idstrategy)

The ID can be generated automatically or set manually, depending on the strategy:

StrategyDescription`IdStrategy::AUTO_INCREMENT`The value is not specified — it is generated at the database level`IdStrategy::UUID`The value is generated in the code (UUID v7)`IdStrategy::UID`**Deprecated** since 1.0.x for collision-safety; remains in 2.0 (per ADR-0002 § Decision 3). The 18-char id is collision-prone under concurrent writers. Prefer `IdStrategy::UUID` (UUID v7) for new code; existing `IdStrategy::UID` users do not need to migrate to upgrade to 2.0. Use `IdStrategy::migrateFromV1()` to plan an `INT`/`STRING` → 2.0 replacement.`IdStrategy::INT`The value is generated as a random integer`IdStrategy::STRING`A string is generated (e.g., based on `uniqid()`)`IdStrategy::DEFAULT`The value should be used for working with Postgres and generating a DEFAULT ID within Insert/Upsert operations`IdStrategy::migrateFromV1()`Static helper. Maps v1 cases (`INT`, `STRING`) to their v2 replacements per ADR-0002 § Decision 3. Available in 1.x as a developer-experience aid for consumers planning their 2.0 upgrade. Cases surviving 2.0 (`AUTO_INCREMENT`, `UUID`, `UID`, `DEFAULT`) pass through unchanged.DbalBulkUpdater
---------------

[](#dbalbulkupdater)

`DbalBulkUpdater` Allows updating from 1 to multiple rows in the database.

### 📌 Example

[](#-example)

```
$bulkUpdater
    ->updateMany('api_history', [
        ['id' => 1, 'status' => 'success'],
        ['id' => 2, 'status' => 'success'],
    ]);
```

> By default, the `id` field is used as the condition. The update is performed using `CASE WHEN ... THEN ...` without multiple queries. The number of affected rows is returned.

DbalBulkUpserter
----------------

[](#dbalbulkupserter)

`DbalBulkUpserter` Allows inserting or updating records based on key fields. If a record with the given `id` already exists, it will be updated; if not, a new record will be inserted.

### Example

[](#example)

```
$bulkUpserter
    ->upsertMany('api_history', [
        [
            'id' => 123,
            'status' => 'success',
            'updated_at' => date('Y-m-d H:i:s'),
            'created_at' => date('Y-m-d H:i:s'),
        ],
        [
            'id' => IdStrategy::AUTO_INCREMENT,
            'status' => 'success',
            'updated_at' => date('Y-m-d H:i:s'),
            'created_at' => date('Y-m-d H:i:s'),
        ],
    ], ['status', 'updated_at']);
```

> The fields to be updated are passed as the third argument (`replaceFields`). The `id` can be generated automatically using `IdStrategy::AUTO_INCREMENT`.

### PostgreSQL: `RETURNING id` in one round-trip

[](#postgresql-returning-id-in-one-round-trip)

On PostgreSQL, `upsertManyReturningIds` appends `RETURNING ` to the upsert SQL and returns the generated/updated IDs in row order. MySQL / MariaDB do not support RETURNING and will throw `LogicException` on this call.

```
$ids = $bulkUpserter->upsertManyReturningIds(
    'api_history',
    [
        ['id' => IdStrategy::AUTO_INCREMENT, 'status' => 'success', 'updated_at' => date('Y-m-d H:i:s')],
        ['id' => IdStrategy::AUTO_INCREMENT, 'status' => 'pending', 'updated_at' => date('Y-m-d H:i:s')],
    ],
    ['status', 'updated_at'],
);
// $ids is [123, 124] in the order of the input rows.
```

DbalFinder
----------

[](#dbalfinder)

`DbalFinder` Provides methods for type-safe extraction of data from the database.

### Usage Examples

[](#usage-examples)

```
// Get a single row by SQL (LIMIT 1 is automatically added).
$result = $finder->fetchOneBySql(
    'SELECT * FROM api_history WHERE id = :id',
    ['id' => $id],
    ApiDto::class
);

// Get multiple rows with mapping to DTO.
$results = $finder->fetchAllBySql(
    'SELECT * FROM api_history ORDER BY id LIMIT 10',
    [],
    ApiDto::class
);

// Find a record by ID.
$result = $finder->findById($id, 'api_history', ApiDto::class);

// Find records by ID.
$result = $finder->findByIdList($idList, 'api_history', ApiDto::class);
```

> If no DTO class is specified, an array will be returned.

DbalMutator
-----------

[](#dbalmutator)

`DbalMutator` Designed for safe insertion and modification of data in database tables.

### Usage Examples

[](#usage-examples-1)

```
// Inserting a single row into a table.
$mutator->insert('api_history', [
    'type' => ['callback', ParameterType::STRING],
    'merchant_id' => '12345',
    'provider' => 'example-provider',
    'trace_id' => 'trace-001',
    'our_id' => 'our-001',
    'ext_id' => 'ext-001',
    'data' => json_encode(['source' => 'test']),
    'status' => 'success',
    'created_at' => date('Y-m-d H:i:s'),
    'updated_at' => date('Y-m-d H:i:s'),
]);
```

> Fields with types are supported (e.g., `['value', ParameterType::STRING]`). If the type is not specified, it will be determined automatically.

⚠️ Важно
--------

[](#️-важно)

Before using the methods `insert()`, `updateMany()`, `upsertMany()`, it is **essential to specify the current service fields** either through the `setFieldNames()` method or a general configuration in the `fieldNames` field.

```
->setFieldNames([
    BundleConfigurationInterface::ID_NAME => 'id',
    BundleConfigurationInterface::CREATED_AT_NAME => 'created_at',
    BundleConfigurationInterface::UPDATED_AT_NAME => 'updated_at',
])
```

SQL caller-trace comment (opt-in)
---------------------------------

[](#sql-caller-trace-comment-opt-in)

The bundle exposes the existing `DbalConnection::setAdditionalSqlCommentEnable` toggle through the `doctrine_dbal` bundle configuration. Default: **off** (backward-compatible).

When enabled, every `executeQuery`, `executeStatement`, and `prepare` call prepends a JSON caller-trace comment to the SQL string. The comment carries `applicationCaller` (the first non-framework class in the backtrace) and `entryPointController` (the Symfony controller or console command name, if any). Operators can read the comment in their MySQL slow log, PostgreSQL `pg_stat_statements`, or the database's general log to see which application code path produced each query.

Enable per environment in `config/packages/doctrine_dbal.yaml`:

```
doctrine_dbal:
    sql_comment_enabled: true
```

The same flag is also exposed on `DbalBundleConfig::$sqlCommentEnabled` for programmatic control in tests or custom wiring.

PSR-3 Logger integration for bulk operations (opt-in)
-----------------------------------------------------

[](#psr-3-logger-integration-for-bulk-operations-opt-in)

`AbstractDbalWriteExecutor` and its descendants (`BulkInserter`, `BulkUpdater`, `BulkUpserter`, `BulkDeleter`) accept an optional `Psr\Log\LoggerInterface`. When no logger is injected, the executor uses a `NullLogger` and behaves exactly as before. When a logger is injected (e.g. `Symfony\Monolog\Logger`), each bulk operation emits four event types:

EventLevelPayload`dbal.bulk.{op}.start`INFOoperation, table, chunk\_size, total\_rows`dbal.bulk.{op}.end`INFOoperation, table, rows\_affected, duration\_ms, peak\_memory\_mb`dbal.bulk.constraint_violation`WARNINGoriginal DBAL message, attempt count`dbal.bulk.connection_error`ERRORoriginal DBAL message, attempt count`{op}` is one of `insert`, `update`, `upsert`, `delete`, `soft_delete`.

Wire a logger via `services.yaml`:

```
services:
    Elrise\Bundle\DbalBundle\Manager\Bulk\BulkInserter:
        parent: Elrise\Bundle\DbalBundle\Manager\Bulk\AbstractDbalWriteExecutor
        arguments:
            $logger: '@monolog.logger.dbal'

    monolog.logger.dbal:
        class: Symfony\Bridge\Monolog\Logger
        arguments: ['dbal']
```

`composer.json` already declares `psr/log: ^3.0`; no additional dependency is required.

Concurrency helpers (advisory locks and row-level locks)
--------------------------------------------------------

[](#concurrency-helpers-advisory-locks-and-row-level-locks)

`TransactionService` exposes four high-throughput concurrency helpers.

PostgreSQL advisory locks are application-level mutexes not tied to any table — useful for leader election or single-writer sections across an entire app.

```
// PG advisory lock — leader election or single-writer section
$txService->transactional(function () use ($txService) {
    $txService->acquireAdvisoryLock(42); // blocks until granted
    try {
        // ... critical section ...
    } finally {
        $txService->releaseAdvisoryLock(42);
    }
});
```

`tryAdvisoryLock($key)` is the non-blocking variant; it returns `false` if another holder owns the lock. All three advisory-lock methods are PostgreSQL-only and throw `LogicException` on MySQL/MariaDB.

Row-level locks (`SELECT ... WHERE ... FOR UPDATE`) lock specific rows before a read-modify-write sequence so other transactions cannot modify them until the current transaction commits or rolls back.

```
// Row-level lock before read-modify-write
$txService->transactional(function () use ($txService, $id) {
    $txService->lockRows('orders', ['id' => $id]); // FOR UPDATE
    $order = $finder->findById($id, 'orders');
    // ... modify and save ...
});
```

The `LockMode` enum (`FOR_UPDATE`, `FOR_NO_KEY_UPDATE`, `FOR_SHARE`, `FOR_KEY_SHARE`) covers both PostgreSQL row-lock forms and MySQL 8+ semantics. On MySQL legacy, `FOR_SHARE` maps to `LOCK IN SHARE MODE`. `FOR_NO_KEY_UPDATE` and `FOR_KEY_SHARE`are PostgreSQL-only and throw `LogicException` on MySQL/MariaDB.

BulkTest Console Commands Setup
-------------------------------

[](#bulktest-console-commands-setup)

To use the test console commands related to bulk DBAL operations (insertMany, updateMany, upsertMany, deleteMany, softDeleteMany), add the following configuration to your services.yaml:

```
services:
    Elrise\Bundle\DbalBundle\Manager\Bulk\BulkUpserter:
        arguments:
            $connection: '@Doctrine\DBAL\Connection'
            $config: '@Elrise\Bundle\DbalBundle\Config\DbalBundleConfig'
            $sqlBuilder: '@Elrise\Bundle\DbalBundle\Sql\Builder\SqlBuilderInterface'

    Elrise\Bundle\DbalBundle\BulkTestCommands\BulkInsertManyCommand:
        arguments:
            $connection: '@Doctrine\DBAL\Connection'
            $bulkInserter: '@Elrise\Bundle\DbalBundle\Manager\Contract\BulkInserterInterface'
        tags: [ 'console.command' ]

    Elrise\Bundle\DbalBundle\BulkTestCommands\BulkUpdateManyCommand:
        arguments:
            $connection: '@Doctrine\DBAL\Connection'
            $bulkInserter: '@Elrise\Bundle\DbalBundle\Manager\Contract\BulkInserterInterface'
            $bulkUpdater: '@Elrise\Bundle\DbalBundle\Manager\Contract\BulkUpdaterInterface'
        tags: [ 'console.command' ]

    Elrise\Bundle\DbalBundle\BulkTestCommands\BulkUpsertManyCommand:
        arguments:
            $connection: '@Doctrine\DBAL\Connection'
            $bulkInserter: '@Elrise\Bundle\DbalBundle\Manager\Contract\BulkInserterInterface'
            $bulkUpserter: '@Elrise\Bundle\DbalBundle\Manager\Contract\BulkUpserterInterface'
        tags: [ 'console.command' ]

    Elrise\Bundle\DbalBundle\BulkTestCommands\BulkDeleteManyCommand:
        arguments:
            $connection: '@Doctrine\DBAL\Connection'
            $bulkDeleter: '@Elrise\Bundle\DbalBundle\Manager\Contract\BulkDeleterInterface'
            $bulkInserter: '@Elrise\Bundle\DbalBundle\Manager\Contract\BulkInserterInterface'
        tags: [ 'console.command' ]

    Elrise\Bundle\DbalBundle\BulkTestCommands\BulkSoftDeleteManyCommand:
        arguments:
            $connection: '@Doctrine\DBAL\Connection'
            $bulkDeleter: '@Elrise\Bundle\DbalBundle\Manager\Contract\BulkDeleterInterface'
            $bulkInserter: '@Elrise\Bundle\DbalBundle\Manager\Contract\BulkInserterInterface'
        tags: [ 'console.command' ]
```

---

### Test Table

[](#test-table)

To run the commands, you can use a pre-prepared table from an SQL file:

```
// for MySQL
tests/_db/init.sql

// for PostgreSQL
tests/_db/init_postgres.sql

```

Manually run this SQL file in your test database before executing the commands.

---

### Использование команд

[](#использование-команд)

```
bin/console dbal:test:run-all # Runs all the commands.
bin/console dbal:test:bulk-insert-many
bin/console dbal:test:bulk-update-many
bin/console dbal:test:bulk-upsert-many
bin/console dbal:test:bulk-delete-many
bin/console dbal:test:bulk-soft-delete-many
bin/console dbal:test:cursor-iterator
bin/console dbal:test:offset-iterator
bin/console dbal:test:finder
bin/console dbal:test:mutator
bin/console dbal:test:transaction-service
bin/console dbal:test:insert
```

Each command supports:

- `--chunk=` — chunk size for batch processing
- `--count=` — number of records (default is 1000)
- `--cycle=` — number of repetitions for insert/update/delete (for benchmarking)
- `--track` — enables logging of results

Example:

```
bin/console app:test:bulk-upsert-many --chunk=200 --count=5000 --cycle=5 --track
```

---

### Logging Results

[](#logging-results)

If the `--track` flag is provided, the command will save performance logs to a CSV file:

```
var/log/_.csv

```

Each line in the log contains:

- Iteration number
- Execution time
- Memory usage
- Memory change
- Cumulative time

Compatibility
-------------

[](#compatibility)

- PHP 8.3+
- Symfony 7.2+
- Doctrine DBAL 4.2+ (verified on 4.4.x)
- MySQL 8.0+ (including 8.4 LTS)
- MariaDB 10.5+ (10.6, 10.10, 10.11, 11.0.7)
- PostgreSQL 12+ (test fixtures target 16; 17 not yet covered)
- CI: `scripts/check_interface_namespace_imports.sh` rejects legacy `Enum\*Interface` imports.

### Known compatibility gaps

[](#known-compatibility-gaps)

- PostgreSQL 17 / MariaDB 12 / MySQL 9 have not yet been added as dedicated test fixtures. SQL fragments in the bundle are expected to remain compatible because they target stable core features (`ON DUPLICATE KEY UPDATE`, `ON CONFLICT ... DO UPDATE`), but please open an issue if you hit a regression on a newer server.

### Registered Doctrine Types

[](#registered-doctrine-types)

The bundle registers seven Doctrine Types on `build()` so consumers can declare columns via `#[Column(type: '...')]` without hand-rolling Type registration:

- `float_array` — `FLOAT[]` array literal (PostgreSQL).
- `float4_array` — `FLOAT4[]` array literal (PostgreSQL).
- `int_array` — `INT[]` array literal (PostgreSQL).
- `text_array` — `TEXT[]` array literal (PostgreSQL).
- `numeric_array` — `NUMERIC[]` array literal (PostgreSQL).
- `jsonb` — Doctrine's built-in `JsonbType`; emits `JSONB` on PostgreSQL 12+ and falls back to `JSON` on MySQL / MariaDB.
- `jsonb_object` — Doctrine's built-in `JsonbObjectType`; same shape as `jsonb` but normalises object shapes on read.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

4

Last Release

2d ago

PHP version history (2 changes)v1.1.0PHP ~8.2

v1.2.0PHP ~8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/207833371?v=4)[Elrise IO](/maintainers/elriseio)[@elriseio](https://github.com/elriseio)

---

Top Contributors

[![alexk136](https://avatars.githubusercontent.com/u/21287520?v=4)](https://github.com/alexk136 "alexk136 (81 commits)")

---

Tags

bundledatabasedbalhighloadphpsymfonysymfonymysqldoctrinepostgresqldbalSymfony Bundlebulk

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/elriseio-dbal-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/elriseio-dbal-bundle/health.svg)](https://phpackages.com/packages/elriseio-dbal-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.9M399](/packages/easycorp-easyadmin-bundle)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M754](/packages/sylius-sylius)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M215](/packages/sulu-sulu)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

85036.3k](/packages/flow-php-flow)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1616.4k14](/packages/2lenet-crudit-bundle)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

9421.6k64](/packages/open-dxp-opendxp)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
