Skip to content
Muhammet Şafak edited this page May 24, 2026 · 2 revisions

InitORM QueryBuilder

A lightweight, dialect-aware SQL query builder for PHP. Fluent calls in, SQL string + a separate parameter bag out — ready to execute with PDO, without ever concatenating user input into SQL.

Packagist PHP Version License PHPUnit PHPStan

composer require initorm/query-builder

Hello query

use InitORM\QueryBuilder\QueryBuilder;

$qb = new QueryBuilder('mysql');

$qb->select('id', 'name')
   ->from('users')
   ->where('status', 1)
   ->andWhere('country', 'TR')
   ->orderBy('id', 'DESC')
   ->limit(20);

echo $qb->generateSelectQuery();
// SELECT `id`, `name` FROM `users`
//  WHERE `status` = 1 AND `country` = :country
//  ORDER BY `id` DESC LIMIT 20

print_r($qb->getParameter()->all());
// [ ':country' => 'TR' ]

Hand the SQL and the bag to PDO:

$stmt = $pdo->prepare($qb->generateSelectQuery());
$stmt->execute($qb->getParameter()->all());
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

That's the entire mental model.

Why this library

  • Safe by default. Every value flows through a collision-safe parameter bag (:col, :col_1, :col_2, …). Raw SQL is opt-in via RawQuery.
  • Dialect aware. Identifier escaping is driver-specific: backticks for MySQL / SQLite, double quotes for PostgreSQL, no-op for the generic driver.
  • Tiny and predictable. Single namespace, no service container, no reflection, no annotations. About 1 600 lines of code total.
  • Battle-tested clause DSL. Comparison operators, BETWEEN, IN, the LIKE family (like / startLike / endLike), NULL checks, REGEXP, SOUNDEX, FIND_IN_SET, parenthesized groups, closure-based JOIN ON expressions, closure-based sub-queries.
  • 324 tests, 97 % line coverage. Including a dedicated security regression suite covering SQL-injection vectors classic query builders often leave open.

Wiki contents

Chapter What's in it
Getting Started Install, the first query, the factory, PDO execution.
SELECT Queries Projections, aggregates, string functions, COALESCE, CONCAT, GROUP BY, ORDER BY, LIMIT/OFFSET.
WHERE Clauses Comparison matrix, value-shortcut, NULL, BETWEEN, IN, LIKE family, REGEXP, SOUNDEX, FIND_IN_SET.
JOIN Queries innerJoin, leftJoin, rightJoin, leftOuterJoin, rightOuterJoin, selfJoin, naturalJoin, closure-based ON.
INSERT UPDATE DELETE set / addSet, single-row + batch INSERT, single + CASE/WHEN batch UPDATE, DELETE.
Sub Queries subQuery() in WHERE IN / FROM / JOIN / standalone.
Grouped Conditions group() for parenthesized WHERE/HAVING/ON.
Raw Queries RawQuery: when and how to use it responsibly.
Parameters The parameter bag, auto-suffix, NULL short-circuit, PDO handoff.
Drivers MySQL/PgSQL/SQLite/Generic; writing a custom dialect driver.
Security Threat model, defenses, application-level residual risks.
Recipes Pagination, soft-delete, dynamic filters, upsert, ranking, …
API Reference Categorized table of every public method.
FAQ Frequently asked questions.
Migration from v1 Breaking changes between 1.x and 2.x, plus an upgrade walkthrough.

At a glance

// SELECT with JOIN, GROUP BY, HAVING
$qb->select('u.id', 'u.name')
   ->selectCount('p.id', 'post_count')
   ->from('users', 'u')
   ->leftJoin('posts AS p', 'p.user_id = u.id')
   ->where('u.active', 1)
   ->groupBy('u.id')
   ->having('post_count', '>', 5)
   ->orderBy('post_count', 'DESC')
   ->limit(10);

// INSERT
$qb->from('users')
   ->set(['name' => 'Muhammet', 'email' => 'info@muhammetsafak.com.tr']);
$qb->generateInsertQuery();

// UPDATE with CASE/WHEN batch
$qb->from('posts')
   ->set(['id' => 1, 'views' => 100])
   ->set(['id' => 2, 'views' => 42]);
$qb->generateUpdateBatchQuery('id');

// Sub-query inside WHERE IN
$qb->whereIn('u.id', $qb->subQuery(function ($sub) {
    $sub->select('id')->from('roles')->where('name', 'admin');
}));

Where else to look

  • In-repo docsdocs/en/ ships the same chapters as Markdown files alongside the source.
  • Source codesrc/ is small enough to read in an afternoon; if a corner is unclear, the source is the canonical reference.
  • Teststests/ every wiki example is mirrored by a test that pins the expected SQL.

Reporting a problem

License

MIT. © Muhammet ŞAFAK.

Clone this wiki locally