Skip to content
Muhammet Şafak edited this page May 24, 2026 · 1 revision

FAQ

Common questions about InitORM QueryBuilder.

General

Q. Does this library actually execute queries against the database?

No. It only builds SQL strings and a parameter bag. You hand both to PDO yourself. The builder never opens a connection.

$stmt = $pdo->prepare($qb->generateSelectQuery());
$stmt->execute($qb->getParameter()->all());

Q. How does it compare to Doctrine DBAL, Laravel's query builder, …

Different goals:

  • Doctrine DBAL — full database-abstraction layer with schema introspection, migrations, type coercion.
  • Laravel query builder — tightly coupled to the Eloquent / Illuminate ecosystem.
  • InitORM QueryBuilder — small, framework-agnostic, zero runtime dependencies. Pairs naturally with initorm/dbal for connection management or with raw PDO when you don't need a connection layer.

If you need schema migrations and a connection layer, pick a heavier toolkit. If you want to drop a small builder into an existing PDO setup without inheriting a framework, this is the right tool.

Q. Is it production-ready?

Yes. 324 unit tests, 97 % line coverage, PHPStan level 6, PSR-12, CI on PHP 8.1–8.4. The 2.0.0 release also fixed a documented SQL-injection vector (FIND_IN_SET, B28 in the CHANGELOG) and added a dedicated security regression suite.

Setup

Q. Which PHP version is required?

PHP 8.1+. The CI matrix covers 8.1 / 8.2 / 8.3 / 8.4.

Q. What dependencies does the library need?

Just ext-pdo — and that's only needed by your application code at execution time. The builder itself doesn't open a connection. Dev dependencies are PHPUnit, PHP_CodeSniffer and PHPStan.

Q. Do I need a specific PDO driver?

No. You pick the dialect driver (= identifier-quoting strategy) when you construct the builder:

new QueryBuilder('mysql');     // backticks
new QueryBuilder('pgsql');     // double-quotes
new QueryBuilder('sqlite');    // backticks
new QueryBuilder();            // no quoting

PDO compatibility is your responsibility downstream — point any PDO flavor at the generated SQL.

Query building

Q. How do I add a SQL function like NOW() to my query?

Use RawQuery:

$qb->set('updated_at', $qb->raw('NOW()'));
$qb->where('created_at', '>=', $qb->raw('NOW() - INTERVAL 7 DAY'));

See Raw Queries.

Q. How do I write a sub-query?

$qb->whereIn('user_id', $qb->subQuery(function ($sub) {
    $sub->select('id')->from('admins');
}));

See Sub Queries.

Q. How do I parenthesize WHERE conditions?

$qb->where('status', 1)
   ->group(function ($g) {
       $g->where('type', 3)->where('type', 4);
   });
// WHERE status = 1 AND (type = 3 AND type = 4)

See Grouped Conditions.

Q. Why does my integer comparison emit WHERE id = 5 instead of WHERE id = :id?

Integers are inlined directly into the SQL — they're always numeric, so parameter binding adds no safety. Strings, floats, booleans and other scalar types go through the bag. See Parameters.

Q. How do I write IS NULL instead of = NULL?

Use the dedicated helper:

$qb->whereIsNull('deleted_at');
// WHERE `deleted_at` IS NULL

Passing PHP null to where('col', null) would just produce the bare column reference — use whereIsNull / whereIsNotNull for explicit NULL checks.

Q. Does where('id', 5) mean the same as where('id', '=', 5)?

Yes. The value-shortcut swaps the operator and value when:

  • you supplied only two arguments, and
  • the operator slot is not actually a SQL operator.

See WHERE Clauses § "The value-shortcut".

Q. Why does where(...)->orWhere(...) actually emit OR?

It does in v2.0.0+. Before v2 there was a long-standing bug (B26) where the AND-bucket and OR-bucket were joined with " AND ", silently collapsing top-level orX() calls into AND. Fixed in 2.0.0; see the CHANGELOG.

Drivers

Q. Can I add support for another database (Oracle, MS SQL, …)?

Yes. Extend AbstractDriver:

final class OracleDriver extends AbstractDriver
{
    protected const NAME = 'oracle';
    protected const ESCAPE_CHAR = '"';
}

See Drivers § "Adding a custom driver" for the integration patterns.

Q. Do I have to pick a driver?

No — passing null (or any unknown string) gives you GenericDriver, which does no identifier quoting. Useful for tests or for SQL that already pre-escapes identifiers.

Security

Q. Is the builder safe against SQL injection?

Yes, for values. Every user value flows through the parameter bag. The defense breaks down when you pass user input as an identifier (table or column name) or as a SQL fragment via RawQuery — both are caller-side responsibilities. See Security for the full threat model.

Q. I'm worried about 'NOW()' ending up in a WHERE value slot — is that a real risk?

It can be, if $_GET['id'] happens to look like 'CURRENT_USER()' or similar. The library inlines such strings as SQL function calls (intentional, so programmers can write $qb->set('updated_at', 'NOW()') without ceremony). Mitigation: always coerce / validate user input before passing it to a value slot. See Security §V1.

Q. What about LIKE wildcards in user search input?

v2.0.0 auto-escapes %, _, and \ inside any user value passed to the LIKE family. To opt out (e.g. you really want wildcards), pass a RawQuery. See Security §V4.

Q. How do I report a security issue?

Follow the organization-wide disclosure process. Do not open public issues for security problems.

Upgrading

Q. I'm on v1. What breaks when I upgrade?

A lot — v2 is a deliberate breaking release. See Migration from v1 for the complete walkthrough.

Q. Can I keep v1.x for a while longer?

Yes, the 1.x line still exists on Packagist. But v1 has a documented SQL-injection vector (FIND_IN_SET) and the LIKE / B26 / B27 footguns, so upgrading is recommended.

Contributing

Q. Where do I file a bug?

GitHub issues.

Q. How do I run the tests locally?

composer install
composer test     # phpunit
composer cs       # PSR-12
composer stan     # PHPStan level 6
composer qa       # all of the above

Q. Where's the contributing guide?

See the organization-wide CONTRIBUTING.

"Why doesn't it …"

Q. … have an INSERT … ON DUPLICATE KEY UPDATE method?

Not implemented as a first-class helper. Use RawQuery to append the tail; see Recipes § "Upsert".

Q. … support window functions?

OVER (PARTITION BY ...) clauses aren't first-class but RawQuery slots right in. See Recipes § "Ranking & windowed counts".

Q. … emit SUBSTRING(col FROM offset FOR length)?

selectMid() emits MySQL's MID(col, offset, length). For PostgreSQL/SQLite use RawQuery:

$qb->select($qb->raw('SUBSTRING(name FROM 1 FOR 5) AS prefix'));

Q. … have schema migrations?

That's a different concern. Reach for a migration library (e.g. Phinx) — the builder is intentionally scope-limited.


Next: Migration from v1

Clone this wiki locally