Search by

wilycode / database-driver-pdo

szymonpietrek-design

PDO database driver implementing WilyCode database contracts, with a query parameter binder.

Package info

github.com/wilycode/database-driver-pdo

pkg:composer/wilycode/database-driver-pdo

Statistics

Installs: 3

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-10-12 23:01 UTC

This package is auto-updated.

Last update: 2026-10-12 23:05:28 UTC


README

PHP 8.4+ License: MIT Tests

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.

Explore the WilyCode ecosystem →

Features

  • Implement wilycode/database-driver's DatabaseInterface using PDO.
  • Accept an existing PDO instance or create one with PdoDatabase::connect().
  • Support named and zero-based positional SQL parameters with automatic basic type detection.
  • Return associative arrays for fetch() and fetchAll() even when PDO's default fetch mode is configured differently.
  • Wrap query errors in QueryException, including PDO preparation, binding, and execution failures reported as false.
  • Commit successful transactions, roll back on thrown exceptions, and reject nested transactions.
  • Convert wilycode/database-query parameter types using PdoBinder.

Requirements

  • PHP 8.4 or newer.
  • PHP pdo extension, plus a driver for your database (such as pdo_mysql, pdo_pgsql, or pdo_sqlite).
  • wilycode/database-driver ^0.1.
  • wilycode/database-query ^0.1 (used by PdoBinder).

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.