Repository navigation
Bring Sql\Predicate to full Mago compliance - #230
Merged
Merged
Conversation
The class documents AND, OR, NEST and UNNEST alongside their lower-case forms, and the docs promise the names are case-insensitive, but __get() lost its strtolower() in an earlier refactor. Upper-case names fell through to the default and returned the predicate unchanged, so $where->OR->equalTo(...) was silently combined with AND. The private $unnest property is renamed $parent. It shared its name with the @Property Predicate $unnest tag, so the analyzer read every access as the magic property and reported the null guard as impossible. The in(), notIn() and expression() docblocks now carry the value types their predicates accept.
A string with a placeholder and no key became a Sql\Expression, which is not a PredicateInterface, so addPredicates(['a = ?']) threw a TypeError. It now becomes a Predicate\Expression, as it did before the refactor. A Select bound to a placeholder key is wrapped in an Argument\Select so it reaches the expression as a parameter. A list entry that is not a predicate, an expression or a string now throws InvalidArgumentException::forInvalidListPredicate() rather than a TypeError from str_contains(). Entries are built by two private helpers, one per key type, typed against a PredicateValue alias that the where() and having() docblocks on Select, Update and Delete now share. getExpressionData() drops its special cases for zero and one predicate; the loop produces the same result for both.
Every class and interface under Sql\Predicate is public API that callers build and extend, so each is marked @api rather than final. Mago's class finality check accepts @api, which settles both the class-must-be-final and missing-api-or-internal findings without closing the classes to the platform packages. Predicate joins the too-many-methods exclusions. It is the fluent WHERE and HAVING builder, one method per predicate type, so its method count is the size of its public API. Sql\Predicate now leaves both baselines entirely. Typing the predicate arrays moves six findings onto the TableGateway and RowGateway callers of where(), which are left for the gateway pass.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
simon-mundy
marked this pull request as draft
October 11, 2026 23:09
__get() matches the lower- and upper-case names its @Property tags declare instead of lower-casing every name it is given. A mixed-case form of a known name, such as Or, now throws InvalidArgumentException::forMixedCaseMagicProperty() rather than falling through and silently leaving the next predicate's combination unchanged. Other unknown names still return the predicate. The where-having guide described the names as case-insensitive. It now documents lower and upper case and the exception, and its example loses a stray quote. The Predicate exception catalogue joins the too-many-methods exclusions alongside src/Sql/Exception/.
simon-mundy
marked this pull request as ready for review
October 11, 2026 23:43
tyrsson
previously approved these changes
Oct 12, 2026
simon-mundy
dismissed
tyrsson’s stale review
October 12, 2026 02:13
The merge-base changed after approval.
Mago 1.56.2 reports the PDO driver's docblock as narrower than DriverInterface::createStatement(), which documents resource|string. The PDO driver now documents resource as well. It already treats any argument that is not a PDOStatement or a string as no SQL and prepares a fresh statement, so behaviour is unchanged.
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.
Takes
Sql\Predicateout of both Mago baselines entirely, and fixes two bugs found along the way.Bugs fixed
Upper-case magic properties were ignored.
PredicatedocumentsAND,OR,NESTandUNNESTnext to their lower-case forms, anddocs/book/sql/where-having.mdcalled the names case-insensitive. An earlier refactor dropped thestrtolower()in__get(), so upper-case names fell through and returned the predicate unchanged:addPredicates(['a = ?'])threw aTypeError. A keyless string with a placeholder was built as aSql\Expression, which is not aPredicateInterface. It is now aPredicate\Expression, as it was in laminas-db.Behaviour changes
__get()now matches exactly the names its@propertytags declare:or/OR,and/AND,nest/NESTandunnest/UNNEST. It no longer lower-cases every name it's given. A mixed-case form of one of those names, such asOr, throwsInvalidArgumentException::forMixedCaseMagicProperty()rather than silently leaving the next predicate's combination unchanged. Other unknown names still return the predicate. The where-having guide now documents lower or upper case and the exception.A keyless entry that is not a predicate, an expression or a string now throws
InvalidArgumentException::forInvalidListPredicate(). It used to throw aTypeErrorfromstr_contains().A
Selectbound to a placeholder key (['id IN (?)' => $select]) is wrapped inArgument\Selectand reaches the expression as a parameter. It used to throw aTypeError.Cleanup
addPredicates()builds entries through two private helpers, one per key type. Both are typed against aPredicateValuealias that thewhere()andhaving()docblocks onSelect,UpdateandDeletenow share.getExpressionData()drops its special cases for zero and one predicate, because the loop gives the same result for both. This clears thehalsteadandkan-defectfindings onPredicateSet.@propertytags forunnestandUNNESTnow declare?Predicate, matching the private?Predicate $unnestproperty behind them. Mago applies those tags to$this->unnestinside the class, so with a non-null type it reported the "not nested" null guard as impossible and the null assignments as invalid.__get('unnest')still returns the parent predicate or throws when the predicate is not nested.@api. None is madefinal. Mago's finality check accepts@api, so this settles bothclass-must-be-finalandmissing-api-or-internalwhile leaving the classes open to the platform packages.Predicateand the Predicate exception catalogue join thetoo-many-methodsexclusions inmago.toml. It has one fluent method per predicate type, so its method count is the size of its public API.Result
src/Sql/Predicatein either baselineTyping the predicate arrays moves six findings onto the
where()calls inAbstractTableGatewayandAbstractRowGateway. They are baselined for the gateway pass.Line and method coverage of
src/Sql/Predicateis 100%. Infection reports 99% covered-code MSI. The two survivors are in untouched code: a protected-to-private visibility mutant ongetNextPredicateCombineOperator(), and the??inIn::getExpressionData().