Skip to content

fix(session): declare PhpDb Sql runtime exceptions in session sources - #27

Merged
tyrsson merged 1 commit into
1.0.xfrom
fix/analyzer-exceptions
Nov 2, 2026
Merged

tyrsson merged 1 commit into
1.0.xfrom
fix/analyzer-exceptions

Conversation

@tyrsson

@tyrsson tyrsson commented Nov 2, 2026

Copy link
Copy Markdown
Member

Symptom

Renovate #25 changes only composer.lock, and both Mago (PHP 8.4) and Mago (PHP 8.5) fail on it. The failures are genuine, not a stale baseline: with the bumped dependency installed, mago analyze reports 11 errors in src/Session.

Root cause

#25 moves the dev dependency php-db/phpdb from source reference ed4a407c2f51b951e0261da0bf93baacb23d2e98 to 1570a6122cde75699394a6a38cf17335c3b1d622 on the 0.6.x branch.

At 1570a612, php-db/phpdb src/Sql/Sql.php declares @throws Exception\RuntimeException on buildSqlString() and prepareStatementForSqlObject(), and src/Sql/Select.php and src/Sql/Delete.php carry a class level @property ?Where $where annotation. Neither is present at ed4a407, so the findings appear only once the lock is refreshed.

Changes

unhandled-thrown-type for PhpDb\Sql\Exception\RuntimeException. Propagation is the intended behaviour: a failed prepare or execute must surface to the caller, so each method now declares what it throws. No exception is caught, no control flow changes, and runtime behaviour is identical.

Method Why
InitDbCommand::executeDdl calls Sql::buildSqlString()
InitDbCommand::execute calls executeDdl(); cascaded once the callee declared the throw
DbSessionHandler::destroy calls prepareStatementForSqlObject()
DbSessionHandler::gc calls prepareStatementForSqlObject()
DbSessionHandler::read calls prepareStatementForSqlObject()
DbSessionHandler::write calls prepareStatementForSqlObject()
DbSessionHandler::updateTimestamp delegates to write(); cascaded
DbSessionHandler::validateId delegates to read(); cascaded
PhpDbSessionPersistence::initializeSessionFromRequest calls prepareStatementForSqlObject()
PhpDbSessionPersistence::persistSession calls prepareStatementForSqlObject() and the private destroy()
PhpDbSessionPersistence::destroy (private) calls prepareStatementForSqlObject()

Fixing the eight reported sites exposed three more at their callers, so eleven @throws lines are added in total, plus the SqlRuntimeException import in each of the three files.

possible-method-access-on-null at DbSessionHandler lines 98 and 121 and PhpDbSessionPersistence line 134 is baselined rather than fixed in source.

The access cannot be null at runtime. Select::__get() and Delete::__get() both return $this->getWhere(), and getWhere() is return $this->where ??= new Where();. The nullable type comes only from the class level @property ?Where $where annotation on the vendor classes, which is expected to be corrected upstream. A null === $where guard in the consumer would be an unreachable defensive branch that can only be covered by stubbing vendor Sql and Delete and injecting them into the readonly $sql property by reflection, so the three findings are recorded in analysis-baseline.toml until the vendor annotation is fixed. The two entries added to the baseline are the blocks mago analyze --generate-baseline produces for those findings, copied verbatim; mago reports no outdated entries against them.

Guard findings were not touched and remain clean.

Gates

Run locally against php-db/phpdb 1570a612, with the same mago 1.59.0 the CI pins:

  • mago format --check: All files are already formatted.
  • mago lint: No issues found.
  • mago analyze: exit 0, found 8 issues: 4 warning(s), 4 help message(s), Filtered out 4 issues based on the baseline file. The 8 remaining are the pre-existing mixed-assignment warnings in SessionPayload, DbSessionHandler and PhpDbSessionPersistence and the redundant-comparison / redundant-condition help messages in SchemaFactory; they are identical at ed4a407.
  • mago guard: No issues found.
  • unit suite: OK (70 tests, 141 assertions)
  • integration suite: 29 of 30 pass. The one error is SchemaFactoryIntegrationTest::itCreatesPrefixedTableOnPgsql failing with could not find driver; pdo_pgsql is not installed in this environment, and the failure is identical before and after the change.
  • Infection was not run locally, by instruction; it runs in CI.

Run locally against the currently locked php-db/phpdb ed4a407 (what this PR's own CI sees):

  • mago format --check, mago lint, mago guard: clean.
  • mago analyze: exit 0, same 8 pre-existing warnings and help messages, plus Your baseline file contains 3 entries for issues that no longer exist. That warning is expected: the three baseline entries describe findings that only exist at 1570a612. It is informational and does not fail the run, and it disappears once Lock file maintenance #25 is rebased onto this branch.

Scope

composer.lock is untouched. php-db/phpdb is untouched; the vendor tree was swapped locally only to reproduce and was restored with composer reinstall php-db/phpdb before committing. No suppression comments were added.

Renovate PR #25 moves the php-db/phpdb dev dependency from source
reference ed4a407c2f51b951e0261da0bf93baacb23d2e98 to
1570a6122cde75699394a6a38cf17335c3b1d622 on the 0.6.x branch. At
1570a612, Sql::buildSqlString() and Sql::prepareStatementForSqlObject()
declare PhpDb\Sql\Exception\RuntimeException, so mago analyze reports
unhandled-thrown-type at every call site in this package that was not
declaring it, and possible-method-access-on-null where the code reads
the magic `where` property of Select and Delete.

Declare the exception with @throws where propagation is intended:
InitDbCommand::executeDdl and execute; DbSessionHandler::destroy, gc,
read, write, updateTimestamp and validateId;
PhpDbSessionPersistence::initializeSessionFromRequest, persistSession
and the private destroy. No exception is caught, no control flow is
changed and runtime behaviour is identical; only docblocks and imports
change.

The three possible-method-access-on-null findings are recorded in
analysis-baseline.toml rather than fixed in source. Select::__get() and
Delete::__get() both return getWhere(), which is
`return $this->where ??= new Where();`, so the null branch is
unreachable at runtime; the nullable type comes only from the
`@property ?Where $where` annotation on the vendor classes, which is
expected to be corrected upstream. The baseline entries are the two
issue blocks mago itself generates for those findings.

composer.lock is untouched; the dependency change belongs to #25.
@codecov

codecov Bot commented Nov 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tyrsson
tyrsson merged commit 953c091 into 1.0.x Nov 2, 2026
12 checks passed
@tyrsson
tyrsson deleted the fix/analyzer-exceptions branch November 2, 2026 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant