Skip to content

Commit ceebfbf

Browse files
authored
Merge pull request #230 from simon-mundy/qa/mago-sql-predicate
Bring Sql\Predicate to full Mago compliance
2 parents 4ba1a1c + 72366e4 commit ceebfbf

27 files changed

Lines changed: 368 additions & 290 deletions

‎analyzer-baseline.toml‎

Lines changed: 36 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,24 @@ code = "invalid-array-element-key"
15061506
message = "Invalid array key type."
15071507
count = 1
15081508

1509+
[[issues]]
1510+
file = "src/RowGateway/AbstractRowGateway.php"
1511+
code = "less-specific-nested-argument-type"
1512+
message = 'Argument type mismatch for argument #1 of `PhpDb\Sql\Delete::where`: expected `(closure(...mixed): mixed)|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\Where|array<array-key, PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\SqlInterface|list<bool|float|int|null|string>|null|scalar>|string`, but provided type `array<array-key, mixed>` is less specific.'
1513+
count = 1
1514+
1515+
[[issues]]
1516+
file = "src/RowGateway/AbstractRowGateway.php"
1517+
code = "less-specific-nested-argument-type"
1518+
message = 'Argument type mismatch for argument #1 of `PhpDb\Sql\Select::where`: expected `(closure(...mixed): mixed)|PhpDb\Sql\Predicate\PredicateInterface|array<array-key, PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\SqlInterface|list<bool|float|int|null|string>|null|scalar>|string`, but provided type `array<array-key, mixed>` is less specific.'
1519+
count = 1
1520+
1521+
[[issues]]
1522+
file = "src/RowGateway/AbstractRowGateway.php"
1523+
code = "less-specific-nested-argument-type"
1524+
message = 'Argument type mismatch for argument #1 of `PhpDb\Sql\Update::where`: expected `(closure(...mixed): mixed)|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\Where|array<array-key, PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\SqlInterface|list<bool|float|int|null|string>|null|scalar>|string`, but provided type `array<array-key, mixed>` is less specific.'
1525+
count = 1
1526+
15091527
[[issues]]
15101528
file = "src/RowGateway/AbstractRowGateway.php"
15111529
code = "less-specific-return-statement"
@@ -2598,174 +2616,6 @@ code = "missing-api-or-internal"
25982616
message = 'Interface `PhpDb\Sql\Platform\PlatformDecoratorInterface` is missing an `@api` or `@internal` annotation.'
25992617
count = 1
26002618

2601-
[[issues]]
2602-
file = "src/Sql/Predicate/Exception/InvalidArgumentException.php"
2603-
code = "class-must-be-final"
2604-
message = 'Class `PhpDb\Sql\Predicate\Exception\InvalidArgumentException` should be declared `final`.'
2605-
count = 1
2606-
2607-
[[issues]]
2608-
file = "src/Sql/Predicate/Exception/RuntimeException.php"
2609-
code = "class-must-be-final"
2610-
message = 'Class `PhpDb\Sql\Predicate\Exception\RuntimeException` should be declared `final`.'
2611-
count = 1
2612-
2613-
[[issues]]
2614-
file = "src/Sql/Predicate/Expression.php"
2615-
code = "class-must-be-final"
2616-
message = 'Class `PhpDb\Sql\Predicate\Expression` should be declared `final`.'
2617-
count = 1
2618-
2619-
[[issues]]
2620-
file = "src/Sql/Predicate/IsNotNull.php"
2621-
code = "class-must-be-final"
2622-
message = 'Class `PhpDb\Sql\Predicate\IsNotNull` should be declared `final`.'
2623-
count = 1
2624-
2625-
[[issues]]
2626-
file = "src/Sql/Predicate/Literal.php"
2627-
code = "class-must-be-final"
2628-
message = 'Class `PhpDb\Sql\Predicate\Literal` should be declared `final`.'
2629-
count = 1
2630-
2631-
[[issues]]
2632-
file = "src/Sql/Predicate/NotBetween.php"
2633-
code = "class-must-be-final"
2634-
message = 'Class `PhpDb\Sql\Predicate\NotBetween` should be declared `final`.'
2635-
count = 1
2636-
2637-
[[issues]]
2638-
file = "src/Sql/Predicate/NotIn.php"
2639-
code = "class-must-be-final"
2640-
message = 'Class `PhpDb\Sql\Predicate\NotIn` should be declared `final`.'
2641-
count = 1
2642-
2643-
[[issues]]
2644-
file = "src/Sql/Predicate/NotLike.php"
2645-
code = "class-must-be-final"
2646-
message = 'Class `PhpDb\Sql\Predicate\NotLike` should be declared `final`.'
2647-
count = 1
2648-
2649-
[[issues]]
2650-
file = "src/Sql/Predicate/Operator.php"
2651-
code = "class-must-be-final"
2652-
message = 'Class `PhpDb\Sql\Predicate\Operator` should be declared `final`.'
2653-
count = 1
2654-
2655-
[[issues]]
2656-
file = "src/Sql/Predicate/Predicate.php"
2657-
code = "impossible-condition"
2658-
message = "This condition (type `false`) will always evaluate to false."
2659-
count = 1
2660-
2661-
[[issues]]
2662-
file = "src/Sql/Predicate/Predicate.php"
2663-
code = "imprecise-type"
2664-
message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array<array-key, mixed>`."
2665-
count = 1
2666-
2667-
[[issues]]
2668-
file = "src/Sql/Predicate/Predicate.php"
2669-
code = "imprecise-type"
2670-
message = "Type `array` in parameter `$valueSet` is imprecise, equivalent to `array<array-key, mixed>`."
2671-
count = 2
2672-
2673-
[[issues]]
2674-
file = "src/Sql/Predicate/Predicate.php"
2675-
code = "invalid-property-assignment-value"
2676-
message = 'Invalid type for property `$unnest`: expected `PhpDb\Sql\Predicate\Predicate`, but got `PhpDb\Sql\Predicate\Predicate|null`.'
2677-
count = 1
2678-
2679-
[[issues]]
2680-
file = "src/Sql/Predicate/Predicate.php"
2681-
code = "invalid-property-assignment-value"
2682-
message = 'Invalid type for property `$unnest`: expected `PhpDb\Sql\Predicate\Predicate`, but got `null`.'
2683-
count = 1
2684-
2685-
[[issues]]
2686-
file = "src/Sql/Predicate/Predicate.php"
2687-
code = "less-specific-nested-argument-type"
2688-
message = 'Argument type mismatch for argument #2 of `PhpDb\Sql\Expression::__construct`: expected `PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|array<array-key, PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|list<bool|float|int|null|string>|null|scalar>|list<bool|float|int|null|string>|null|scalar`, but provided type `PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|float|int|non-empty-array<array-key, mixed>|null|string` is less specific.'
2689-
count = 1
2690-
2691-
[[issues]]
2692-
file = "src/Sql/Predicate/Predicate.php"
2693-
code = "possibly-invalid-argument"
2694-
message = 'Possible argument type mismatch for argument #2 of `PhpDb\Sql\Predicate\In::__construct`: expected `PhpDb\Sql\ArgumentInterface|PhpDb\Sql\Select|list<bool|float|int|null|string>|null`, but possibly received `PhpDb\Sql\ArgumentInterface|array<array-key, mixed>`.'
2695-
count = 2
2696-
2697-
[[issues]]
2698-
file = "src/Sql/Predicate/PredicateInterface.php"
2699-
code = "missing-api-or-internal"
2700-
message = 'Interface `PhpDb\Sql\Predicate\PredicateInterface` is missing an `@api` or `@internal` annotation.'
2701-
count = 1
2702-
2703-
[[issues]]
2704-
file = "src/Sql/Predicate/PredicateSet.php"
2705-
code = "mixed-argument"
2706-
message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Expression::__construct`: expected `string`, but found `mixed`.'
2707-
count = 1
2708-
2709-
[[issues]]
2710-
file = "src/Sql/Predicate/PredicateSet.php"
2711-
code = "mixed-argument"
2712-
message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Literal::__construct`: expected `string`, but found `mixed`.'
2713-
count = 1
2714-
2715-
[[issues]]
2716-
file = "src/Sql/Predicate/PredicateSet.php"
2717-
code = "mixed-argument"
2718-
message = "Invalid argument type for argument #1 of `str_contains`: expected `string`, but found `mixed`."
2719-
count = 1
2720-
2721-
[[issues]]
2722-
file = "src/Sql/Predicate/PredicateSet.php"
2723-
code = "mixed-argument"
2724-
message = 'Invalid argument type for argument #2 of `PhpDb\Sql\Expression::__construct`: expected `PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|array<array-key, PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|list<bool|float|int|null|string>|null|scalar>|list<bool|float|int|null|string>|null|scalar`, but found `mixed`.'
2725-
count = 1
2726-
2727-
[[issues]]
2728-
file = "src/Sql/Predicate/PredicateSet.php"
2729-
code = "mixed-argument"
2730-
message = 'Invalid argument type for argument #3 of `PhpDb\Sql\Predicate\Operator::__construct`: expected `PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|PhpDb\Sql\SqlInterface|null|scalar`, but found `nonnull`.'
2731-
count = 1
2732-
2733-
[[issues]]
2734-
file = "src/Sql/Predicate/PredicateSet.php"
2735-
code = "mixed-assignment"
2736-
message = "Assigning `mixed` type to a variable may lead to unexpected behavior."
2737-
count = 1
2738-
2739-
[[issues]]
2740-
file = "src/Sql/Predicate/PredicateSet.php"
2741-
code = "possible-method-access-on-null"
2742-
message = "Attempting to call a method on `null`."
2743-
count = 1
2744-
2745-
[[issues]]
2746-
file = "src/Sql/Predicate/PredicateSet.php"
2747-
code = "possibly-invalid-argument"
2748-
message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Predicate\PredicateSet::addPredicate`: expected `PhpDb\Sql\Predicate\PredicateInterface`, but possibly received `PhpDb\Sql\Expression|PhpDb\Sql\Predicate\Expression|PhpDb\Sql\Predicate\In|PhpDb\Sql\Predicate\IsNull|PhpDb\Sql\Predicate\Literal|PhpDb\Sql\Predicate\Operator|PhpDb\Sql\Predicate\PredicateInterface`.'
2749-
count = 1
2750-
2751-
[[issues]]
2752-
file = "src/Sql/Predicate/PredicateSet.php"
2753-
code = "possibly-invalid-argument"
2754-
message = 'Possible argument type mismatch for argument #2 of `PhpDb\Sql\Predicate\In::__construct`: expected `PhpDb\Sql\ArgumentInterface|PhpDb\Sql\Select|list<bool|float|int|null|string>|null`, but possibly received `array<array-key, mixed>`.'
2755-
count = 1
2756-
2757-
[[issues]]
2758-
file = "src/Sql/Predicate/PredicateSet.php"
2759-
code = "possibly-null-destructuring-source"
2760-
message = 'Destructuring a possibly null value of type `array{0: string, 1: PhpDb\Sql\Predicate\PredicateInterface}|null`.'
2761-
count = 1
2762-
2763-
[[issues]]
2764-
file = "src/Sql/Predicate/PredicateSet.php"
2765-
code = "possibly-undefined-int-array-index"
2766-
message = 'Possibly undefined array index accessed on `list<array{0: string, 1: PhpDb\Sql\Predicate\PredicateInterface}>`.'
2767-
count = 1
2768-
27692619
[[issues]]
27702620
file = "src/Sql/PreparableSqlInterface.php"
27712621
code = "missing-api-or-internal"
@@ -3234,12 +3084,30 @@ code = "less-specific-nested-argument-type"
32343084
message = 'Argument type mismatch for argument #1 of `PhpDb\ResultSet\ResultSetInterface::initialize`: expected `iterable<array-key, mixed>`, but provided type `PhpDb\Adapter\Driver\ResultInterface|null` is less specific.'
32353085
count = 1
32363086

3087+
[[issues]]
3088+
file = "src/TableGateway/AbstractTableGateway.php"
3089+
code = "less-specific-nested-argument-type"
3090+
message = 'Argument type mismatch for argument #1 of `PhpDb\Sql\Delete::where`: expected `(closure(...mixed): mixed)|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\Where|array<array-key, PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\SqlInterface|list<bool|float|int|null|string>|null|scalar>|string`, but provided type `PhpDb\Sql\Where|array<array-key, mixed>|string` is less specific.'
3091+
count = 1
3092+
32373093
[[issues]]
32383094
file = "src/TableGateway/AbstractTableGateway.php"
32393095
code = "less-specific-nested-argument-type"
32403096
message = 'Argument type mismatch for argument #1 of `PhpDb\Sql\Select::columns`: expected `array<array-key, PhpDb\Sql\ExpressionInterface|string>`, but provided type `non-empty-array<array-key, mixed>` is less specific.'
32413097
count = 1
32423098

3099+
[[issues]]
3100+
file = "src/TableGateway/AbstractTableGateway.php"
3101+
code = "less-specific-nested-argument-type"
3102+
message = 'Argument type mismatch for argument #1 of `PhpDb\Sql\Select::where`: expected `(closure(...mixed): mixed)|PhpDb\Sql\Predicate\PredicateInterface|array<array-key, PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\SqlInterface|list<bool|float|int|null|string>|null|scalar>|string`, but provided type `PhpDb\Sql\Where|array<array-key, mixed>|string` is less specific.'
3103+
count = 1
3104+
3105+
[[issues]]
3106+
file = "src/TableGateway/AbstractTableGateway.php"
3107+
code = "less-specific-nested-argument-type"
3108+
message = 'Argument type mismatch for argument #1 of `PhpDb\Sql\Update::where`: expected `(closure(...mixed): mixed)|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\Where|array<array-key, PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|PhpDb\Sql\Predicate\PredicateInterface|PhpDb\Sql\SqlInterface|list<bool|float|int|null|string>|null|scalar>|string`, but provided type `(closure(...mixed=): mixed)|PhpDb\Sql\Where|array<array-key, mixed>|string` is less specific.'
3109+
count = 1
3110+
32433111
[[issues]]
32443112
file = "src/TableGateway/AbstractTableGateway.php"
32453113
code = "missing-api-or-internal"

‎docs/book/sql/where-having.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -614,11 +614,12 @@ Produces:
614614
WHERE status = 'active' AND age > 18 OR role = 'admin'
615615
```
616616

617-
The properties are case-insensitive for convenience:
617+
The properties can be written in lower or upper case. Mixed case, such as `Or`, throws
618+
`PhpDb\Sql\Predicate\Exception\InvalidArgumentException`:
618619

619-
```php title="Case-insensitive magic property usage"
620+
```php title="Lower- and upper-case magic property usage"
620621
$where->and->equalTo('a', 1);
621-
$where->AND->equalTo('b', 2');
622+
$where->AND->equalTo('b', 2);
622623
```
623624

624625
### Deep nesting of predicates

‎lint-baseline.toml‎

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -426,24 +426,6 @@ code = "too-many-methods"
426426
message = "Class has too many methods."
427427
count = 1
428428

429-
[[issues]]
430-
file = "src/Sql/Predicate/Predicate.php"
431-
code = "too-many-methods"
432-
message = "Class has too many methods."
433-
count = 1
434-
435-
[[issues]]
436-
file = "src/Sql/Predicate/PredicateSet.php"
437-
code = "halstead"
438-
message = "Method has a high halstead difficulty"
439-
count = 1
440-
441-
[[issues]]
442-
file = "src/Sql/Predicate/PredicateSet.php"
443-
code = "kan-defect"
444-
message = "Class has a high kan defect score."
445-
count = 1
446-
447429
[[issues]]
448430
file = "src/Sql/Select.php"
449431
code = "cyclomatic-complexity"

‎mago.toml‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,17 @@ baseline = "lint-baseline.toml"
1515
# Exception classes are message catalogues: one template and one named constructor per
1616
# condition, per the convention in docs/book/exceptions.md. The method count tracks the
1717
# number of distinct failures a component can report, so the maintainability signal does
18-
# not apply. "test/" repeats the phpdb-qa-tools default, which this key replaces.
19-
too-many-methods = { exclude = ["test/", "src/Sql/Exception/", "src/TableGateway/Exception/", "src/Adapter/Exception/"] }
18+
# not apply. Predicate is the fluent WHERE/HAVING builder: one public method per predicate
19+
# type, so its method count is the size of its public API. "test/" repeats the
20+
# phpdb-qa-tools default, which this key replaces.
21+
too-many-methods = { exclude = [
22+
"test/",
23+
"src/Sql/Exception/",
24+
"src/Sql/Predicate/Exception/",
25+
"src/TableGateway/Exception/",
26+
"src/Adapter/Exception/",
27+
"src/Sql/Predicate/Predicate.php",
28+
] }
2029

2130
# The capability interfaces return `ResultSetInterface&<themselves>`, and PHP rejects
2231
# `self` as part of an intersection type, so this rule's advice cannot be taken there.

‎src/Adapter/Driver/Pdo/AbstractPdo.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function checkEnvironment(): bool
5353
/**
5454
* todo: this needs improved
5555
*
56-
* @param PDOStatement|string $sqlOrResource
56+
* @param PDOStatement|resource|string|null $sqlOrResource A non-PDO resource is ignored, as with null
5757
*/
5858
#[Override]
5959
public function createStatement($sqlOrResource = null): StatementInterface

‎src/Sql/Delete.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
/**
1818
* @property Where $where
1919
* @psalm-import-type Specification from AbstractSql
20+
* @psalm-import-type PredicateValue from Predicate\PredicateSet
2021
*/
2122
class Delete extends AbstractPreparableSql
2223
{
@@ -79,7 +80,7 @@ public function getRawState(?string $key = null): mixed
7980
/**
8081
* Create where clause
8182
*
82-
* @param PredicateInterface|array<array-key, mixed>|Closure|string|Where $predicate
83+
* @param PredicateInterface|array<array-key, PredicateValue>|Closure|string|Where $predicate
8384
* @param string $combination One of the OP_* constants from Predicate\PredicateSet
8485
*/
8586
public function where(

‎src/Sql/Predicate/Between.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
use PhpDb\Sql\ArgumentInterface;
1212
use PhpDb\Sql\Predicate\Exception\InvalidArgumentException;
1313

14+
/**
15+
* @api
16+
*/
1417
class Between extends AbstractExpression implements PredicateInterface
1518
{
1619
protected string $operator = 'BETWEEN';

‎src/Sql/Predicate/Exception/InvalidArgumentException.php‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,19 @@
66

77
use PhpDb\Sql\Exception;
88

9+
use function sprintf;
10+
11+
/**
12+
* @api
13+
*/
914
class InvalidArgumentException extends Exception\InvalidArgumentException
1015
{
1116
final public const string INVALID_COMBINATION = "Invalid combination: expected 'AND' or 'OR'";
1217

18+
final public const string INVALID_LIST_PREDICATE = 'A predicate without a string key must be a predicate, an expression or a string';
19+
20+
final public const string MIXED_CASE_MAGIC_PROPERTY = 'Magic property "%s" must be given in lower or upper case';
21+
1322
final public const string MISSING_IDENTIFIER = 'Identifier must be specified';
1423

1524
final public const string MISSING_LEFT_EXPRESSION = 'Left expression must be specified';
@@ -31,6 +40,11 @@ public static function forInvalidCombination(): self
3140
return new self(self::INVALID_COMBINATION);
3241
}
3342

43+
public static function forInvalidListPredicate(): self
44+
{
45+
return new self(self::INVALID_LIST_PREDICATE);
46+
}
47+
3448
public static function forMissingIdentifier(): self
3549
{
3650
return new self(self::MISSING_IDENTIFIER);
@@ -66,6 +80,11 @@ public static function forMissingValueSet(): self
6680
return new self(self::MISSING_VALUE_SET);
6781
}
6882

83+
public static function forMixedCaseMagicProperty(string $name): self
84+
{
85+
return new self(sprintf(self::MIXED_CASE_MAGIC_PROPERTY, $name));
86+
}
87+
6988
public static function forPredicateWithStringKey(): self
7089
{
7190
return new self(self::PREDICATE_WITH_STRING_KEY);

‎src/Sql/Predicate/Exception/RuntimeException.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
use PhpDb\Sql\Exception;
88

9+
/**
10+
* @api
11+
*/
912
class RuntimeException extends Exception\RuntimeException
1013
{
1114
final public const string NOT_NESTED = 'Not nested';

‎src/Sql/Predicate/Expression.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66

77
use PhpDb\Sql\Expression as BaseExpression;
88

9+
/**
10+
* @api
11+
*/
912
class Expression extends BaseExpression implements PredicateInterface {}

0 commit comments

Comments
 (0)