Refract your queries across any database.
Intent-based database abstraction layer with unified adapters, schema inspection, and migrations. Write once. Query everywhere.
- What is DBPrism?
- Installation
- Quick Start
- Supported Databases
- Query Building API
- Schema Operations
- Schema Inspection
- Migrations
- API Reference
DBPrism is a sophisticated, framework-agnostic database abstraction layer that unifies database operations across MySQL, PostgreSQL, and SQLite.
Like a prism refracting light into its component colors, DBPrism takes your query intents and refracts them into engine-specific SQL—transparently, elegantly, and efficiently.
- 🔄 Unified Adapters — Single API for 5+ database engines
- 🎯 Intent-Based Query Building — Declarative query construction with automatic SQL rendering
- 🔍 Schema Inspection — Deep schema introspection across all engines
- 🚀 Migrations — Fluent migration API with helpers for schema transformations
- ⚡ Multi-Engine Rendering — One query intent → Multiple engine-specific SQL outputs
- 💪 Type Normalization — Consistent column types across databases
- 🧩 Framework-Agnostic — Works standalone or integrated with any framework
composer require callismart/dbprismRequirements:
- PHP 8.1+
- One or more: MySQLi, PDO, PostgreSQL, SQLite extensions
use Callismart\DBPrism\Database;
use Callismart\DBPrism\Adapters\MysqliAdapter;
use Callismart\DBPrism\DBConfigDTO;
$config = new DBConfigDTO([
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'myapp',
'driver' => 'mysql',
]);
$adapter = new MysqliAdapter($config);
$db = new Database($adapter);// Insert
$user_id = $db->insert('users', [
'name' => 'John Doe',
'email' => 'john@example.com',
]);
// Fetch
$user = $db->get_row('SELECT * FROM users WHERE id = ?', [$user_id]);
// Update
$db->update('users',
['status' => 'active'],
['id' => $user_id]
);
// Delete
$db->delete('users', ['id' => $user_id]);use Callismart\DBPrism\Query\SQLBuilder;
$builder = new SQLBuilder($db->get_driver());
// Build a SELECT query
$intent = $builder->select('id', 'name', 'email')
->from('users')
->where('status', '=', 'active')
->where('created_at', '>', '2024-01-01')
->order_by('created_at', 'DESC')
->limit(10);
$sql = $intent->build();
$bindings = $intent->get_bindings();
$users = $db->get_results($sql, $bindings);use Callismart\DBPrism\Adapters\MysqliAdapter;
$config = new DBConfigDTO([
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'myapp',
'driver' => 'mysql',
]);
$adapter = new MysqliAdapter($config);
$db = new Database($adapter);use Callismart\DBPrism\Adapters\PostgresAdapter;
$config = new DBConfigDTO([
'host' => 'localhost',
'username' => 'postgres',
'password' => 'secret',
'dbname' => 'myapp',
'driver' => 'pgsql',
]);
$adapter = new PostgresAdapter($config);
$db = new Database($adapter);use Callismart\DBPrism\Adapters\SqliteAdapter;
$config = new DBConfigDTO([
'dbname' => '/path/to/database.sqlite',
'driver' => 'sqlite',
]);
$adapter = new SqliteAdapter($config);
$db = new Database($adapter);use Callismart\DBPrism\Adapters\PdoAdapter;
$config = new DBConfigDTO([
'dsn' => 'mysql:host=localhost;dbname=myapp',
'username' => 'root',
'password' => 'secret',
'driver' => 'pdo',
]);
$adapter = new PdoAdapter($config);
$db = new Database($adapter);use Callismart\DBPrism\Adapters\WPDBAdapter;
$adapter = new WPDBAdapter();
$db = new Database($adapter);Select queries are built using the SelectionIntent class, accessed via SQLBuilder::select().
$builder = new SQLBuilder('mysql');
$intent = $builder->select('id', 'name', 'email')
->from('users');
$sql = $intent->build();
// SELECT `id`, `name`, `email` FROM `users`;$intent = $builder->select('*')
->from('orders')
->where('status', '=', 'completed')
->where('total', '>', 100);
$sql = $intent->build();
$bindings = $intent->get_bindings();// Basic comparison
->where('age', '>=', 18)
->where('name', '!=', 'Admin')
->where('email', 'LIKE', '%@example.com')
// IS NULL / IS NOT NULL
->where_null('deleted_at')
->where_not_null('verified_at')
// Direct SQL operators
->where('deleted_at', 'IS NULL')
->where('verified_at', 'IS NOT NULL')
// IN / NOT IN
->where_in('status', ['active', 'pending'])
->where_not_in('role', ['banned', 'suspended'])
// BETWEEN / NOT BETWEEN
->where_between('age', 18, 65)
->where_not_between('created_at', '2023-01-01', '2023-12-31')
// OR conditions
->where('status', '=', 'active')
->or_where('status', '=', 'pending')
// Grouped conditions
->where_group(function($q) {
$q->where('status', '=', 'active')
->or_where('status', '=', 'pending');
})
// Raw SQL
->where_raw('YEAR(created_at) = 2024', [])// INNER JOIN
->join('orders', 'users.id', '=', 'orders.user_id')
// LEFT JOIN
->left_join('profiles', 'users.id', '=', 'profiles.user_id')
// RIGHT JOIN
->right_join('departments', 'employees.dept_id', '=', 'departments.id')
// CROSS JOIN
->cross_join('statuses')$intent = $builder->select('category', 'COUNT(*) as total')
->from('products')
->group_by('category')
->order_by('total', 'DESC')
->limit(10)
->offset(0);
$sql = $intent->build();Insert queries are built using the PersistenceIntent class, accessed via SQLBuilder::insert().
$intent = $builder->insert('users')
->values([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => hash('sha256', 'secret'),
]);
$sql = $intent->build();
$bindings = $intent->get_bindings();$intent = $builder->insert('users')
->multi_values([
['name' => 'John Doe', 'email' => 'john@example.com'],
['name' => 'Jane Smith', 'email' => 'jane@example.com'],
['name' => 'Bob Wilson', 'email' => 'bob@example.com'],
]);
$sql = $intent->build();$intent = $builder->insert('users')
->set(['name' => 'John Doe', 'email' => 'john@example.com']);Update queries are built using the PersistenceIntent class, accessed via SQLBuilder::update().
$intent = $builder->update('users')
->set([
'status' => 'inactive',
'updated_at' => date('Y-m-d H:i:s'),
])
->where('id', '=', 1);
$sql = $intent->build();$intent = $builder->update('users')
->set([
'verified' => true,
'verified_at' => date('Y-m-d H:i:s'),
])
->where('email_confirmed', '=', true)
->where_null('deleted_at')
->where_in('status', ['pending', 'new']);Delete queries are built using the DeleteIntent class, accessed via SQLBuilder::delete().
$intent = $builder->delete('users')
->where('id', '=', 1);
$sql = $intent->build();$intent = $builder->delete('sessions')
->where('user_id', '=', 5)
->where('expires_at', '<', date('Y-m-d H:i:s'));Create tables using the CreateTableIntent class with fluent Column and Constraint builders.
use Callismart\DBPrism\Utils\Column;
use Callismart\DBPrism\Utils\ColumnType;
use Callismart\DBPrism\Utils\Constraint;
$builder = new SQLBuilder($db->get_driver());
$intent = $builder->create_table('users')
->add_columns([
Column::make('id')
->type(ColumnType::BIG_INT)
->auto_increment()
->unsigned()
->required(),
Column::make('name')
->type(ColumnType::VARCHAR)
->size(100)
->required(),
Column::make('email')
->type(ColumnType::VARCHAR)
->size(100)
->required(),
Column::make('password')
->type(ColumnType::VARCHAR)
->size(255)
->required(),
Column::make('created_at')
->type(ColumnType::DATETIME)
->default(DefaultColumnValue::expression('CURRENT_TIMESTAMP')),
])
->add_constraints([
Constraint::primary('users_pk')
->on('id'),
Constraint::unique('users_email_unique')
->on('email'),
]);
$sql = $intent->build();
$db->exec($sql);$intent = $builder->create_table('posts')
->add_columns([
Column::make('id')
->type(ColumnType::BIG_INT)
->auto_increment()
->unsigned()
->required(),
Column::make('user_id')
->type(ColumnType::BIG_INT)
->unsigned()
->required(),
Column::make('title')
->type(ColumnType::VARCHAR)
->size(200)
->required(),
Column::make('body')
->type(ColumnType::TEXT)
->required(),
Column::make('created_at')
->type(ColumnType::DATETIME)
->default(DefaultColumnValue::expression('CURRENT_TIMESTAMP')),
])
->add_constraints([
Constraint::primary('posts_pk')
->on('id'),
Constraint::foreign_key('posts_user_fk')
->on('user_id')
->references('users', 'id')
->on_delete('CASCADE')
->on_update('CASCADE'),
]);
$sql = $intent->build();
$db->exec($sql);use Callismart\DBPrism\Utils\ColumnType;
// Integer types
->type(ColumnType::INT)->size(11)
->type(ColumnType::BIG_INT)->unsigned()
->type(ColumnType::TINY_INT)
->type(ColumnType::SMALL_INT)
// String types
->type(ColumnType::VARCHAR)->size(100)
->type(ColumnType::CHAR)->size(10)
->type(ColumnType::TEXT)
// Numeric types
->type(ColumnType::DECIMAL)->precision(10, 2)
->type(ColumnType::FLOAT)
->type(ColumnType::DOUBLE)
// Date/Time types
->type(ColumnType::DATE)
->type(ColumnType::TIME)
->type(ColumnType::DATETIME)
->type(ColumnType::TIMESTAMP)
// Other types
->type(ColumnType::BOOLEAN)
->type(ColumnType::JSON)
->type(ColumnType::ENUM)Column::make('email')
->type(ColumnType::VARCHAR)
->size(100)
->required() // NOT NULL
->unsigned() // UNSIGNED (for numeric types)
->auto_increment() // AUTO_INCREMENT
->default('active') // DEFAULT value
->default(DefaultColumnValue::expression('CURRENT_TIMESTAMP'))Modify existing tables using the AlterTableIntent class.
$intent = $builder->alter_table('users')
->rename_column('old_name', 'new_name');
$sql = $intent->build();
$db->exec($sql);[SECTION NEEDS CLARIFICATION] — I need more context on:
- Complete API for
add_column(),modify_column(),drop_column(),drop_constraint(),drop_index()methods onAlterTableIntent - How these are called exactly
$intent = $builder->truncate_table('logs')
->restart_identity(true)
->cascade(false);
$sql = $intent->build();
$db->exec($sql);$intent = $builder->drop_table('old_table')
->if_exists();
$sql = $intent->build();
$db->exec($sql);The TableHelper provides fluent interface for table-level operations.
use Callismart\DBPrism\Migrations\Helpers\TableHelper;
$helper = new TableHelper($db, new SQLBuilder($db->get_driver()), 'users');
// Rename table
$helper->rename('new_table_name');
// Truncate table
$helper->truncate(restart: true, cascade: false);
// Drop table
$helper->drop(exists_check: true);
// Drop index
$helper->drop_index('idx_created_at');
// Access column operations
$helper->column();
// Access constraint operations
$helper->constraint();The ColumnHelper provides fluent interface for column-level operations.
use Callismart\DBPrism\Migrations\Helpers\ColumnHelper;
$helper = new ColumnHelper($db, new SQLBuilder($db->get_driver()), 'users');
// Add column
$helper->add(Column::make('phone')
->type(ColumnType::VARCHAR)
->size(20)
->nullable()
);
// Drop column
$helper->drop('deprecated_field');
// Rename column
$helper->rename('old_name', 'new_name');
// Modify column
$helper->modify(Column::make('name')
->type(ColumnType::VARCHAR)
->size(255)
);
// Change column type
$helper->changeType('status', ColumnType::ENUM);
// Check operations
$helper->exists('email'); // bool
$helper->getType('email'); // string|null
$helper->list(); // array of column namesThe ConstraintHelper provides fluent interface for constraint operations.
[SECTION NEEDS CLARIFICATION] — I need more context on:
- Complete method signatures and usage for:
add_primary_key(),add_unique(),add_foreign_key()drop_primary_key(),drop_unique(),drop_foreign_key()add_constraint(),drop(),drop_constraint()
- SQLite-specific constraint handling details
- How to properly call these methods with parameters
Inspect database schema using the Inspector class.
use Callismart\DBPrism\Inspection\Inspector;
$inspector = new Inspector($db);// List all tables
$tables = $inspector->get_all_tables();
// Check if table exists
if ($inspector->table_exists('users')) {
echo "Table exists!";
}
// Get table metadata
$meta = $inspector->get_table_metadata('users');
// Returns: ['engine' => 'InnoDB', 'charset' => 'utf8mb4', 'collation' => '...', 'row_count' => 1250, 'comment' => '']// Get all column names
$columns = $inspector->get_columns('users');
// Check if column exists
$inspector->column_exists('users', 'email');
// Get column type (normalized)
$type = $inspector->get_column_type('users', 'id');
// Get detailed column information
$details = $inspector->get_column_details('users');
// Check if column is nullable
$inspector->is_column_nullable('users', 'email');
// Get column default
$inspector->get_column_default('users', 'is_active');// Get all indexes
$indexes = $inspector->get_indexes('users');
// Check if index exists
$inspector->has_index('users', 'idx_created_at');// Get primary key
$pk = $inspector->get_primary_key('users');
// Returns: ['id'] or null// Get all foreign keys
$fks = $inspector->get_foreign_keys('orders');
// Check if foreign key exists
$inspector->has_foreign_key('orders', 'fk_orders_user');// Get unique constraints
$unique = $inspector->get_unique_constraints('users');
// Get check constraints
$checks = $inspector->get_check_constraints('products');// Get database engine type
$engine = $inspector->get_engine_type(); // 'mysql', 'pgsql', 'sqlite'
// Get server version
$version = $inspector->get_server_version();
// Get protocol version
$protocol = $inspector->get_protocol_version();
// Get host info
$host = $inspector->get_host_info();Execute multiple queries atomically with automatic rollback on failure.
try {
$result = $db->transactional(function() use ($db) {
$order_id = $db->insert('orders', ['total' => 99.99]);
$db->insert('order_items', ['order_id' => $order_id, 'product_id' => 5]);
return $order_id;
});
} catch (\Throwable $e) {
error_log("Transaction failed: " . $e->getMessage());
}-
Always Use Parameterized Queries
// ✓ Good $user = $db->get_row('SELECT * FROM users WHERE id = ?', [$id]); // ✗ Bad - SQL injection vulnerability $user = $db->get_row("SELECT * FROM users WHERE id = {$id}");
-
Use Transactions for Related Operations
$db->transactional(function() use ($db) { $order_id = $db->insert('orders', $order_data); foreach ($items as $item) { $db->insert('order_items', [...$item, 'order_id' => $order_id]); } });
-
Leverage the Inspector for Schema-Aware Logic
$inspector = new Inspector($db); if ($inspector->table_exists('users') && $inspector->column_exists('users', 'email')) { // Safe to query }
-
Handle Database Errors Gracefully
try { $result = $db->transactional(function() use ($db) { // operations }); } catch (\Throwable $e) { error_log($e->getMessage()); }
Run the test suite:
composer testContributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License — see the LICENSE file for details.
Callistus Nwachukwu
- Email: admin@callismart.com.ng
- Website: https://callismart.com.ng
For issues, questions, or feature requests:
Made with ❤️ by Callismart