PDOdb is a lightweight, framework-agnostic PHP database library providing a unified API across MySQL, MariaDB, PostgreSQL, SQLite, and Microsoft SQL Server (MSSQL).
Built on top of PDO with zero external dependencies, it offers:
Core Features:
- Fluent Query Builder - Intuitive, chainable API for all database operations
- Cross-Database Compatibility - Automatic SQL dialect handling (MySQL, MariaDB, PostgreSQL, SQLite, MSSQL)
- 80+ Helper Functions - SQL helpers for strings, dates, math, JSON, aggregations, and more (REPEAT, REVERSE, LPAD, RPAD emulated for SQLite; REGEXP operations supported across all dialects)
Performance:
- Query Caching - PSR-16 integration for result caching (10-1000x faster repeated queries)
- Query Compilation Cache - Cache compiled SQL strings (10-30% performance improvement)
- Prepared Statement Pool - Automatic statement caching with LRU eviction (20-50% faster repeated queries)
- Query Performance Profiling - Built-in profiler for tracking execution times, memory usage, and slow query detection
Advanced Features:
- Window Functions - Advanced analytics with ROW_NUMBER, RANK, LAG, LEAD, running totals, moving averages
- Common Table Expressions (CTEs) - WITH clauses for complex queries, recursive CTEs for hierarchical data, materialized CTEs for performance optimization
- LATERAL JOINs - Correlated subqueries in FROM clause for PostgreSQL, MySQL, and MSSQL (CROSS APPLY/OUTER APPLY)
- Set Operations - UNION, INTERSECT, EXCEPT for combining query results with automatic deduplication
- JSON Operations - Native JSON support with consistent API across all databases
- Full-Text Search - Cross-database FTS with unified API (MySQL FULLTEXT, PostgreSQL tsvector, SQLite FTS5)
- Read/Write Splitting - Horizontal scaling with master-replica architecture and load balancing
- Sharding - Horizontal partitioning across multiple databases with automatic query routing (range, hash, modulo strategies)
- ActiveRecord Pattern - Optional lightweight ORM for object-based database operations with relationships (hasOne, hasMany, belongsTo, hasManyThrough), eager/lazy loading, and query scopes
Developer Experience:
- CLI Tools - Database management, user management, dump/restore, migration generator, seed generator, model generator, schema inspector, and interactive query tester (REPL)
- Enhanced EXPLAIN - Automatic detection of full table scans, missing indexes, and optimization recommendations
- Exception Hierarchy - Typed exceptions for precise error handling
- Enhanced Error Diagnostics - Query context, sanitized parameters, and debug information in exceptions
- SQL Formatter/Pretty Printer - Human-readable SQL output for debugging with indentation and line breaks
- Query Debugging - Comprehensive debug information and query inspection tools
- PSR-14 Event Dispatcher - Event-driven architecture for monitoring, auditing, and middleware
- Plugin System - Extend PdoDb with custom plugins for macros, scopes, and event listeners
Production Ready:
- Fully Tested - 2325 tests, 7759 assertions across all dialects
- Type-Safe - PHPStan level 8 validated, PSR-12 compliant
- Zero Memory Leaks - Production-tested memory management with automatic cursor cleanup
- Connection Retry - Automatic retry with exponential backoff
- Transactions & Locking - Full transaction support with table locking and savepoints for nested transactions
- Batch Processing - Memory-efficient generators for large datasets with zero memory leaks
Additional Capabilities:
- Bulk Operations - CSV/XML/JSON loaders, multi-row inserts, UPSERT support
- INSERT ... SELECT - Fluent API for copying data between tables with QueryBuilder, subqueries, and CTE support
- UPDATE/DELETE with JOIN - Update and delete operations with JOIN clauses (MySQL/MariaDB/PostgreSQL/MSSQL)
- MERGE Statements - INSERT/UPDATE/DELETE based on match conditions (PostgreSQL/MSSQL native, MySQL/SQLite emulated)
- Schema Introspection - Query indexes, foreign keys, and constraints programmatically
- DDL Query Builder - Production-ready fluent API for creating, altering, and managing database schema (tables, columns, indexes, foreign keys, constraints) with Yii2-style methods, partial indexes, fulltext/spatial indexes, cross-dialectal support, and dialect-specific types (MySQL ENUM/SET, PostgreSQL UUID/JSONB/arrays, MSSQL UNIQUEIDENTIFIER/NVARCHAR, SQLite type affinity)
- Database Migrations - Version-controlled schema changes with rollback support (Yii2-inspired)
- Database Seeds - Populate database with initial or test data, batch tracking, rollback support
- Advanced Pagination - Full, simple, and cursor-based pagination with metadata
- Export Helpers - Export results to JSON, CSV, and XML formats
- DISTINCT & DISTINCT ON - Remove duplicates with full PostgreSQL DISTINCT ON support
- FILTER Clause - Conditional aggregates (SQL:2003 standard) with automatic MySQL fallback to CASE WHEN
Inspired by ThingEngineer/PHP-MySQLi-Database-Class and Yii2 framework
Perfect for:
- ✅ Beginners - Simple, intuitive API with zero configuration needed
- ✅ Cross-database projects - Switch between MySQL, PostgreSQL, SQLite, MSSQL without code changes
- ✅ Performance-critical apps - Built-in caching, query optimization, profiling
- ✅ Modern PHP - Type-safe, PSR-compliant, PHP 8.4+ features
vs. Raw PDO:
- ✅ Fluent query builder instead of manual SQL strings
- ✅ Automatic parameter binding (SQL injection protection built-in)
- ✅ Cross-database compatibility out of the box
- ✅ Helper functions for common operations
vs. Eloquent/Doctrine:
- ✅ Zero dependencies (no framework required)
- ✅ Lightweight (no ORM overhead)
- ✅ Direct SQL access when needed
- ✅ Better performance for complex queries
- ✅ Optional ActiveRecord pattern available
- Requirements
- Installation
- 📖 Documentation
- 📚 Examples
- Quick Example
- 5-Minute Tutorial
- Configuration
- Quick Start
- JSON Operations
- Advanced Usage
- CLI Tools
- Error Handling
- Performance Tips
- Helper Functions
- API Reference
- Dialect Differences
- Frequently Asked Questions
- Migration Guide
- Troubleshooting
- Testing
- Contributing
- License
- PHP: 8.4 or higher
- PDO Extensions:
pdo_mysqlfor MySQL/MariaDBpdo_pgsqlfor PostgreSQLpdo_sqlitefor SQLitesqlsrvfor Microsoft SQL Server (requires Microsoft ODBC Driver for SQL Server)
- Supported Databases:
- MySQL 5.7+ / MariaDB 10.3+
- PostgreSQL 9.4+
- SQLite 3.38+
- Microsoft SQL Server 2019+ / Azure SQL Database
Check if your SQLite has JSON support:
sqlite3 :memory: "SELECT json_valid('{}')"Install via Composer:
composer require tommyknocker/pdo-database-classFor 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-masterFastest way to get started: Use the interactive wizard to configure your project:
vendor/bin/pdodb initThe wizard will guide you through:
- Database connection settings (MySQL, PostgreSQL, SQLite, MSSQL)
- Configuration file format (
.envorconfig/db.php) - Directory structure creation (migrations, models, repositories, services)
- Connection testing
- Advanced options (caching, table prefix, multiple connections)
See CLI Tools Documentation for more details.
Complete documentation is available in the documentation/ directory with 56+ detailed guides covering all features:
- Getting Started - Installation, configuration, your first connection
- Core Concepts - Connection management, query builder, parameter binding, dialects
- Query Builder - SELECT, DML, filtering, joins, aggregations, subqueries
- JSON Operations - Working with JSON across all databases
- Advanced Features - Transactions, batch processing, bulk operations, UPSERT, query scopes, query macros, plugin system
- Error Handling - Exception hierarchy, enhanced error diagnostics with query context, retry logic, logging, monitoring
- Helper Functions - Complete reference for all helper functions
- Best Practices - Security, performance, memory management, code organization
- API Reference - Complete API documentation
- Cookbook - Common patterns, real-world examples, troubleshooting
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:
- Basic - Connection, CRUD, WHERE conditions
- Intermediate - JOINs, aggregations, pagination, transactions, savepoints
- Advanced - Connection pooling, bulk operations, UPSERT, subqueries, MERGE, window functions, CTEs
- JSON Operations - Complete guide to JSON features
- Helper Functions - String, math, date/time, NULL, comparison helpers
- Performance - Query caching, compilation cache, profiling, EXPLAIN analysis
- ActiveRecord - Object-based operations, relationships, scopes
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.shFastest 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-classUse the interactive wizard:
vendor/bin/pdodb initAlternative: 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();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 masterQuery 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.
// 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]db- 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 performance
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# 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/ModelsSee 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 timestamp
String 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 select
Filtering:
where(...)/andWhere(...)/orWhere(...)- Add WHERE conditionswhereIn(...)/whereNotIn(...)- IN / NOT IN conditionswhereNull(...)/whereNotNull(...)- NULL checkswhereBetween(...)- BETWEEN conditionsjoin(...)/leftJoin(...)/rightJoin(...)- Add JOIN clauses
Data Manipulation:
insert(array)- Insert single rowinsertMulti(array)- Insert multiple rowsupdate(array)- Update rowsdelete()- Delete rows
Execution:
get()- Execute SELECT, return all rowsgetOne()- Execute SELECT, return first rowgetValue()- Execute SELECT, return single value
Full Reference: See API Reference Documentation for complete method list and signatures.
PDOdb handles most differences automatically, but here are some key points:
UPSERT:
- MySQL:
ON DUPLICATE KEY UPDATE - PostgreSQL/SQLite:
ON CONFLICT ... DO UPDATE SET
Use onDuplicate() for portable UPSERT:
$db->find()->table('users')->onDuplicate([ 'age' => Db::inc() ])->insert(['email' => 'user@example.com', 'age' => 25]);JSON Functions:
- MySQL: Uses
JSON_EXTRACT,JSON_CONTAINS - PostgreSQL: Uses
->,->>,@>operators - SQLite: Uses
json_extract,json_each
All 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! 2325+ tests, 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-sqlite3Solution: 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- MySQL/MariaDB: Running instance on localhost:3306
- PostgreSQL: Running instance on localhost:5432
- SQLite: No setup required (uses
:memory:) - MSSQL: Running instance on localhost:1433
Contributions are welcome! Please follow these guidelines:
- Open an issue first for new features or bug reports
- Include failing tests that demonstrate the problem
- Follow PSR-12 coding standards
- Write tests for all new functionality
- Test against all five dialects (MySQL, MariaDB, PostgreSQL, SQLite, MSSQL)
- 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 open source. See LICENSE file for details.
Inspired by ThingEngineer/PHP-MySQLi-Database-Class and Yii2 framework
Built with ❤️ for the PHP community.