infocyph / dblayer
High-performance, secure database layer for PHP and multi-driver capabilities
Requires
- php: ^8.4
- ext-pdo: *
Requires (Dev)
- captainhook/captainhook: ^5.29.2
- laravel/pint: ^1.29
- pestphp/pest: ^4.4.5
- pestphp/pest-plugin-drift: ^4.1
- phpbench/phpbench: ^1.6
- rector/rector: ^2.4.1
- squizlabs/php_codesniffer: ^4.0.1
- symfony/var-dumper: ^7.3 || ^8.0.8
- tomasvotruba/cognitive-complexity: ^1.1
- vimeo/psalm: ^6.16.1
Suggests
- ext-pdo_mysql: For MySQL database support
- ext-pdo_pgsql: For PostgreSQL database support
- ext-pdo_sqlite: For SQLite database support
This package is auto-updated.
Last update: 2026-04-12 08:22:33 UTC
README
A robust, secure, and feature-rich database abstraction layer for PHP 8.4+ with multi-driver compatibility.
Features
Core Features
- ✅ Query Builder - Fluent, Laravel-like API
- ✅ Repository Layer - Thin class-based repositories on top of Query Builder
- ✅ Connection Manager - Connection pooling + read replicas
- ✅ Replica Strategies -
random,round_robin,least_latency - ✅ Multi-Driver - MySQL, PostgreSQL, SQLite
- ✅ Security - Multi-layer SQL injection protection
- ✅ Transactions - Nested transactions with savepoints
- ✅ Caching - Query result caching
- ✅ Profiling - Performance monitoring
- ✅ Events - Lifecycle hooks
- ✅ Telemetry - Query + transaction observability export
- ✅ Pagination - Length-aware, simple, and cursor pagination
Performance
- Microsecond-level benchmark results for core query-builder and transaction paths
- Connection pooling for reuse
- Memory-efficient cursor mode for large datasets
Security
- Automatic parameterization (all values bound)
- Identifier validation & escaping
- Operator whitelist
- SQL injection pattern detection
- Config-driven hardening with TLS policy controls
- Rate limiting
- Audit logging
Installation
composer require infocyph/dblayer
Quick Start
Basic Configuration
use Infocyph\DBLayer\DB; // Single connection DB::addConnection([ 'driver' => 'mysql', 'host' => 'localhost', 'port' => 3306, 'database' => 'myapp', 'username' => 'root', 'password' => 'secret', 'charset' => 'utf8mb4', ]); // Read replicas DB::addConnection([ 'driver' => 'mysql', 'read_strategy' => 'round_robin', // random | round_robin | least_latency 'read' => [ ['host' => 'replica1.example.com'], ['host' => 'replica2.example.com'], ], 'database' => 'myapp', 'username' => 'root', 'password' => 'secret', ]);
Query Controls
// Per-query timeout budget (milliseconds) DB::withQueryTimeout(500, function () { DB::select('select * from users'); }); // Absolute deadline relative to now (seconds) DB::withQueryDeadline(0.25, function () { DB::select('select * from users'); }); // Cooperative cancellation check DB::withQueryCancellation( fn () => false, fn () => DB::select('select 1') );
Telemetry
DB::enableTelemetry(); DB::select('select 1'); DB::beginTransaction(); DB::rollBack(); $snapshot = DB::telemetry(); // read buffer $exported = DB::flushTelemetry(); // read + clear
Query Builder
// SELECT $users = DB::table('users') ->select('id', 'name', 'email') ->where('active', true) ->where('age', '>=', 18) ->orderBy('created_at', 'desc') ->limit(10) ->get(); // INSERT $id = DB::table('users')->insertGetId([ 'name' => 'John Doe', 'email' => 'john@example.com', ]); // UPDATE DB::table('users') ->where('id', $id) ->update(['name' => 'Jane Doe']); // DELETE DB::table('users')->where('id', $id)->delete(); // Complex queries $orders = DB::table('orders as o') ->join('users as u', 'o.user_id', '=', 'u.id') ->leftJoin('products as p', 'o.product_id', '=', 'p.id') ->where('o.status', 'completed') ->where(function($q) { $q->where('o.total', '>', 1000) ->orWhere('u.vip', true); }) ->select('o.*', 'u.name as user_name', 'p.name as product_name') ->get(); // Aggregates $count = DB::table('users')->count(); $total = DB::table('orders')->sum('amount'); $average = DB::table('products')->avg('price');
Repository Layer
use Infocyph\DBLayer\DB; $users = DB::repository('users'); $all = $users->all(); $one = $users->find(1); $active = $users->get(fn ($q) => $q->where('active', 1));
Choosing APIs (DB vs QueryBuilder vs Repository)
- Use
DBfor infrastructure concerns: connections, transactions, retries, telemetry, pooling. - Use
DB::table()/QueryBuilderfor ad-hoc SQL shaping: joins, CTEs, dynamic filters, reporting. - Use
DB::repository()for reusable table-level rules: tenant scope, soft deletes, optimistic locking, hooks, casts.
If the same table rules appear in multiple call sites, move that logic into a repository-oriented class.
TableRepository (Repository-Oriented, Non-ORM)
use Infocyph\DBLayer\Repository\TableRepository; use Infocyph\DBLayer\Query\QueryBuilder; use Infocyph\DBLayer\Query\Repository; final class User extends TableRepository { protected static string $table = 'users'; protected static ?string $connection = 'main'; protected static function configureRepository(Repository $repository): Repository { return $repository->enableSoftDeletes()->setDefaultOrder('id', 'desc'); } protected static function configureQuery(QueryBuilder $query): QueryBuilder { return $query->where('active', '=', 1); } } $one = User::find(1); // Repository method $rows = User::where('active', '=', 1)->get(); // QueryBuilder method $stats = User::stats(); // DB facade method $reportRows = User::query('reporting')->get(); // Per-call connection override
Transactions
// Automatic transaction DB::transaction(function() { DB::table('accounts')->where('id', 1)->update(['balance' => 900]); DB::table('accounts')->where('id', 2)->update(['balance' => 1100]); DB::table('transactions')->insert(['amount' => 100]); }); // Manual transaction DB::beginTransaction(); try { // ... operations DB::commit(); } catch (\Exception $e) { DB::rollBack(); throw $e; }
Testing
composer tests composer test:code composer test:static:strict composer test:security:strict composer release:audit
Test execution is driver-aware:
- SQLite-only environments run the base test set.
- If MySQL/PostgreSQL are available (via
DBLAYER_MYSQL_*/DBLAYER_PGSQL_*env vars), matrix tests automatically run for those drivers too.
So total test count increases when more drivers are available.
Benchmarking
composer bench:run composer bench:quick composer bench:chart
Benchmarks
Latest composer bench:quick sample output:
| Subject | Mode | RSD |
|---|---|---|
benchBuildSelectSql |
11.79μs |
±0.80% |
benchSelectByPrimaryKey |
28.20μs |
±1.27% |
benchTransactionTwoPointReads |
20.66μs |
±1.61% |
benchUpdateSingleColumn |
23.45μs |
±4.21% |
Environment for this sample: PHP 8.5.4, xdebug disabled, opcache disabled, 10 revs x 3 iterations.
Security
DBLayer implements multiple layers of security:
- Parameterization - All values automatically bound
- Identifier Validation - Table/column names validated
- Operator Whitelist - Only safe operators allowed
- Injection Detection - Scans for suspicious patterns
- Rate Limiting - Prevents query flooding
- Audit Logging - Tracks all queries
Hardening controls:
DB::hardenProduction()setsenabled=true,strict_identifiers=true,require_tls=true.SecurityMode::OFFis blocked by default (allow explicitly withSecurity::allowInsecureMode(true)).security.enabled=falseandsecurity.require_tls=falserequiresecurity.allow_insecure=true.
Requirements
- PHP 8.4+
- ext-pdo
- ext-pdo_mysql (for MySQL)
- ext-pdo_pgsql (for PostgreSQL)
- ext-pdo_sqlite (for SQLite)
License
MIT License
Author
Hasan - Infocyph
Contributing
Contributions are welcome via pull requests and issues.