faster-php / db
A drop-in replacement for PDO with connection pooling, lazy connect, auto-reconnect, and prepared statement caching.
Requires (Dev)
- dealerdirect/phpcodesniffer-composer-installer: ^1.0
- phpunit/phpunit: ^11.5
- roave/security-advisories: dev-latest
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-27 15:52:55 UTC
README
FasterPHP/Db is a high-performance, drop-in replacement for PHP's native PDO class. It enhances standard database operations with advanced features designed for robust, modern applications β all while retaining full compatibility with PDO interfaces and expectations.
If you're tired of dealing with intermittent MySQL timeouts or disconnects, repetitive statement preparation, or clunky connection logic, this package gives you a clean, extensible, and efficient solution that just works β no learning curve required.
Key features:
- β
Drop-in
PDOreplacement (extendsPDO, not just wraps it) - π Auto-reconnect on lost or timed-out connections with configurable retry/backoff
- π‘οΈ Transaction-aware reconnect protection
- π Managed transactions via
transaction(), with safe nesting - π Lazy-loading and internal connection pooling
- β‘οΈ Prepared statement caching for reduced overhead
- π Optional PSR-3 logging support
- π§± Custom
DbStatementclass for enhanced control
π Installation
composer require faster-php/db
β Requirements
- PHP 8.2 or higher
- A supported PDO database (e.g. MySQL, PostgreSQL, SQLite, etc.)
- PDO extension (
ext-pdo)
π¦ Basic Usage
use FasterPhp\Db\Db; $db = new Db( dsn: 'mysql:host=localhost;dbname=mydb', username: 'myuser', password: 'mysecret', ); $stmt = $db->prepare('SELECT * FROM users WHERE id = :id'); $stmt->execute([':id' => 1]); $user = $stmt->fetch(PDO::FETCH_ASSOC);
π Auto-Reconnect
If a connection is lost (e.g. due to a timeout), FasterPhp\Db automatically reconnects, re-prepares (where necessary), and re-executes the statement.
Configurable Retry with Exponential Backoff
The DefaultStrategy supports configurable retry attempts with exponential backoff:
use FasterPhp\Db\Db; use FasterPhp\Db\Reconnect\DefaultStrategy; $strategy = new DefaultStrategy( maxAttempts: 3, // Try up to 3 times (default: 1) baseDelayMs: 100, // Start with 100ms delay (default: 100) backoffMultiplier: 2.0 // Double the delay each attempt (default: 2.0) ); $db = new Db( dsn: 'mysql:host=localhost;dbname=mydb', username: 'myuser', password: 'mysecret', reconnectStrategy: $strategy );
With these settings, reconnect attempts will wait 100ms, then 200ms, then 400ms between retries.
Transaction-Aware Reconnect Protection
Reconnecting mid-transaction would silently lose uncommitted changes. FasterPhp\Db prevents this by throwing a DbException if a reconnectable error occurs during an active transaction:
$db->beginTransaction(); $db->exec('INSERT INTO users (name) VALUES ("Alice")'); // If connection is lost here, DbException is thrown instead of reconnecting // This prevents silent data loss
This protection works for both explicit transactions (beginTransaction()) and raw SQL transactions (BEGIN/START TRANSACTION).
Managed Transactions
transaction() runs a callable as a single transaction and returns its result:
$userId = $db->transaction(function () use ($db) { $db->prepare('INSERT INTO users (name) VALUES (?)')->execute(['Alice']); $userId = (int)$db->lastInsertId(); $db->prepare('INSERT INTO audit (user_id) VALUES (?)')->execute([$userId]); return $userId; });
If no transaction is active, transaction() owns one: it begins, runs the work and commits. If the work throws, it rolls back and rethrows the original exception, even if the rollback itself fails.
If a transaction is already active (from beginTransaction(), raw SQL, or an enclosing transaction() call), the work joins it: nothing is begun, committed or rolled back, and any exception propagates to the owner. This lets transactional methods call each other safely:
function createUser(Db $db, string $name): int { return $db->transaction(function () use ($db, $name) { $db->prepare('INSERT INTO users (name) VALUES (?)')->execute([$name]); return (int)$db->lastInsertId(); }); } $db->transaction(function () use ($db) { createUser($db, 'Alice'); createUser($db, 'Bob'); }); // Both users are committed together, or neither is
Points to be aware of:
- Recovery after connection loss: if the connection is lost inside an owned transaction, the original exception is rethrown and the connection is discarded, so the next operation reconnects normally rather than being refused as a lost transaction.
- No retries: the work is run at most once. Retrying after a deadlock or connection loss is left to the caller, since the work may have side effects outside the database.
- No savepoints: a nested call shares the outer transaction. If the outer work catches an exception from a nested call, the nested call's writes are still committed with the rest.
- Lost commit acknowledgement: if the connection drops while the commit is in flight, the server may or may not have committed. The error is rethrown, but the outcome cannot be known from the client.
PSR-3 Logging
You can attach a PSR-3 compatible logger to monitor reconnect events:
use Psr\Log\LoggerInterface; $db->setLogger($logger); // Reconnect events are logged at WARNING level with DSN and error details
Custom Reconnect Strategy
You can configure reconnect patterns via Config\ArrayProvider or inject a custom ReconnectStrategy by implementing Reconnect\StrategyInterface.
βοΈ Statement Caching
Prepared statements are cached internally to avoid repeated preparation overhead:
$stmt1 = $db->prepare('SELECT * FROM users WHERE id = :id'); $stmt2 = $db->prepare('SELECT * FROM users WHERE id = :id'); // $stmt1 === $stmt2
To clear the statement cache (e.g. after schema changes or to free memory):
$db->clearStatementCache();
π§ Advanced Usage
π‘ ConnectionManager
The ConnectionManager class enables application-wide connection pooling and shared configuration. Itβs ideal for large applications, services, or frameworks that require multiple named database instances or shared lifecycle control.
use FasterPhp\Db\ConnectionManager; use FasterPhp\Db\Config\ArrayProvider; $config = new ArrayProvider([ 'main' => [ 'dsn' => 'mysql:host=127.0.0.1;dbname=main_db', 'username' => 'root', 'password' => '', 'options' => [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ], ], 'analytics' => [ 'dsn' => 'mysql:host=127.0.0.1;dbname=analytics', 'username' => 'readonly', 'password' => '', ], ]); $manager = new ConnectionManager($config); // Get a shared Db instance $db = $manager->get('main');
π‘
ConnectionManagerwill automatically reuse idle connections if available.
βοΈ Configuration Providers
The Config\ArrayProvider class is a simple implementation of the ProviderInterface that lets you define connection settings in plain PHP arrays.
To use a different configuration source (like .env, JSON, or YAML), implement:
interface ProviderInterface { public function get(string $name): ?array; }
This keeps your configuration logic separate from your application logic, and makes it easy to test or extend.
π§ͺ Testing
Tests require a MySQL server with a db_test database. You can configure your environment using .env variables or phpunit.xml:
DB_DSN="mysql:host=localhost;dbname=db_test"
DB_USER="db_test"
DB_PASS="db_test"
Run tests with:
vendor/bin/phpunit
To generate a coverage report (requires Xdebug or PCOV):
vendor/bin/phpunit --coverage-html build/coverage
π€ Contributing
Contributions are welcome! To get started:
- Fork the repository
- Create a new branch (
git checkout -b feature/my-feature) - Commit your changes (
git commit -am 'Add feature') - Push to the branch (
git push origin feature/my-feature) - Open a pull request
π§ Code of Conduct
Please be respectful and constructive in all interactions. We aim to foster a professional, welcoming environment.
π Security
If you discover a security vulnerability, please report it privately via GitHub or email the maintainer. Avoid opening public issues for sensitive disclosures.
π License
This package is open-source software licensed under the MIT license.