-
Notifications
You must be signed in to change notification settings - Fork 0
Migration from v2
v3 fixes a handful of long-standing bugs and tightens the public API. Most applications will upgrade with no code changes; a few patterns need touching.
composer require initorm/database:^3.0Then read the breaking-changes list below. Each entry tells you whether you're affected and how to fix it.
| v2 | v3 |
|---|---|
>=7.4 (declared, but actually broken — dependencies need 8.0+) |
^8.1 |
v3 honestly requires PHP 8.1 — matching the actual constraint of initorm/query-builder ^2.0. If you're stuck on PHP 8.0 or older, stay on initorm/database ^2.x.
A second call throws DatabaseException. If you really want to swap, use the new explicit replaceImmutable():
- DB::createImmutable($newCfg); // silently overrode previous
+ DB::replaceImmutable($newCfg); // explicit swapcreateImmutable() is the safe default; replaceImmutable() is the escape hatch.
create() / createBatch() / update() / updateBatch() / delete() now return bool true on successful execution (and throw on failure). They no longer return numRows() > 0 — which used to be misleading when an UPDATE found rows but didn't change any values.
// Don't write this anymore — it's always true when execution succeeded:
- if ($db->update('users', $data, ['id' => 1])) {
- $ok = true;
- }
// Do one of these:
+ try {
+ $db->update('users', $data, ['id' => 1]);
+ $changed = $db->affectedRows(); // 0 = matched-no-change OR no match
+ } catch (\InitORM\Database\Exceptions\DatabaseException $e) {
+ // execution failure
+ }insertId() still returns the inserted id (now typed string|false).
| v2 | v3 |
|---|---|
Untyped, actually returned PDO's string|false
|
Typed string|false
|
The interface and facade @method annotation used to claim int|string|false, which didn't match the implementation. Now everything agrees on string|false — matching \PDO::lastInsertId().
If you have code like $id = (int) $db->insertId(), it still works.
Exceptions thrown inside the closure are no longer swallowed. The original throwable is reachable via getPrevious():
- $ok = $db->transaction(function () { ... });
- if (!$ok) {
- // ??? — no way to know what failed
- }
+ try {
+ $db->transaction(function () { ... });
+ } catch (\InitORM\Database\Exceptions\DatabaseException $e) {
+ $original = $e->getPrevious();
+ // handle $original
+ }attempt: 0 now also throws (DatabaseInvalidArgumentException) instead of silently doing nothing.
In v2, this would silently bleed:
DB::where('id', '=', 1)->delete('users');
$rows = DB::read('users')->asAssoc()->rows(); // returned 0 rows in v2 — WHERE leaked!In v3, the structure and parameter bag are wiped in a finally block after every CRUD execution, so the second read correctly returns all remaining users.
If you relied on the old leaking behaviour (please don't), explicitly re-chain the clauses.
// v2: SQLSTATE[HY093] — :id placeholder in SQL but not in params
DB::read('users', null, ['id' => 5]);
// v3: works correctly
DB::read('users', null, ['id' => 5]);If you had read() calls with $conditions and were working around the bug by using where() chains instead, you can now use the shortcut directly.
Constructor signatures on interfaces are LSP-hostile. The interface now only declares behavioural methods; concrete implementations are free to define their own constructor signatures.
If you implemented DatabaseInterface yourself, your constructor no longer needs to match the abstract signature.
DB is now a strictly static facade — instantiation throws (the constructor is private). If you had (new DB())->... anywhere, switch to the static form DB::....
The old name was ambiguous — builder could mean "the inner builder" or "a new sibling Database". The new name says what it does. The old builder() is kept as a deprecated thin alias for the duration of the v3 line:
- $sibling = $db->builder();
+ $sibling = $db->withFreshBuilder();- PHPStan level 6 clean.
- PSR-12 clean (PHPCS in CI).
- 49 unit tests / 90% line coverage.
-
CI workflows (
phpunit,phpcs,phpstan,composer-validate) for PHP 8.1–8.4. -
affectedRows()new method — returns the rowCount of the most recent CRUD call. -
__clone()properly deep-copies the inner builder. -
QueryBuilderFactoryInterfacecan be injected into the constructor — useful for tests. -
All exceptions carry descriptive messages (v2 threw empty
new DatabaseException()in several places). -
All
@methodannotations on the facade match the actual QueryBuilder signatures (a handful were stale in v2). -
README +
docs/rewritten end-to-end.
| If you do this in v2… | …do this in v3 |
|---|---|
if ($db->create(...)) { … } |
Same — still works, true == success
|
if (!$db->update(...)) { return false; } |
Wrap in try/catch and inspect affectedRows()
|
$db->transaction($fn) and check the return |
try { $db->transaction($fn) } catch (DatabaseException $e) |
DB::createImmutable(...) repeatedly in tests |
Call DB::replaceImmutable(...) in setUp()
|
(new DB())->... |
DB::... |
$db->builder() |
$db->withFreshBuilder() (old name still works) |
Implement DatabaseInterface with own constructor |
Just remove __construct from the interface usage |
-
Bump the constraint:
composer require initorm/database:^3.0
If composer refuses because of PHP version, upgrade PHP to 8.1+ first.
-
Search-and-replace
(new DB())withDB::. The instance form never worked anyway, but cleanup is cheap. -
Wrap your
transaction()calls intry/catchif you previously relied on the boolean return for error handling. -
Search for
numRows() > 0after CRUD calls. If the test was "did the SQL succeed?", remove the check (success is now signaled by no exception). If it was "did any row change?", switch toaffectedRows(). -
Run your test suite. Most apps see no failures.
-
Optional polish: rename
builder()→withFreshBuilder()for the call sites you spot.
There's no direct v1 → v3 migration guide. The recommended path is v1 → v2 → v3:
- For v1 → v2, see the v2.0 release notes.
- Then follow this page.
Most v1 → v2 changes are about namespace and dependency restructuring (initorm/database was split out of an old monolithic package). Code-level changes are minimal.
If something broke that this guide doesn't cover, please open an issue with:
- Your
composer.lockresolved versions - A minimal reproducer
- The actual vs expected behaviour
Migration friction reports are gold — they sharpen this guide.
- Architecture — what changed under the hood
-
Static Facade — for the
createImmutable/replaceImmutabledistinction - Transactions — for the new exception semantics
InitORM Database · MIT · maintained by Muhammet ŞAFAK · part of the InitORM stack
Getting Started
Core Operations
Cross-Cutting
Reference
Upgrading
Project