-
-
Notifications
You must be signed in to change notification settings - Fork 0
Migration from v1
The 2.0.0 line is a deliberate breaking release: bug fixes (including one with SQL-injection implications), a wide architectural refactor, and a hardened security surface. This page walks you through the upgrade.
If your code only calls the public clause-builder methods
(select, from, where, andWhere, set, generate*Query,
getParameter, …), the upgrade is mostly transparent. The behavioral
fixes change a handful of edge cases that v1 silently produced wrong SQL
for — most are bugs you wanted fixed.
If you subclass BaseDriver / MySQL / PgSQL / SQLite, or you
directly reference the driver classes by name, you'll need to
rename. See § Driver renames.
If you parse the structure produced by exportQB(), the shape is
unchanged — the structure array still has the same keys and value
shapes.
- "php": ">=8.0"
+ "php": "^8.1"v2.0.0 requires PHP 8.1+. PHP 8.4 is supported (no more implicit- nullable deprecations).
- "require": {
- "initorm/query-builder": "^1.0"
- }
+ "require": {
+ "initorm/query-builder": "^2.0"
+ }This is the biggest surface for breakage. All four driver classes were renamed and the abstract base too:
| v1 (gone) | v2 |
|---|---|
InitORM\QueryBuilder\Drivers\BaseDriver |
InitORM\QueryBuilder\Drivers\AbstractDriver (now abstract) |
InitORM\QueryBuilder\Drivers\MySQL |
InitORM\QueryBuilder\Drivers\MySqlDriver |
InitORM\QueryBuilder\Drivers\PgSQL |
InitORM\QueryBuilder\Drivers\PostgreSqlDriver |
InitORM\QueryBuilder\Drivers\SQLite |
InitORM\QueryBuilder\Drivers\SqliteDriver |
(implicit default in BaseDriver) |
InitORM\QueryBuilder\Drivers\GenericDriver (explicit no-op driver) |
If you only ever passed 'mysql' / 'pgsql' / 'sqlite' as a string
to new QueryBuilder(…), you don't need to do anything — the strings
still work.
- public function escapeIdentify(string &$string): string;
- public function getDriver(): ?string;
+ public function escapeIdentifier(string $identifier): string;
+ public function getName(): ?string;-
escapeIdentify(&$s)was a by-reference mutator that also returned the value. v2'sescapeIdentifier($s)is pure — returns the escaped identifier, never mutates the input. -
getDriver()was renamed togetName()to clarify what it returns.
If you implemented your own driver, you must:
- Rename both methods.
- Drop the by-reference parameter on
escapeIdentifier.
escapeIdentifier() now raises
QueryBuilderInvalidArgumentException when the supplied identifier
contains ; or --. If you were silently constructing identifiers from
user input that contained such characters (you shouldn't have been —
that's an injection vector), the call now fails loudly. See
Security §V3.
Subtle but breaking for callers that implement the interface directly:
- public function naturalJoin(string|RawQuery $table, string|RawQuery|Closure $onStmt): self;
+ public function naturalJoin(string|RawQuery $table): static;naturalJoin() no longer takes an $onStmt argument — NATURAL JOIN
does not carry an ON clause.
- public function where(RawQuery|string $column, string $operator = '=', ...): self;
- public function having(RawQuery|string $column, string $operator = '=', ...): self;
- public function on(RawQuery|string $column, string $operator = '=', ...): self;
+ public function where(RawQuery|string $column, mixed $operator = '=', ...): static;
+ public function having(RawQuery|string $column, mixed $operator = '=', ...): static;
+ public function on(RawQuery|string $column, mixed $operator = '=', ...): static;The $operator slot is mixed (not string) — to preserve the
value-shortcut where('id', 5) where the second argument is treated as
the value when the operator slot is not a SQL operator.
- public function group(Closure $closure): self;
+ public function group(Closure $closure, string $logical = 'AND'): static;group() now declares the $logical parameter that the implementation
always accepted (was implicit before).
All return types went from self to static so subclasses inherit
their own static return from chained calls.
Most of these are bugs you might have been working around. Search your codebase for the patterns and decide whether to remove the workaround.
- // v1: $qb->andWhereNotIn('id', [1, 2]) → WHERE id IN (1, 2)
+ // v2: → WHERE id NOT IN (1, 2)- // v1: $qb->where(...)->orLike(...) → ... AND ...
- // $qb->where(...)->andLike(...) → ... OR ...
+ // v2: $qb->where(...)->orLike(...) → ... OR ...
+ // $qb->where(...)->andLike(...) → ... AND ...- // v1: $qb->orBetween('id', 10, 20) → malformed array value, AND connector lost
+ // v2: → OR id BETWEEN 10 AND 20- // v1: $qb->startLike('name', 'A') → LIKE '%A' (matches "ends with A")
+ // v2: → LIKE 'A%' (matches "starts with A")
- // v1: $qb->endLike('name', 'A') → LIKE 'A%'
+ // v2: → LIKE '%A'- // v1: new RawQuery(fn ($qb) => $qb->select(...)->from(...))
- // → TypeError "Closure cannot be converted to string"
+ // v2: → produces the inner SELECT- // v1: $qb->findInSet('roles', 'admin') → FIND_IN_SET(admin, roles)
- // ↑ "admin" is inlined verbatim — SQL injection vector for user input
+ // v2: → FIND_IN_SET(:roles, roles)
+ // ↑ parameterized- // v1: $qb->where('active', true) → WHERE active (operator was lost)
+ // v2: → WHERE active = :active- // v1: $qb->where('a', 1)->orWhere('b', 2) → WHERE a = 1 AND b = 2
+ // v2: → WHERE a = 1 OR b = 2If you depended on the buggy AND-collapse, wrap the OR side in
group(..., 'OR') for the explicit parenthesized form.
- // v1: $qb->like('name', '50%') → param :name = '%50%%' ≡ LIKE '%50%' (matches anything starting with "50")
+ // v2: → param :name = '%50\%%' (matches only strings containing the literal "50%")User input containing %, _, or \ is auto-escaped. Pass a
RawQuery to opt out:
// v2: keep raw wildcards
$qb->like('name', $qb->raw("'custom%pattern'"));
// LIKE 'custom%pattern'See Security §V4.
Every source file's license header went from a ~10-line block to a
two-line @package / @license pair. The canonical legal text now
lives in LICENSE only. This is informational only — it doesn't affect
you unless you forked the source.
If your code referenced the private __generate* helpers (it
shouldn't have — they were private by name and intent):
| v1 | v2 |
|---|---|
QueryBuilder::__generateWhereQuery |
gone — see Compiler\AbstractCompiler::compileWhere
|
QueryBuilder::__generateHavingQuery |
gone — see compileHaving
|
QueryBuilder::__generateLimitQuery |
gone — see compileLimit
|
QueryBuilder::__generateSchemaName |
gone — see compileSchemaName
|
QueryBuilder::__generateStructure |
gone — see Helper\BucketCompiler::compile
|
The compile mechanics now live in dedicated Compiler\* classes. The
public QueryBuilder::generate*Query() methods are unchanged.
-
Composer: bump
initorm/query-builderto^2.0and PHP constraint to^8.1. Runcomposer update. -
Driver class names: if you reference
BaseDriver,MySQL,PgSQLorSQLitedirectly, search-and-replace per the rename table. -
Custom drivers: rename
escapeIdentify→escapeIdentifier(drop the by-reference parameter) andgetDriver→getName. If you relied onBaseDrivernot being abstract, switch toextends AbstractDriver. -
Identifier inputs: anywhere you forwarded user input as a table or
column name (
from($_GET['table'])etc.), wrap it in a whitelist or accept thatQueryBuilderInvalidArgumentExceptionmay now fire on bad inputs. -
LIKE values: if you used
like('name', '%foo%')expecting raw wildcards, switch tolike('name', 'foo')(the builder adds the wildcards based on$type) orlike('name', $qb->raw("'%foo%'"))when you really want raw control. -
OR semantics: search for
orWhere,orLike,orBetweenetc. chains. Pre-v2 they silently behaved as AND; post-v2 they actually OR. Verify the SQL matches your intent. - Run your tests. Particularly anything that asserted the exact emitted SQL — many edge cases will now differ.
- Read the CHANGELOG for the complete list.
If something breaks in a way that isn't covered here, please open an issue with a minimal reproduction — we'll either expand this page or fix the regression.
Back to: Home
InitORM QueryBuilder — MIT licensed · authored by Muhammet ŞAFAK · part of the InitORM family · report an issue · security disclosure