devsrealm / tonics-query-builder
Tonics SQL Query Builder is a library for building SQL in a modular and a hookable manner
Installs: 1 381
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/devsrealm/tonics-query-builder
Requires
- ext-pdo: *
Requires (Dev)
- ext-mbstring: *
- kahlan/kahlan: ^6.0
- vlucas/phpdotenv: ^5.6
README
A small, hookable SQL query builder with pluggable transformers for different databases. This README shows accurate usage based on the Postgres transformer spec and tests.
Installation
Install via Composer:
composer require devsrealm/tonics-query-builder
Requirements
- PHP 7.4+
- PDO extension
- A PDO driver for your database (pgsql for PostgreSQL, mysql for MySQL/MariaDB)
Quick Start (PostgreSQL)
use Devsrealm\TonicsQueryBuilder\TonicsQueryBuilder; use Devsrealm\TonicsQueryBuilder\Transformers\Postgres\PostgresTonicsQueryTransformer; use Devsrealm\TonicsQueryBuilder\Transformers\Postgres\PostgresTables; $pdo = new PDO('pgsql:host=localhost;port=5432;dbname=yourdb', 'user', 'pass', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); $tables = (new PostgresTables()) ->addTable('users', ['id','username','email','created_at','logins']) ->addTable('posts', ['id','user_id','title','content','metadata','created_at']); $qb = new TonicsQueryBuilder($pdo, new PostgresTonicsQueryTransformer(), $tables); // Build and execute a SELECT with date filter, LIKE, order, limit/offset $q = $qb->getNewQuery(); $users = $q->Select($tables->pickTable('users', ['id','username'])) ->From($tables->getTable('users')) ->WhereDate('created_at', '>=', '2025-01-01') ->WhereLike('username', 'ali') ->OrderByAsc('"username"') ->Take(10) ->Skip(0) ->FetchResult();
Date/Time
// CAST for date/time filters and TO_CHAR for formatting $q = $qb->getNewQuery(); $q->Select()->DateFormat('created_at', '%Y-%m-%d %H:%i:%s')->As('formatted_date') ->From('users') ->WhereTime('created_at', '>=', '10:00:00'); $row = $q->FetchFirst();
String pattern matching
$q = $qb->getNewQuery(); $q->Select('username') ->From('users') ->WhereLike('username', 'john') // username LIKE '%john%' ->WhereStarts('email', 'admin') // email LIKE 'admin%' ->WhereEnds('username', '123'); // username LIKE '%123' $rows = $q->FetchResult();
JSON helpers (jsonb)
// Extract JSON field $q = $qb->getNewQuery(); $q->Select('title') ->Select($q->Q()->JsonExtract('metadata', 'author'))->As('author') ->From('posts') ->Where('title', '=', 'First Post'); $first = $q->FetchFirst(); // Nested path and containment filter $q = $qb->getNewQuery(); $posts = $q->Select('title') ->From('posts') ->WhereJsonContains('metadata', 'tags', '["featured"]', '$.') ->OrderByAsc('"title"') ->FetchResult(); // Update/merge/remove JSON $q = $qb->getNewQuery(); $q->Update('posts') ->Set('metadata', $q->Q()->JsonSet('metadata', 'author', '"Updated Author"')) ->Where('title', '=', 'First Post') ->Exec(); $q = $qb->getNewQuery(); $q->Select()->JsonMergePatch('metadata', '{"new_field": "value"}'); $sql = $q->getSqlString();
Upsert (ON CONFLICT)
$q = $qb->getNewQuery(); $data = [ ['username' => 'alice', 'email' => 'alice@example.com', 'logins' => 1], ['username' => 'bob', 'email' => 'bob@example.com', 'logins' => 1], ]; // Specify conflict columns $q->InsertOnDuplicate('users', $data, [ 'conflict' => ['username'], 'set' => ['email', 'logins'] ]); // Or, specify a constraint name $q->InsertOnDuplicate('users', $data, [ 'constraint' => 'users_pkey', 'set' => ['email', 'logins'] ]); // Batch large datasets with chunk size (e.g., 50) $q->InsertOnDuplicate('users', $data, [ 'conflict' => ['username'], 'set' => ['email'] ], 50);
Null-safe equals
// MySQL `<=>` is transformed to Postgres `IS NOT DISTINCT FROM` $q = $qb->getNewQuery(); $withNullEmails = $q->Select('username') ->From('users') ->Where('email', '<=>', null) ->OrderByAsc('"username"') ->FetchResult();
Key differences from MySQL/MariaDB
- Identifier quoting: PostgreSQL uses double quotes (")
- Date filtering: CAST(col AS DATE) instead of DATE(col)
- Time filtering: CAST(col AS TIME)
- Date formatting: TO_CHAR() (MySQL tokens are converted by the transformer)
- LIKE concatenation: uses
||instead of CONCAT() - JSON: uses jsonb operators (
#>>,@>,||, jsonb_set) - Upsert: uses
ON CONFLICT ... DO UPDATE - Null-safe equals:
IS NOT DISTINCT FROM
Quick Start (MySQL)
use Devsrealm\TonicsQueryBuilder\TonicsQueryBuilder; use Devsrealm\TonicsQueryBuilder\Transformers\MySQL\MySQLTonicsQueryTransformer; use Devsrealm\TonicsQueryBuilder\Transformers\MariaDB\MariaDBTables; // reuse backtick-quoting tables $pdo = new PDO('mysql:host=localhost;dbname=yourdb;charset=utf8mb4', 'user', 'pass', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); $tables = (new MariaDBTables()) ->addTable('users', ['id','username','email','created_at']); $qb = new TonicsQueryBuilder($pdo, new MySQLTonicsQueryTransformer(), $tables); // Example: Insert and return inserted rows (emulated) $q = $qb->getNewQuery(); $data = [ ['username' => 'alice', 'email' => 'alice@example.com'], ['username' => 'bob', 'email' => 'bob@example.com'], ]; $inserted = $q->InsertReturning('users', $data, ['id','username','email'], 'id'); // Example: Basic SELECT $q = $qb->getNewQuery(); $users = $q->Select($tables->pickTable('users', ['id','username'])) ->From($tables->getTable('users')) ->Where('username', 'LIKE', '%ali%') ->OrderByAsc('`username`') ->Take(10) ->FetchResult();
Quick Start (MariaDB)
use Devsrealm\TonicsQueryBuilder\TonicsQueryBuilder; use Devsrealm\TonicsQueryBuilder\Transformers\MariaDB\MariaDBTonicsQueryTransformer; use Devsrealm\TonicsQueryBuilder\Transformers\MariaDB\MariaDBTables; $pdo = new PDO('mysql:host=localhost;dbname=yourdb;charset=utf8mb4', 'user', 'pass', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); $tables = (new MariaDBTables()) ->addTable('users', ['id','username','email','created_at']); $qb = new TonicsQueryBuilder($pdo, new MariaDBTonicsQueryTransformer(), $tables); // Example: Basic SELECT $q = $qb->getNewQuery(); $users = $q->Select($tables->pickTable('users', ['id','username','email'])) ->From($tables->getTable('users')) ->Where('created_at', '>=', '2025-01-01') ->OrderByAsc('`username`') ->Take(10) ->FetchResult();
Testing
This repo ships with Kahlan specs. To run tests (requires a local PostgreSQL and PHP):
- Windows (cmd.exe):
vendor\bin\kahlan.bat
- Linux/macOS:
vendor/bin/kahlan
See TESTING.md for environment variables and setup details.
License
MIT