Skip to content

InitORM/QueryBuilder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

InitORM QueryBuilder

Packagist Version Total Downloads PHP Version License PHPUnit PHPStan PHP_CodeSniffer

A lightweight, dialect-aware SQL query builder for PHP. It turns fluent calls into a SQL string plus a separate parameter bag suitable for direct execution with PDO — without ever concatenating user values into SQL.

InitORM QueryBuilder is the lowest layer of the InitORM package family; it has no runtime dependencies beyond the pdo extension and is designed to be used either standalone or as part of the initorm/database and initorm/orm stack.

Why this library

  • Safe by default — every value goes through a collision-safe parameter bag. Raw fragments are opt-in via RawQuery.
  • Dialect aware — identifier escaping is delegated to pluggable drivers for MySQL/MariaDB, PostgreSQL, SQLite, plus a no-op generic driver.
  • Tiny and predictable — single namespace, no service container, no reflection, no annotations; the whole thing is around 1 600 lines of code.
  • Battle-tested clause DSL — comparison operators, BETWEEN, IN, LIKE family (like / startLike / endLike), NULL checks, REGEXP, SOUNDEX, FIND_IN_SET, sub-queries, parenthesized groups, closure-based JOIN ON expressions.

Requirements

  • PHP ≥ 8.1
  • ext-pdo (only needed by the consumer at execution time; the builder itself does not require an open connection)

Installation

composer require initorm/query-builder

Quick start

use InitORM\QueryBuilder\QueryBuilder;

$qb = new QueryBuilder('mysql');

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

// $sql ─────────────────────────────────────────────────────────────────
// SELECT `u`.`id`, `u`.`name`
//   FROM `users` AS `u`
//  WHERE `u`.`status` = 1 AND `u`.`country` = :country
//  ORDER BY `u`.`id` DESC
//  LIMIT 20

$pdo = new PDO('mysql:host=localhost;dbname=app', 'app', 'secret');
$stmt = $pdo->prepare($sql);
$stmt->execute($qb->getParameter()->all());
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

INSERT

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

echo $qb->generateInsertQuery();
// INSERT INTO `users` (`name`, `email`, `created`)
//   VALUES (:name, :email, NOW());

Sub-query in WHERE IN

$qb->select('u.name')
   ->from('users AS u')
   ->whereIn('u.id', $qb->subQuery(function (QueryBuilder $sub) {
       $sub->select('id')->from('roles')->where('name', 'admin');
   }));
// SELECT `u`.`name` FROM `users` AS `u`
//  WHERE `u`.`id` IN (SELECT `id` FROM `roles` WHERE `name` = :name)

Closure-based JOIN ON

$qb->select('p.title', 'u.name')
   ->from('posts AS p')
   ->innerJoin('users AS u', function (QueryBuilder $j) {
       $j->on('u.id', 'p.user_id')
         ->where('u.active', 1);
   });

Batch UPDATE (CASE/WHEN)

$qb->from('posts')
   ->set(['id' => 1, 'title' => 'First',  'views' => 100])
   ->set(['id' => 2, 'title' => 'Second', 'views' =>  42]);

echo $qb->generateUpdateBatchQuery('id');
// UPDATE `posts`
//    SET `title` = CASE WHEN `id` = 1 THEN :title WHEN `id` = 2 THEN :title_1 ELSE `title` END,
//        `views` = CASE WHEN `id` = 1 THEN 100   WHEN `id` = 2 THEN 42         ELSE `views` END
//  WHERE `id` IN (1, 2)

Supported drivers

String Driver class Escape char
'mysql' Drivers\MySqlDriver `
'pgsql' / 'postgres' / 'postgresql' Drivers\PostgreSqlDriver "
'sqlite' Drivers\SqliteDriver `
null (or anything unknown) Drivers\GenericDriver (no quoting) (none)

A custom dialect can be added by extending Drivers\AbstractDriver and setting the NAME and ESCAPE_CHAR class constants.

Documentation

Full developer documentation with runnable examples lives in docs/ — see docs/en/index.md for the table of contents.

Security

InitORM QueryBuilder is built around the rule "user input is a value, never an identifier or a SQL fragment". Defenses shipped in 2.0.0:

  • Identifier hardeningescapeIdentifier() rejects ; and -- so query-breakout characters in a column or table name cannot survive the escape pass (relevant especially on PostgreSQL, where PDO allows multi-statement queries by default).
  • LIKE wildcard auto-escape%, _, and \ inside user-supplied LIKE values are escaped by default. Opt out with $qb->raw(...) when raw wildcards are intentional.
  • Strict placeholder regex — placeholder names are now tightly bound to ^:\w+$.
  • FIND_IN_SET parameter fix (B28) — a pre-2.0.0 inversion bug inlined raw user strings as SQL; fixed.

The full threat model, residual application-level concerns (ORDER BY whitelisting, value-shaped function detection), and a complete regression suite live in docs/en/security.md and tests/SecurityTest.php.

Report vulnerabilities through the organization-wide security policy.

Tests, lint, static analysis

composer install
composer test     # phpunit (with pcov line-coverage summary)
composer cs       # PHP_CodeSniffer (PSR-12)
composer cs-fix   # phpcbf — auto-fix style violations
composer stan     # PHPStan level 6
composer qa       # cs-ci + stan + test

The repository ships with GitHub Actions workflows under .github/workflows/ that run the same checks on every push and pull request, across the PHP 8.1 → 8.4 matrix.

Current numbers: 293 tests / 391 assertions / 96.46 % line coverage.

Contributing

The contribution workflow, code style, and pull-request template are shared across the InitORM organization. See InitORM/.github → CONTRIBUTING and the PR template. A short summary:

  1. Branch from master.
  2. Stick to PSR-12; run composer qa before opening a PR.
  3. Add tests for new behavior — the test suite is the contract.
  4. Reference issues with Fixes #123 / Refs #123.

Security issues should follow the disclosure process in InitORM/.github → SECURITY.

Versioning

This package follows Semantic Versioning. The behavioral and structural changes between 1.x and 2.x are listed in CHANGELOG.md.

License

MIT — see LICENSE.

Credits

Authored and maintained by Muhammet ŞAFAK <info@muhammetsafak.com.tr>. Issues and contributions are welcome on GitHub.

About

Lightweight, dialect-aware SQL query builder for PHP 8.1+ with safe parameterized output for PDO. MySQL, PostgreSQL & SQLite.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages