Repository navigation
Route the Sql throw sites through named constructors - #192
Merged
Merged
Conversation
simon-mundy
force-pushed
the
exception/pr-3-sql
branch
from
September 28, 2026 06:39
ba0e798 to
84e1538
Compare
This was referenced Sep 28, 2026
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This was referenced Sep 28, 2026
simon-mundy
force-pushed
the
exception/pr-3-sql
branch
from
September 28, 2026 22:52
84e1538 to
df142fd
Compare
simon-mundy
added a commit
to simon-mundy/phpdb
that referenced
this pull request
Sep 28, 2026
… constructors The last 5 throw sites outside the components covered by php-db#191 to php-db#194. Message text is byte-identical, verified template by template against the originals. - Add ResultSet\Exception\RuntimeException::forUnbufferedIteration() - Add forNonAdapterAwareDelegate() to Exception\ContainerException, and give the three reasons that AdapterInterfaceFactory and AbstractAdapterInterfaceFactory passed as inline strings their own factories: forMissingConfigService(), forMissingConfiguration() and forMissingDriver(), each composing the reason into the SERVICE template - Retype the delegator's non-AdapterAware guard from the root Exception\RuntimeException to ContainerException, which extends it, so the failure carries PSR-11's ContainerExceptionInterface as a container failure should - Own the "not found in container" wording as ContainerException::MISSING_SERVICE while still throwing the ServiceManager's own ServiceNotFoundException. Laminas annotates that class @Final, so this package cannot own it; the throw site renders the template itself, which docs/book/exceptions.md now records as the single documented exception to the convention - Derive the message assertions in the six test files the earlier PRs do not touch from the templates rather than restating them One literal assertion is deliberately left in test/unit/Container/TableIdentifierFactoryFactoryTest.php: it asserts Sql\Exception\InvalidArgumentException::EMPTY_SEPARATOR, a constant php-db#192 introduces, so it can only be migrated once that has landed. Signed-off-by: Simon Mundy <simon.mundy@peptolab.com>
simon-mundy
force-pushed
the
exception/pr-3-sql
branch
from
September 29, 2026 02:29
df142fd to
1948b1a
Compare
All 55 throw sites in src/Sql now call a named constructor. Message text is byte-identical, verified template by template against the original literals. - Add Sql\Predicate\Exception with an InvalidArgumentException and a RuntimeException holding the 10 templates the predicate classes use, so the messages sit with the classes that raise them rather than concentrating on one component catalogue, and register both in ExceptionHierarchyTest's EXCEPTION_CLASSES map. Neither namespace declares an ExceptionInterface: the package has one marker, PhpDb\Exception\ExceptionInterface, and every class here implements it by inheritance - Add 24 templates and factories to Sql\Exception\InvalidArgumentException and 6 to Sql\Exception\RuntimeException, the last four covering the Ddl column and check constraint messages added by php-db#183, php-db#184 and php-db#185 - Collapse nine duplicated messages onto one template each, five of which the audit on php-db#189 had missed: the four "intended to work with only the table" copies in Sql, the two parameter-count copies in AbstractSql, the two column key copies in Insert, the two read-only copies in Select, and the subject messages shared by Platform and AbstractPlatform - Fix a copy-paste bug in Platform::getSqlString(), which reported prepareStatement() as the method with no effect - Retype Between's \LogicException and AbstractSql's \ValueError to the package types, so both reach the marker interface - Declare the @throws Predicate::unnest() and Predicate::__get() never had - Cover every factory with a template test, and derive the 46 Sql message assertions from the templates rather than restating them - Re-attribute the two baselined sprintf argument-type entries to the factories that now receive those arguments, and baseline the three halstead readings the refactor shifts - Exclude src/Sql/Exception/ from too-many-methods in mago.toml. The threshold is 10 and a catalogue holds one template per condition a component can report, so the count tracks failure modes rather than anything a split would improve Signed-off-by: Simon Mundy <simon.mundy@peptolab.com>
simon-mundy
force-pushed
the
exception/pr-3-sql
branch
from
September 29, 2026 03:34
1948b1a to
a63d366
Compare
simon-mundy
added a commit
to simon-mundy/phpdb
that referenced
this pull request
Sep 29, 2026
… constructors The last 5 throw sites outside the components covered by php-db#191 to php-db#194. Message text is byte-identical, verified template by template against the originals. - Add ResultSet\Exception\RuntimeException::forUnbufferedIteration() - Add forNonAdapterAwareDelegate() to Exception\ContainerException, and give the three reasons that AdapterInterfaceFactory and AbstractAdapterInterfaceFactory passed as inline strings their own factories: forMissingConfigService(), forMissingConfiguration() and forMissingDriver(), each composing the reason into the SERVICE template - Retype the delegator's non-AdapterAware guard from the root Exception\RuntimeException to ContainerException, which extends it, so the failure carries PSR-11's ContainerExceptionInterface as a container failure should - Own the "not found in container" wording as ContainerException::MISSING_SERVICE while still throwing the ServiceManager's own ServiceNotFoundException. Laminas annotates that class @Final, so this package cannot own it; the throw site renders the template itself, which docs/book/exceptions.md now records as the single documented exception to the convention - Derive the message assertions in the six test files the earlier PRs do not touch from the templates rather than restating them One literal assertion is deliberately left in test/unit/Container/TableIdentifierFactoryFactoryTest.php: it asserts Sql\Exception\InvalidArgumentException::EMPTY_SEPARATOR, a constant php-db#192 introduces, so it can only be migrated once that has landed. Signed-off-by: Simon Mundy <simon.mundy@peptolab.com>
tyrsson
previously approved these changes
Sep 29, 2026
simon-mundy
dismissed
tyrsson’s stale review
September 29, 2026 03:49
The merge-base changed after approval.
Signed-off-by: Simon Mundy <46739456+simon-mundy@users.noreply.github.com>
tyrsson
approved these changes
Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Types of changes
Checklist:
Description
PR 3 of 6 from #189, and the largest: all 55 throw sites in
src/Sql.Message text is byte-identical to what
src/Sqlproduced before, verified template by template against the original literals rather than by eye.New namespace.
Sql\Predicate\Exceptionholds the 10 templates the predicate classes raise, so they sit with the classes that raise them. This is purely additive:Sql\Predicate\Exception\InvalidArgumentExceptionextendsSql\Exception\InvalidArgumentException, so anything catching the parent is unaffected.The namespace deliberately has no
ExceptionInterfaceof its own. An earlier revision of this PR added one for symmetry with the other components, and it earned nothing: because both classes extend theirSql\Exceptioncounterparts, they are already caught bySql\Exception\ExceptionInterface, bySql\Exception\InvalidArgumentExceptionand by the root marker. A narrower marker would only have collapsed the sameInvalidArgumentException/RuntimeExceptionsplit a second time, over a strict subset of whatSql\Exception\ExceptionInterfacealready covers — four classes against two. It was unreleased, so dropping it costs nothing. The split leaves 24 factories onSql\Exception\InvalidArgumentExceptionand 6 onSql\Exception\RuntimeException, against 9 and 1 on the new classes. Four of those 24 cover theSql\Ddlcolumn and check-constraint messages added by #183, #184 and #185, which landed on0.6.xafter the audit on #189 was taken.Nine duplicated messages collapse onto one template each, five of which the audit on #189 had missed:
This Sql object is intended to work with only the table "%s"...SqlIdentifier must be specifiedPredicate\Between,In,IsNull,LikeA number of parameters (%d) was found...AbstractSqlThe key %s was not found in this objects column listInsertSince this object was created with a table and/or schema...SelectThe subject does not appear to implement %s, thus calling %s...Platform,AbstractPlatform$prefix must be a valid table prefix or null...TableIdentifier,TableIdentifierFactory$separator must be a valid table separator...TableIdentifier,TableIdentifierFactory%s expects parameter to be numeric, "%s" givenSelectA copy-paste bug, fixed.
Platform::getSqlString()reported'...thus calling prepareStatement() has no effect'. Parameterising the subject template exposed it. Nothing asserted the string, so that path had no coverage; the two platform tests now assert the full rendered message.Two retypes.
Predicate\Between's\LogicExceptionandAbstractSql's\ValueErrorbecome package types, so both reachPhpDb\Exception\ExceptionInterface.Betweenis not a BC break, contrary to what #189 says:\InvalidArgumentExceptionextends\LogicException, so every existingcatch (\LogicException)still matches.BetweenTestpassed untouched while still expecting\LogicException, which is how this surfaced; it now asserts the precise type.AbstractSql's\ValueErroris a real break, since\Errorand\Exceptionare unrelated branches. That leaves three genuine catch-breaking changes across the whole RFC rather than five.Also in passing:
Predicate::unnest()andPredicate::__get()never declared what they throw, andPredicateSetimportedExceptionfrom inside its own namespace.Motivation and Context
Refs #189. Depends on #190 for the convention, the constant naming rule and the structural guards.
src/Sqlis 51 of the 119 throw sites in the package and the worst concentration of duplicated message text.On the target branch. Targets
0.6.xalthough it carries a breaking change, for the reason agreed on #189: 0.5.0 is the current tag and 0.6.0 is unreleased. The only genuine break here isAbstractSql's\ValueError, on adefault =>match arm that no test reaches.How has this been tested?
mago format --check,mago lintandmago analyzeall exit 0. Full unit suite passes: 1743 tests, 3642 assertions.Every factory is covered: 24/24, 6/6, 9/9 and 1/1. Templates are verified byte-identical against the original literals by a throwaway script asserting all 36 before the call sites were touched, and the four generated template tests assert each rendered message thereafter. The 46
src/Sqlmessage assertions now derive from the templates rather than restating them.One thing worth knowing, because it will recur in any PR that touches a method its test class does not declare: before the per-factory tests existed, clover reported only 3 of 36 factories covered, yet deliberately breaking a factory failed 5 tests, proving the code was exercised. The cause is
#[CoversMethod]. Once a test class declares any, PHPUnit records coverage only for the methods listed, so code executed outside that list is not attributed. The factories were reached through the classes under test, which do not declare them. The per-factory tests declare their ownCoversMethodand resolve the number. #194 hit the same thing onAdapter::executeQuery(), where four existing tests exercised a method the class had never declared.Baseline and configuration changes
sprintfargument-type entries inanalyzer-baseline.tomlare re-attributed, not added to: the same unsoundness now reports againstforInvalidJoinNameandforForeignTable, with counts matching exactly (1 and 4). Underlying cause, worth its own issue:TableIdentifierhas no__toString(), soSql::forForeignTable($this->table)fatals when$this->tableis aTableIdentifier.halsteadreadings shift, inAbstractSql,SelectandDdl\Column\AbstractPrecisionColumn. Removing long string literals reduces distinct operands, which raises halstead difficulty, so simplifying these methods moved the metric the wrong way.too-many-methodsis excluded forsrc/Sql/Exception/inmago.tomlrather than baselined. The threshold is 10, verified empirically against mago 1.50.0. A catalogue holds one template per condition a component can report, so the count tracks failure modes; the remaining 20 are a long tail of 9 consumers, 7 of them with one or two factories, so there is no cohesive further split to make.phpdb-qa-toolsis untouched. This will conflict with Route the TableGateway and RowGateway throw sites through named constructors #193 on the same line, which addssrc/TableGateway/Exception/; whichever merges second needs the two paths merged into one array.