wilycode / database-driver-pdo
PDO database driver implementing WilyCode database contracts, with a query parameter binder.
Requires
- php: ^8.4
- ext-pdo: *
- wilycode/database-driver: ^0.1
- wilycode/database-query: ^0.1
Requires (Dev)
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-12 23:05:28 UTC
README
A lightweight PDO implementation of the WilyCode database driver contract, with a companion query parameter binder.
Wrap a native PDO connection behind DatabaseInterface, execute parameterized SQL, fetch associative results, and use strict transactions. PdoBinder translates compiled query parameter types into PDO binding types.
WilyCode PHP Ecosystem
Part of WilyCode — independent, reusable PHP components, each with its own repository, releases, and Composer installation.
Features
- Implement
wilycode/database-driver'sDatabaseInterfaceusing PDO. - Accept an existing
PDOinstance or create one withPdoDatabase::connect(). - Support named and zero-based positional SQL parameters with automatic basic type detection.
- Return associative arrays for
fetch()andfetchAll()even when PDO's default fetch mode is configured differently. - Wrap query errors in
QueryException, including PDO preparation, binding, and execution failures reported asfalse. - Commit successful transactions, roll back on thrown exceptions, and reject nested transactions.
- Convert
wilycode/database-queryparameter types usingPdoBinder.
Requirements
- PHP 8.4 or newer.
- PHP
pdoextension, plus a driver for your database (such aspdo_mysql,pdo_pgsql, orpdo_sqlite). wilycode/database-driver^0.1.wilycode/database-query^0.1(used byPdoBinder).
Both WilyCode dependencies must be available to Composer before installation succeeds. The SQLite test suite additionally requires pdo_sqlite.
Installation
composer require wilycode/database-driver-pdo
Quick start
For an SQLite example (requires pdo_sqlite):
<?php require __DIR__ . '/vendor/autoload.php'; use WilyCode\Database\DriverPdo\PdoDatabase; $db = PdoDatabase::connect('sqlite::memory:'); $db->execute('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL)'); $db->execute('INSERT INTO users (email) VALUES (:email)', ['email' => 'user@example.test']); $user = $db->fetch('SELECT id, email FROM users WHERE email = :email', [ 'email' => 'user@example.test', ]); $users = $db->fetchAll('SELECT id, email FROM users ORDER BY id'); $count = $db->fetchColumn('SELECT COUNT(*) FROM users'); $lastId = $db->getLastInsertId(); // String, as returned by PDO.
fetch() returns [] when there is no row, fetchAll() returns [] for no matches, and fetchColumn() returns null when there is no row. A SQL NULL column also produces null.
Connect with MySQL or an existing PDO instance
use WilyCode\Database\DriverPdo\PdoDatabase; $db = PdoDatabase::connect( 'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4', getenv('DB_USER') ?: null, getenv('DB_PASSWORD') ?: null, ); // Or reuse a PDO connection that your application already manages: $db = new PdoDatabase($pdo);
The default connection options include PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION and PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC. Explicit connection options take precedence. Query results from fetch() and fetchAll() remain associative regardless of that fetch mode override.
Never commit real database passwords to source control. Parameter binding protects values, not table or column identifiers: never concatenate untrusted identifiers into SQL.
SQL parameters and types
Named placeholders work with keys such as email or :email. Positional ? placeholders use zero-based PHP arrays, for example ['name', 18].
use PDO; $db->execute('INSERT INTO users (email) VALUES (?)', ['second@example.test']); // Optional explicit PDO parameter types; keys may include a leading colon. $db->fetch('SELECT email FROM users WHERE id = :id', ['id' => '1'], [ ':id' => PDO::PARAM_INT, ]);
Without explicit types, null is bound with PDO::PARAM_NULL, booleans with PDO::PARAM_BOOL, integers with PDO::PARAM_INT, and other values with PDO::PARAM_STR. Supply a valid PDO parameter type where this default is insufficient.
Integrate with WilyCode Database Query
PdoBinder implements BinderInterface from wilycode/database-query and maps the query's ParameterType values to PDO constants. It does not execute queries itself.
use WilyCode\Database\DriverPdo\PdoBinder; use WilyCode\Database\Query\Query; $compiled = Query::select('users', 'u') ->columns(['id' => 'id', 'email' => 'email']) ->where('u.id', 1) ->compile(); $bound = (new PdoBinder())->bind($compiled); $row = $db->fetch($bound->sql, $bound->params, $bound->types);
Query::select() only compiles SQL; PdoBinder maps parameter types, and PdoDatabase executes it. The query builder currently uses some MySQL-style identifier quoting, so verify compiled SQL against your database dialect. The PdoDatabase class itself does not require using a query builder or query executor to execute ordinary SQL.
Transactions
$result = $db->transaction(function (PdoDatabase $transaction): string { $transaction->execute( 'INSERT INTO users (email) VALUES (:email)', ['email' => 'another@example.test'], ); return $transaction->getLastInsertId(); });
The callback receives the active PdoDatabase instance. A successful callback commits and returns its result; a thrown exception triggers rollback and is rethrown. Nested calls to transaction() are not supported and raise NestedTransactionException. This adapter does not implement savepoints.
Error handling
use WilyCode\Database\Driver\Exception\ConnectionException; use WilyCode\Database\Driver\Exception\QueryException; use WilyCode\Database\Driver\Exception\TransactionException; try { $db->execute('INSERT INTO users (email) VALUES (:email)', ['email' => 'user@example.test']); } catch (QueryException $exception) { // Decide whether to retry, log, or return an application-level error. }
PdoDatabase::connect() wraps PDO connection failures in ConnectionException; query failures in QueryException; and transaction setup or commit failures in TransactionException. Error context records parameter keys, not the parameter values. However, exception SQL text or chained PDO exceptions may still reveal sensitive information: never expose raw exception details to clients or untrusted logs.
Related packages
| Package | Purpose |
|---|---|
wilycode/database-driver |
Shared database driver contract and exceptions |
wilycode/database-query |
Compiled query, parameter types, and binder contract |
wilycode/database-driver-mysqli |
Alternative driver based on MySQLi |
Each package is versioned and installed independently.
Development
composer install
composer validate --strict
composer test
The integration tests use SQLite in memory and require pdo_sqlite. GitHub Actions installs that extension and runs PHPUnit on PHP 8.4. Tests are included in Git, but excluded from Composer distribution archives.
Contributing
Issues and pull requests are welcome. See CONTRIBUTING.md and the issue tracker.
License
MIT © 2026 Simon Piter. See LICENSE.