| Install | |
|---|---|
composer require tommyknocker/pdo-database-class |
|
| Latest Version: | v2.12.0 |
| PHP: | ^8.4 |
PDOdb is a lightweight, framework-agnostic PHP database library providing a unified API across MySQL, MariaDB, PostgreSQL, SQLite, Microsoft SQL Server (MSSQL), and Oracle.
Built on top of PDO with zero external dependencies, it offers:
Core Features:
Performance:
Advanced Features:
Developer Experience:
explainAiAdvice() method or CLI commands. Includes MCP server for IDE/agent integration.pdodb uiProduction Ready:
Additional Capabilities:
Inspired by ThingEngineer/PHP-MySQLi-Database-Class and Yii2 framework
Perfect for:
pdodb ui)vs. Raw PDO:
vs. Eloquent/Doctrine:
pdo_mysql for MySQL/MariaDBpdo_pgsql for PostgreSQLpdo_sqlite for SQLitesqlsrv for Microsoft SQL Server (requires Microsoft ODBC Driver for SQL Server)oci for Oracle DatabaseCheck if your SQLite has JSON support:
sqlite3 :memory: "SELECT json_valid('{}')"
Install via Composer:
composer require tommyknocker/pdo-database-class
For specific versions:
# Latest 2.x version
composer require tommyknocker/pdo-database-class:^2.0
# Latest 1.x version
composer require tommyknocker/pdo-database-class:^1.0
# Development version
composer require tommyknocker/pdo-database-class:dev-master
pdodb initFastest way to get started: Use the interactive wizard to configure your project:
vendor/bin/pdodb init
The wizard will guide you through:
.env or config/db.php)See CLI Tools Documentation for more details.
Complete documentation is available in the documentation/ directory with 56+ detailed guides covering all features:
Each guide includes working code examples, dialect-specific notes, security considerations, and best practices.
Start here: Documentation Index
Comprehensive, runnable examples are available in the examples/ directory:
Each example is self-contained with setup instructions. See examples/README.md for the full catalog.
Quick start:
cd examples
# SQLite (ready to use, no setup required)
php 01-basic/02-simple-crud.php
# MySQL (update config.mysql.php with your credentials)
PDODB_DRIVER=mysql php 01-basic/02-simple-crud.php
# Test all examples on all available databases
./scripts/test-examples.sh
Fastest start: Run vendor/bin/pdodb init for interactive setup, or get started manually:
use tommyknocker\pdodb\PdoDb;
// Connect (SQLite - no setup needed!)
$db = new PdoDb('sqlite', ['path' => ':memory:']);
// Create table
$db->rawQuery('CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT,
age INTEGER
)');
// Insert
$id = $db->find()->table('users')->insert([
'name' => 'John',
'email' => 'john@example.com',
'age' => 30
]);
// Query
$users = $db->find()
->from('users')
->where('age', 18, '>')
->orderBy('name', 'ASC')
->limit(10)
->get();
// Update
$db->find()
->table('users')
->where('id', $id)
->update(['age' => 31]);
That's it! No configuration, no dependencies, just works.
composer require tommyknocker/pdo-database-class
Use the interactive wizard:
vendor/bin/pdodb init
Alternative: Manual configuration
use tommyknocker\pdodb\PdoDb;
// SQLite (easiest - no database server needed)
$db = new PdoDb('sqlite', ['path' => ':memory:']);
// Or MySQL/PostgreSQL
$db = new PdoDb('mysql', [
'host' => 'localhost',
'dbname' => 'mydb',
'username' => 'user',
'password' => 'pass'
]);
// Simple approach (raw SQL)
$db->rawQuery('CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT,
age INTEGER
)');
// Or use DDL Query Builder
$db->schema()->createTable('products', [
'id' => $db->schema()->primaryKey(),
'name' => $db->schema()->string(255)->notNull(),
'status' => $db->schema()->enum(['draft', 'published', 'archived'])
->defaultValue('draft'),
'created_at' => $db->schema()->timestamp()->defaultExpression('CURRENT_TIMESTAMP')
]);
// Create
$id = $db->find()->table('users')->insert([
'name' => 'Alice',
'email' => 'alice@example.com',
'age' => 30
]);
// Read
$users = $db->find()->from('users')->get();
$user = $db->find()->from('users')->where('id', $id)->getOne();
// Update
$db->find()->table('users')
->where('id', $id)
->update(['name' => 'Bob']);
// Delete
$db->find()->table('users')->where('id', $id)->delete();
Launch the interactive TUI Dashboard to monitor your database in real-time:
vendor/bin/pdodb ui
Monitor active queries, connection pool, cache statistics, and server metrics. Press h for help, q to quit.
Next: See Quick Start for more examples.
use tommyknocker\pdodb\PdoDb;
// MySQL
$db = new PdoDb('mysql', [
'host' => '127.0.0.1',
'username' => 'testuser',
'password' => 'testpass',
'dbname' => 'testdb',
'port' => 3306,
'charset' => 'utf8mb4',
]);
// PostgreSQL
$db = new PdoDb('pgsql', [
'host' => '127.0.0.1',
'username' => 'testuser',
'password' => 'testpass',
'dbname' => 'testdb',
'port' => 5432,
]);
// SQLite
$db = new PdoDb('sqlite', [
'path' => '/path/to/database.sqlite', // or ':memory:' for in-memory
]);
// MSSQL
$db = new PdoDb('sqlsrv', [
'host' => 'localhost',
'username' => 'testuser',
'password' => 'testpass',
'dbname' => 'testdb',
'port' => 1433,
]);
Connection Pooling:
$db = new PdoDb();
$db->addConnection('mysql_main', ['driver' => 'mysql', ...]);
$db->addConnection('pgsql_analytics', ['driver' => 'pgsql', ...]);
$db->connection('mysql_main')->find()->from('users')->get();
Read/Write Splitting:
$db->enableReadWriteSplitting(new RoundRobinLoadBalancer());
$db->addConnection('master', [...], ['type' => 'write']);
$db->addConnection('replica-1', [...], ['type' => 'read']);
// SELECTs automatically go to replicas, DML to master
Query Caching:
$cache = CacheFactory::create(['type' => 'filesystem', 'directory' => '/var/cache']);
$db = new PdoDb('mysql', $config, [], null, $cache);
$users = $db->find()->from('users')->cache(3600)->get();
See Configuration Documentation for complete configuration options.
Note: All query examples start with $db->find() which returns a QueryBuilder instance.
Get intelligent optimization recommendations for your queries using AI:
use tommyknocker\pdodb\PdoDb;
$db = PdoDb::fromEnv();
// Configure AI (or use environment variables: PDODB_AI_OPENAI_KEY, etc.)
$db = new PdoDb('mysql', [
'host' => 'localhost',
'dbname' => 'mydb',
'username' => 'user',
'password' => 'pass',
'ai' => [
'provider' => 'openai',
'openai_key' => 'sk-...',
],
]);
// Get AI-enhanced analysis
$result = $db->find()
->from('users')
->where('email', 'user@example.com')
->explainAiAdvice(tableName: 'users', provider: 'openai');
// Access base analysis (traditional EXPLAIN)
foreach ($result->baseAnalysis->issues as $issue) {
echo "Issue: {$issue->message}\n";
}
// Access AI recommendations
echo "AI Analysis:\n";
echo $result->aiAnalysis . "\n";
Or use CLI:
# Set API key
export PDODB_AI_OPENAI_KEY=sk-...
export PDODB_AI_OPENAI_MODEL=gpt-4o-mini # Optional: gpt-4, gpt-3.5-turbo
export PDODB_AI_GOOGLE_MODEL=gemini-2.5-flash # Optional: gemini-2.5-pro, gemini-2.0-flash-001, gemini-flash-latest
# Analyze query
pdodb ai query "SELECT * FROM users WHERE email = 'user@example.com'" \
--provider=openai \
--table=users
Supported Providers: OpenAI, Anthropic, Google, Microsoft, DeepSeek, Yandex, Ollama (local, no API key)
Model Selection: Configure models via environment variables (PDODB_AI_<PROVIDER>_MODEL) or config array (ai.providers.<provider>.model)
See AI Analysis Documentation for complete guide.
// SELECT
$user = $db->find()
->from('users')
->where('id', 10)
->getOne();
$users = $db->find()
->from('users')
->where('age', 18, '>=')
->get();
// INSERT
$id = $db->find()->table('users')->insert([
'name' => 'Alice',
'email' => 'alice@example.com',
'age' => 30
]);
// UPDATE
$db->find()
->table('users')
->where('id', $id)
->update(['age' => 31]);
// DELETE
$db->find()
->table('users')
->where('id', $id)
->delete();
use tommyknocker\pdodb\helpers\Db;
// WHERE conditions
$users = $db->find()
->from('users')
->where('status', 'active')
->andWhere('age', 18, '>')
->andWhere(Db::like('email', '%@example.com'))
->get();
// JOIN and GROUP BY
$stats = $db->find()
->from('users AS u')
->select(['u.id', 'u.name', 'total' => Db::sum('o.amount')])
->leftJoin('orders AS o', 'o.user_id = u.id')
->groupBy('u.id')
->having(Db::sum('o.amount'), 1000, '>')
->get();
$db->startTransaction();
try {
$userId = $db->find()->table('users')->insert(['name' => 'Alice']);
$db->find()->table('orders')->insert(['user_id' => $userId, 'total' => 100]);
$db->commit();
} catch (\Exception $e) {
$db->rollback();
}
See Query Builder Documentation for more examples.
PDOdb provides a unified JSON API that works consistently across all databases.
use tommyknocker\pdodb\helpers\Db;
// Create JSON data
$db->find()->table('users')->insert([
'name' => 'John',
'meta' => Db::jsonObject(['city' => 'NYC', 'age' => 30]),
'tags' => Db::jsonArray('php', 'mysql', 'docker')
]);
// Query JSON
$adults = $db->find()
->from('users')
->where(Db::jsonPath('meta', ['age'], '>', 25))
->get();
// Extract JSON values
$users = $db->find()
->from('users')
->select([
'id',
'name',
'city' => Db::jsonGet('meta', ['city'])
])
->get();
// Update JSON
$db->find()
->table('users')
->where('id', 1)
->update([
'meta' => Db::jsonSet('meta', ['city'], 'London')
]);
See JSON Operations Documentation for complete guide.
$users = $db->rawQuery(
'SELECT * FROM users WHERE age > :age',
['age' => 18]
);
$users = $db->find()
->from('users')
->whereIn('id', function($query) {
$query->from('orders')
->select('user_id')
->where('total', 1000, '>');
})
->get();
// Full pagination (with total count)
$result = $db->find()
->from('posts')
->orderBy('created_at', 'DESC')
->paginate(20, 1);
// Cursor pagination (most efficient)
$result = $db->find()
->from('posts')
->orderBy('id', 'DESC')
->cursorPaginate(20);
// Process in batches
foreach ($db->find()->from('users')->batch(100) as $batch) {
foreach ($batch as $user) {
processUser($user);
}
}
// Stream results (minimal memory)
foreach ($db->find()->from('users')->stream() as $user) {
processUser($user);
}
// Cache for 1 hour
$products = $db->find()
->from('products')
->where('category', 'Electronics')
->cache(3600)
->get();
See Advanced Features Documentation for complete guide.
PDOdb provides convenient command-line tools for common development tasks:
vendor/bin/pdodb <command> [subcommand] [arguments] [options]
Monitor your database in real-time with a beautiful full-screen terminal interface:
vendor/bin/pdodb ui
Features:
/ to filter tables and server variables in real-time (case-insensitive)Perfect for debugging, monitoring production databases, understanding query patterns, and managing database schema.
ui ⭐ - Interactive TUI Dashboard - Real-time database monitoring (full-screen terminal interface)ai 🤖 - AI-Powered Analysis - Get intelligent database optimization recommendations using OpenAI, Anthropic, Google, Microsoft, DeepSeek, Yandex, or Ollamadb - Manage databases (create, drop, list, check existence)user - Manage database users (create, drop, grant/revoke privileges)dump - Dump and restore database (with compression, auto-naming, rotation)migrate - Manage database migrationsschema - Inspect database schemaquery - Test SQL queries interactively (REPL)model - Generate ActiveRecord modelstable - Manage tables (info, create, drop, truncate)monitor - Monitor database queries and performanceoptimize - Database optimization analysis (analyze, structure, logs, query, db)Install bash completion for enhanced CLI experience:
# Temporary (current session)
source <(curl -s https://raw.githubusercontent.com/tommyknocker/pdo-database-class/refs/heads/master/scripts/pdodb-completion.bash)
# Permanent
curl -o ~/.pdodb-completion.bash https://raw.githubusercontent.com/tommyknocker/pdo-database-class/refs/heads/master/scripts/pdodb-completion.bash
echo "source ~/.pdodb-completion.bash" >> ~/.bashrc
# 🖥️ Launch interactive TUI Dashboard (real-time monitoring)
vendor/bin/pdodb ui
# 🤖 AI-powered analysis (requires API keys or Ollama)
vendor/bin/pdodb ai query "SELECT * FROM users WHERE email = 'user@example.com'" --provider=openai
vendor/bin/pdodb ai query "SELECT * FROM orders" --provider=anthropic --table=orders
vendor/bin/pdodb ai schema --table=users --provider=ollama
# Create database
vendor/bin/pdodb db create myapp
# Dump with compression and rotation
vendor/bin/pdodb dump --auto-name --compress=gzip --rotate=7
# Create migration
vendor/bin/pdodb migrate create create_users_table
# Generate model
vendor/bin/pdodb model make User users app/Models
See CLI Tools Documentation for complete guide.
PDOdb provides a comprehensive exception hierarchy for better error handling:
use tommyknocker\pdodb\exceptions\{
DatabaseException,
ConnectionException,
QueryException,
ConstraintViolationException,
TransactionException
};
try {
$users = $db->find()->from('users')->get();
} catch (ConnectionException $e) {
// Handle connection errors
if ($e->isRetryable()) {
// Implement retry logic
}
} catch (QueryException $e) {
// Handle query errors
error_log("Query: " . $e->getQuery());
error_log("Context: " . $e->getDescription());
} catch (ConstraintViolationException $e) {
// Handle constraint violations
error_log("Constraint: " . $e->getConstraintName());
}
All exceptions extend PDOException for backward compatibility and provide rich context information.
See Error Handling Documentation for complete guide.
For applications with repeated queries, enable result caching:
$cache = new Psr16Cache(new FilesystemAdapter());
$db = new PdoDb('mysql', $config, [], null, $cache);
$products = $db->find()
->from('products')
->where('category', 'Electronics')
->cache(3600)
->get();
Performance Impact: 65-97% faster for repeated queries with cache hits.
// ❌ Slow: Multiple single inserts
foreach ($users as $user) {
$db->find()->table('users')->insert($user);
}
// ✅ Fast: Single batch insert
$db->find()->table('users')->insertMulti($users);
// ✅ Safe: Limited results
$users = $db->find()->from('users')->limit(1000)->get();
// Process in chunks
foreach ($db->find()->from('users')->batch(100) as $batch) {
processBatch($batch);
}
See Performance Documentation for more tips.
PDOdb provides 80+ helper functions for common SQL operations:
Core Helpers:
Db::raw() - Raw SQL expressionsDb::concat() - String concatenationDb::now() - Current timestampString Operations:
Db::upper(), Db::lower(), Db::trim(), Db::substring(), Db::replace()Numeric Operations:
Db::inc(), Db::dec(), Db::abs(), Db::round(), Db::mod()Date/Time Functions:
Db::now(), Db::date(), Db::year(), Db::month(), Db::day()JSON Operations:
Db::jsonObject(), Db::jsonArray(), Db::jsonGet(), Db::jsonPath(), Db::jsonContains()Aggregate Functions:
Db::count(), Db::sum(), Db::avg(), Db::min(), Db::max()Full Reference: See Helper Functions Documentation for complete list and examples.
| Method | Description |
|---|---|
find() |
Returns QueryBuilder instance |
rawQuery(string, array) |
Execute raw SQL, returns array of rows |
rawQueryOne(string, array) |
Execute raw SQL, returns first row |
startTransaction() |
Begin transaction |
commit() |
Commit transaction |
rollBack() |
Roll back transaction |
describe(string) |
Get table structure |
indexes(string) |
Get all indexes for a table |
keys(string) |
Get foreign key constraints |
Table & Selection:
table(string) / from(string) - Set target tableselect(array|string) - Specify columns to selectFiltering:
where(...) / andWhere(...) / orWhere(...) - Add WHERE conditionswhereIn(...) / whereNotIn(...) - IN / NOT IN conditionswhereNull(...) / whereNotNull(...) - NULL checkswhereBetween(...) - BETWEEN conditionsjoin(...) / leftJoin(...) / rightJoin(...) - Add JOIN clausesData Manipulation:
insert(array) - Insert single rowinsertMulti(array) - Insert multiple rowsupdate(array) - Update rowsdelete() - Delete rowsExecution:
get() - Execute SELECT, return all rowsgetOne() - Execute SELECT, return first rowgetValue() - Execute SELECT, return single valueFull Reference: See API Reference Documentation for complete method list and signatures.
PDOdb handles most differences automatically, but here are some key points:
UPSERT:
ON DUPLICATE KEY UPDATEON CONFLICT ... DO UPDATE SETUse onDuplicate() for portable UPSERT:
$db->find()->table('users')->onDuplicate([
'age' => Db::inc()
])->insert(['email' => 'user@example.com', 'age' => 25]);
JSON Functions:
JSON_EXTRACT, JSON_CONTAINS->, ->>, @> operatorsjson_extract, json_eachAll handled transparently through Db::json*() helpers.
Full Reference: See Dialect Differences Documentation for complete guide.
No, PDOdb is a query builder with optional ActiveRecord pattern. It's lighter than full ORMs like Eloquent or Doctrine.
Yes! Use rawQuery() for complete control:
$users = $db->rawQuery('SELECT * FROM users WHERE age > :age', ['age' => 18]);
Yes! PDOdb is framework-agnostic. Works with Laravel, Symfony, Yii, or no framework at all.
Yes! 3806+ tests, 12180+ assertions, PHPStan level 8, used in production environments.
All queries use prepared statements automatically. SQL injection protection is built-in.
Yes! Pass your PDO instance:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$db = new PdoDb('mysql', ['pdo' => $pdo]);
SQLite is perfect for development - no server setup needed:
$db = new PdoDb('sqlite', ['path' => ':memory:']);
Yes! Full transaction support with savepoints:
$db->startTransaction();
try {
// Your operations
$db->commit();
} catch (\Exception $e) {
$db->rollBack();
}
Before:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM users WHERE age > :age');
$stmt->execute(['age' => 18]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
After:
$db = new PdoDb('mysql', [
'host' => 'localhost',
'dbname' => 'test',
'username' => 'user',
'password' => 'pass'
]);
$users = $db->find()->from('users')->where('age', 18, '>')->get();
Before:
User::where('active', 1)
->where('age', '>', 18)
->orderBy('name')
->limit(10)
->get();
After:
$db->find()
->from('users')
->where('active', 1)
->andWhere('age', 18, '>')
->orderBy('name', 'ASC')
->limit(10)
->get();
See Migration Guide Documentation for more examples.
Solution: Install the required PHP extension:
sudo apt-get install php8.4-mysql php8.4-pgsql php8.4-sqlite3
Solution: Check if JSON support is available:
sqlite3 :memory: "SELECT json_valid('{}')"
Problem: Using OFFSET without LIMIT in SQLite.
Solution: Always use LIMIT with OFFSET:
// ✅ Works
$db->find()->from('users')->limit(20)->offset(10)->get();
Solution: Use batch processing or streaming:
foreach ($db->find()->from('users')->batch(100) as $batch) {
processBatch($batch);
}
See Troubleshooting Documentation for more solutions.
The project includes comprehensive PHPUnit tests for all supported databases.
# Run all tests
./vendor/bin/phpunit
# Run specific dialect tests
./vendor/bin/phpunit tests/PdoDbMySQLTest.php
# Run with coverage
./vendor/bin/phpunit --coverage-html coverage
:memory:)pdo_oci extension)Contributions are welcome! Please follow these guidelines:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is open source. See LICENSE file for details.
Inspired by ThingEngineer/PHP-MySQLi-Database-Class and Yii2 framework
Built with ❤️ for the PHP community.