PHPackages                             aeatech/transaction-manager-doctrine-adapter - 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. aeatech/transaction-manager-doctrine-adapter

ActiveLibrary[Database &amp; ORM](/categories/database)

aeatech/transaction-manager-doctrine-adapter
============================================

Implementation of the transaction manager connection with Doctrine

1.0.0(5mo ago)0243MITPHPPHP &gt;=8.2CI passing

Since Jan 11Pushed 5mo agoCompare

[ Source](https://github.com/AEATech/transaction-manager-doctrine-adapter)[ Packagist](https://packagist.org/packages/aeatech/transaction-manager-doctrine-adapter)[ RSS](/packages/aeatech-transaction-manager-doctrine-adapter/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (8)Versions (2)Used By (3)

AEATech Transaction Manager — Doctrine Adapter
==============================================

[](#aeatech-transaction-manager--doctrine-adapter)

[![Code Coverage](.build/coverage_badge.svg)](.build/coverage_badge.svg)

Doctrine DBAL adapter for AEATech Transaction Manager with best‑effort prepared‑statement reuse and explicit parameter binding compatible with both DBAL 3 and DBAL 4.

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

[](#key-features)

- Statement reuse policies: `None`, `PerTransaction`, `PerConnection`.
- Lightweight O(1) LRU cache for prepared statements.
- Stable cache key builder based on SQL hash and parameter count.
- Low‑level statement executor with explicit parameter binding.
- Supports Doctrine DBAL types and legacy `PDO::PARAM_*` integer constants.
- Correct handling of transaction boundaries for MySQL and PostgreSQL.

Contents (API Surface)
----------------------

[](#contents-api-surface)

- Connection adapters
    - Caching (with statement reuse support):
        - `AEATech\TransactionManager\DoctrineAdapter\DbalMysqlStatementCachingConnectionAdapter`
        - `AEATech\TransactionManager\DoctrineAdapter\DbalPostgresStatementCachingConnectionAdapter`
    - Simple (no caching):
        - `AEATech\TransactionManager\DoctrineAdapter\DbalMysqlConnectionAdapter`
        - `AEATech\TransactionManager\DoctrineAdapter\DbalPostgresConnectionAdapter`

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

[](#installation)

```
composer require aeatech/transaction-manager-doctrine-adapter
```

Quick Start
-----------

[](#quick-start)

```
use AEATech\TransactionManager\DoctrineAdapter\DbalMysqlStatementCachingConnectionAdapter;
use AEATech\TransactionManager\DoctrineAdapter\StatementCache\LruStatementCache;
use AEATech\TransactionManager\DoctrineAdapter\StatementCache\SqlAndParamCountCacheKeyBuilder;
use AEATech\TransactionManager\DoctrineAdapter\StatementExecutor\BindingInfoResolver;
use AEATech\TransactionManager\DoctrineAdapter\StatementExecutor\StatementExecutor;
use AEATech\TransactionManager\Query;
use AEATech\TransactionManager\StatementReusePolicy;
use Doctrine\DBAL\DriverManager;

$conn = DriverManager::getConnection([
    'driver' => 'pdo_mysql',
    'host' => '127.0.0.1',
    'dbname' => 'app',
    'user' => 'app',
    'password' => 'secret',
]);

$executor   = new StatementExecutor(new BindingInfoResolver());
$perTxCache = new LruStatementCache(100);
$perConnCache = new LruStatementCache(500);
$keyBuilder = new SqlAndParamCountCacheKeyBuilder();

$adapter = new DbalMysqlStatementCachingConnectionAdapter(
    $conn,
    $executor,
    $keyBuilder,
    $perTxCache,
    $perConnCache,
);

// Execute parametrized query with statement reuse
$q = new Query(
    'UPDATE accounts SET balance = balance - ? WHERE id = ?',
    [100, 42]
);
$q->statementReusePolicy = StatementReusePolicy::PerTransaction;

$affected = $adapter->executeQuery($q);
```

Statement Reuse Policies
------------------------

[](#statement-reuse-policies)

- `None`: No caching — each call prepares a fresh statement (may result in prepare-per-call behavior for server-side prepared statements).
- `PerTransaction`: Prepared statements are cached for the current transaction and dropped on `commit()`/`rollBack()`.
- `PerConnection`: Prepared statements are cached for the lifetime of the connection and dropped on `close()`.

Statement Cache
---------------

[](#statement-cache)

### LRU Cache

[](#lru-cache)

`LruStatementCache` provides O(1) get/set and evicts the least‑recently used entry when capacity is exceeded.

Implementation notes:

- The cache stores `Doctrine\DBAL\Statement` objects.
- Eviction happens only when `set()` pushes the size above capacity.
- `clear()` drops the entire cache (used on transaction/connection boundaries).

### Cache Key Builder

[](#cache-key-builder)

`SqlAndParamCountCacheKeyBuilder` builds keys as `sha256(sql) | 'p:' . count(params)`.

Implications:

- Only SQL text and parameter count are considered. Parameter values, names, and order for named parameters are intentionally ignored.
- Keys are suitable for best-effort reuse only and MUST NOT be relied upon for semantic correctness.

StatementExecutor (explicit binding)
------------------------------------

[](#statementexecutor-explicit-binding)

`StatementExecutor` executes an already prepared DBAL `Statement` and performs explicit parameter binding using the wrapped driver statement. This bypasses DBAL’s internal binding logic and supports a broader set of type descriptors for compatibility across DBAL versions.

Supported type descriptors for each parameter:

- `Doctrine\DBAL\ParameterType` (DBAL 4 enum)
- `Doctrine\DBAL\Types\Type` instance
- DBAL type name as string (e.g., `'integer'`, `'string'`)
- Legacy `PDO::PARAM_*` ints (`PDO::PARAM_INT`, `PDO::PARAM_BOOL`, `PDO::PARAM_NULL`, `PDO::PARAM_LOB`)

Binding rules:

- Positional parameters are bound sequentially as 1‑based positions (`?`). Original array keys may have gaps — binding order follows array iteration order.
- Named parameters must match the placeholders used in SQL exactly (e.g., `':id'` vs `'id'`). The executor does not normalize names.
- Missing types default to `ParameterType::STRING`.
- Array parameter values are not supported (no automatic `IN (...)` expansion) and are rejected.

Compatibility notes:

- DBAL string type names are resolved via `Type::getType($name)`, then `convertToDatabaseValue()` is applied using the active platform, and `getBindingType()` is used for the driver bind.
- `PDO::PARAM_*` integers are mapped to the corresponding `ParameterType` where applicable.

Transaction Adapters
--------------------

[](#transaction-adapters)

### MySQL/MariaDB

[](#mysqlmariadb)

`DbalMysqlStatementCachingConnectionAdapter::beginTransactionWithOptions()` sets the isolation level for the next transaction (if provided in `$options`) and then begins the transaction:

```
$adapter->beginTransactionWithOptions($options);
// executes: SET TRANSACTION ISOLATION LEVEL ... (only if isolationLevel is set), then BEGIN
```

### PostgreSQL

[](#postgresql)

`DbalPostgresStatementCachingConnectionAdapter::beginTransactionWithOptions()` begins a transaction first and then sets isolation for the current transaction only (if provided in `$options`):

```
$adapter->beginTransactionWithOptions($options);
// executes: BEGIN; then SET TRANSACTION ISOLATION LEVEL ... (only if isolationLevel is set)
```

Both adapters:

- Support optional `isolationLevel`. If `null`, no isolation level command is issued, and the database/session default is used.
- Throw if a transaction is already active.
- Clear the per‑transaction cache on every transaction boundary (`begin` with options, `commit`, `rollBack`).

Edge Cases and Limitations
--------------------------

[](#edge-cases-and-limitations)

- Statement caching is the best effort; entries may be evicted at any time due to capacity limits.
- Named parameter keys must match the SQL placeholders format exactly; no normalization is performed.
- No array parameter expansion is provided; arrays as values are rejected.
- `rowCount()` is returned from the driver result and may be 0 for unsupported operations depending on the driver.
- Reusing prepared statements across transaction boundaries is disallowed for `PerTransaction` and prevented by clearing caches.

Examples
--------

[](#examples)

### Positional parameters with mixed types

[](#positional-parameters-with-mixed-types)

```
use Doctrine\DBAL\ParameterType;

$q = new Query('UPDATE t SET a = ? WHERE id = ?', ['10', 5]);
$q->types = [0 => 'integer', 1 => ParameterType::INTEGER];
$q->statementReusePolicy = StatementReusePolicy::PerTransaction;

$adapter->executeQuery($q);
```

### Named parameters with explicit DBAL types

[](#named-parameters-with-explicit-dbal-types)

```
use Doctrine\DBAL\ParameterType;

$q = new Query('UPDATE t SET a = :a WHERE id = :id', [':a' => 'x', ':id' => 10]);
$q->types = [':a' => 'string', ':id' => ParameterType::INTEGER];
$q->statementReusePolicy = StatementReusePolicy::PerConnection;

$adapter->executeQuery($q);
```

### Using PDO type constants

[](#using-pdo-type-constants)

```
use PDO;

$q = new Query('INSERT INTO files(data) VALUES(?)', [$blob]);
$q->types = [PDO::PARAM_LOB];

$adapter->executeQuery($q);
```

Choosing the Right Adapter
--------------------------

[](#choosing-the-right-adapter)

Selecting the appropriate adapter depends on your database, prepared statement mode, and performance characteristics of your workload.

### Adapter Selection Matrix

[](#adapter-selection-matrix)

DatabaseModeRecommended AdapterWhy?**MySQL**Server-side Prepares`DbalMysqlStatementCachingConnectionAdapter`**Significant performance gain.** avoids repeated COM\_STMT\_PREPARE round-trips and server-side parsing.**MySQL**Emulated Prepares`DbalMysqlConnectionAdapter`**Simpler and sufficient.** PDO client-side emulation is already efficient; statement caching provides no measurable benefit.**PostgreSQL**Native`DbalPostgresConnectionAdapter`**Recommended default.** `pdo_pgsql` preparation overhead is low; client-side caching yields only marginal gains in typical workloads.**PostgreSQL**Complex/Heavy load`DbalPostgresStatementCachingConnectionAdapter`**Optional optimization.** May reduce allocation and preparation overhead under extreme load or highly repetitive statement execution.### Recommendations

[](#recommendations)

1. **Use Caching Adapters for MySQL Server-Side Prepares:** If you have `PDO::ATTR_EMULATE_PREPARES => false`, prefer `DbalMysqlStatementCachingConnectionAdapter`. Reusing prepared statements avoids repeated server-side prepares and can result in 2× or greater throughput improvements for statement-heavy workloads.
2. **MySQL with Emulated Prepared Statements:** When using client-side emulation, DbalMysqlConnectionAdapter is the preferred choice. It is lighter, simpler to configure, and provides identical performance without maintaining an internal statement cache.
3. **PostgreSQL Default Usage:** Start with DbalPostgresConnectionAdapter. PostgreSQL already handles prepared statements efficiently via the extended query protocol ,and statement caching typically provides only 1–3% improvements in microbenchmarks.
4. **Memory-Constrained Environments:** When memory usage is critical, prefer non-caching adapters. They avoid maintaining internal LRU caches and provide more predictable memory behavior.

For detailed performance measurements and the rationale behind these recommendations, see the [Benchmarking](#benchmarking) section.

Benchmarking
------------

[](#benchmarking)

The package includes a benchmark suite to measure the effectiveness of prepared statement reuse across different databases and configurations.

### Running Benchmarks

[](#running-benchmarks)

A dedicated script `bench.sh` is provided to run benchmarks in a controlled environment with CPU pinning to reduce noise.

```
# Run all benchmarks (MySQL and PostgreSQL)
./bench.sh all

# Run only MySQL benchmarks
./bench.sh mysql

# Run only PostgreSQL benchmarks
./bench.sh pgsql
```

The script performs the following actions:

1. Starts Docker containers for PHP, MySQL, and PostgreSQL.
2. Waits for databases to become healthy.
3. Pins the PHP process and database processes to specific CPU cores to ensure stable measurements.
4. Executes `phpbench` within the PHP container.

### Results and Analysis

[](#results-and-analysis)

Typical results (measured on PHP 8.4, Opcache enabled, Xdebug disabled):

DatabaseModeSubjectNo CacheWith CacheImprovement**MySQL**Server-side PreparesSimple Query~52μs~22μs**~57%****MySQL**Server-side PreparesComplex Query~59μs~24μs**~59%****MySQL**Emulated PreparesSimple Query~31μs~31μs~0%**MySQL**Emulated PreparesComplex Query~38μs~38μs~0%**PostgreSQL**NativeSimple Query~38μs~37μs~2%**PostgreSQL**NativeComplex Query~41μs~40μs~2%> **Note:**
> In the `No Cache` scenario with MySQL server-side prepared statements, the benchmark intentionally performs a full `PREPARE` on every execution.
> This represents a worst-case usage pattern where statements are not reused at all.
> The observed speedup therefore reflects the cost of repeated server-side prepares rather than the overhead of the cache itself.

#### Key Conclusions:

[](#key-conclusions)

1. **MySQL Server-Side Prepared Statements**: Reusing prepared statements provides the most significant performance improvement (often more than 2× faster). This is because it avoids repeated server-side PREPARE operations and query parsing.
2. **MySQL Emulated Prepared Statements**: When prepared statements are emulated on the client side, caching provides no measurable benefit. In this mode, PDO already avoids additional round-trips for statement preparation.
3. **PostgreSQL**: PostgreSQL handles prepared statements efficiently via the extended query protocol. In the tested scenarios, most of the execution time is dominated by network round-trips and parameter binding, resulting in only marginal gains (~1–3%) from client-side statement caching.

#### Benchmark scope:

[](#benchmark-scope)

These benchmarks are designed to measure adapter-level behavior in a controlled environment. Absolute numbers should not be treated as universal performance characteristics of MySQL or PostgreSQL.

Testing
-------

[](#testing)

Make sure the Docker containers are up and running. From the project root:

```
docker-compose -p aeatech-transaction-manager-doctrine-adapter -f docker/docker-compose.yml up -d --build
```

### Install Dependencies

[](#install-dependencies)

```
docker-compose -p aeatech-transaction-manager-doctrine-adapter -f docker/docker-compose.yml exec php-cli-8.2 composer install
```

### Run Tests

[](#run-tests)

PHP 8.2

```
docker-compose -p aeatech-transaction-manager-doctrine-adapter -f docker/docker-compose.yml exec php-cli-8.2 vendor/bin/phpunit
```

PHP 8.3

```
docker-compose -p aeatech-transaction-manager-doctrine-adapter -f docker/docker-compose.yml exec php-cli-8.3 vendor/bin/phpunit
```

PHP 8.4

```
docker-compose -p aeatech-transaction-manager-doctrine-adapter -f docker/docker-compose.yml exec php-cli-8.4 vendor/bin/phpunit
```

### Run All Tests (Bash Script)

[](#run-all-tests-bash-script)

```
for v in 8.2 8.3 8.4; do \
    echo "Testing PHP $v..."; \
    docker-compose -p aeatech-transaction-manager-doctrine-adapter -f docker/docker-compose.yml exec -T php-cli-$v vendor/bin/phpunit || break; \
done
```

### Run phpstan

[](#run-phpstan)

```
docker-compose -p aeatech-transaction-manager-doctrine-adapter -f docker/docker-compose.yml exec php-cli-8.4 vendor/bin/phpstan analyse -c phpstan.neon --memory-limit=1G
```

License
-------

[](#license)

This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance70

Regular maintenance activity

Popularity6

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 50% 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

Unknown

Total

1

Last Release

173d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/249475575?v=4)[aeatech2025](/maintainers/aeatech2025)[@aeatech2025](https://github.com/aeatech2025)

---

Top Contributors

[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")[![Shahelm](https://avatars.githubusercontent.com/u/5507279?v=4)](https://github.com/Shahelm "Shahelm (1 commits)")

---

Tags

databasemysqlpostgresqltransactionaeatechtransaction-manager

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/aeatech-transaction-manager-doctrine-adapter/health.svg)

```
[![Health](https://phpackages.com/badges/aeatech-transaction-manager-doctrine-adapter/health.svg)](https://phpackages.com/packages/aeatech-transaction-manager-doctrine-adapter)
```

###  Alternatives

[scienta/doctrine-json-functions

A set of extensions to Doctrine that add support for json query functions.

58825.9M54](/packages/scienta-doctrine-json-functions)

PHPackages © 2026

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