Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 36 additions & 168 deletions analyzer-baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1506,6 +1506,24 @@ code = "invalid-array-element-key"
message = "Invalid array key type."
count = 1

[[issues]]
file = "src/RowGateway/AbstractRowGateway.php"
code = "less-specific-nested-argument-type"
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.'
count = 1

[[issues]]
file = "src/RowGateway/AbstractRowGateway.php"
code = "less-specific-nested-argument-type"
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.'
count = 1

[[issues]]
file = "src/RowGateway/AbstractRowGateway.php"
code = "less-specific-nested-argument-type"
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.'
count = 1

[[issues]]
file = "src/RowGateway/AbstractRowGateway.php"
code = "less-specific-return-statement"
Expand Down Expand Up @@ -2598,174 +2616,6 @@ code = "missing-api-or-internal"
message = 'Interface `PhpDb\Sql\Platform\PlatformDecoratorInterface` is missing an `@api` or `@internal` annotation.'
count = 1

[[issues]]
file = "src/Sql/Predicate/Exception/InvalidArgumentException.php"
code = "class-must-be-final"
message = 'Class `PhpDb\Sql\Predicate\Exception\InvalidArgumentException` should be declared `final`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/Exception/RuntimeException.php"
code = "class-must-be-final"
message = 'Class `PhpDb\Sql\Predicate\Exception\RuntimeException` should be declared `final`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/Expression.php"
code = "class-must-be-final"
message = 'Class `PhpDb\Sql\Predicate\Expression` should be declared `final`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/IsNotNull.php"
code = "class-must-be-final"
message = 'Class `PhpDb\Sql\Predicate\IsNotNull` should be declared `final`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/Literal.php"
code = "class-must-be-final"
message = 'Class `PhpDb\Sql\Predicate\Literal` should be declared `final`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/NotBetween.php"
code = "class-must-be-final"
message = 'Class `PhpDb\Sql\Predicate\NotBetween` should be declared `final`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/NotIn.php"
code = "class-must-be-final"
message = 'Class `PhpDb\Sql\Predicate\NotIn` should be declared `final`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/NotLike.php"
code = "class-must-be-final"
message = 'Class `PhpDb\Sql\Predicate\NotLike` should be declared `final`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/Operator.php"
code = "class-must-be-final"
message = 'Class `PhpDb\Sql\Predicate\Operator` should be declared `final`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/Predicate.php"
code = "impossible-condition"
message = "This condition (type `false`) will always evaluate to false."
count = 1

[[issues]]
file = "src/Sql/Predicate/Predicate.php"
code = "imprecise-type"
message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array<array-key, mixed>`."
count = 1

[[issues]]
file = "src/Sql/Predicate/Predicate.php"
code = "imprecise-type"
message = "Type `array` in parameter `$valueSet` is imprecise, equivalent to `array<array-key, mixed>`."
count = 2

[[issues]]
file = "src/Sql/Predicate/Predicate.php"
code = "invalid-property-assignment-value"
message = 'Invalid type for property `$unnest`: expected `PhpDb\Sql\Predicate\Predicate`, but got `PhpDb\Sql\Predicate\Predicate|null`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/Predicate.php"
code = "invalid-property-assignment-value"
message = 'Invalid type for property `$unnest`: expected `PhpDb\Sql\Predicate\Predicate`, but got `null`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/Predicate.php"
code = "less-specific-nested-argument-type"
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.'
count = 1

[[issues]]
file = "src/Sql/Predicate/Predicate.php"
code = "possibly-invalid-argument"
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>`.'
count = 2

[[issues]]
file = "src/Sql/Predicate/PredicateInterface.php"
code = "missing-api-or-internal"
message = 'Interface `PhpDb\Sql\Predicate\PredicateInterface` is missing an `@api` or `@internal` annotation.'
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "mixed-argument"
message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Expression::__construct`: expected `string`, but found `mixed`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "mixed-argument"
message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Literal::__construct`: expected `string`, but found `mixed`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "mixed-argument"
message = "Invalid argument type for argument #1 of `str_contains`: expected `string`, but found `mixed`."
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "mixed-argument"
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`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "mixed-argument"
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`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "mixed-assignment"
message = "Assigning `mixed` type to a variable may lead to unexpected behavior."
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "possible-method-access-on-null"
message = "Attempting to call a method on `null`."
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "possibly-invalid-argument"
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`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "possibly-invalid-argument"
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>`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "possibly-null-destructuring-source"
message = 'Destructuring a possibly null value of type `array{0: string, 1: PhpDb\Sql\Predicate\PredicateInterface}|null`.'
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "possibly-undefined-int-array-index"
message = 'Possibly undefined array index accessed on `list<array{0: string, 1: PhpDb\Sql\Predicate\PredicateInterface}>`.'
count = 1

[[issues]]
file = "src/Sql/PreparableSqlInterface.php"
code = "missing-api-or-internal"
Expand Down Expand Up @@ -3234,12 +3084,30 @@ code = "less-specific-nested-argument-type"
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.'
count = 1

[[issues]]
file = "src/TableGateway/AbstractTableGateway.php"
code = "less-specific-nested-argument-type"
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.'
count = 1

[[issues]]
file = "src/TableGateway/AbstractTableGateway.php"
code = "less-specific-nested-argument-type"
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.'
count = 1

[[issues]]
file = "src/TableGateway/AbstractTableGateway.php"
code = "less-specific-nested-argument-type"
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.'
count = 1

[[issues]]
file = "src/TableGateway/AbstractTableGateway.php"
code = "less-specific-nested-argument-type"
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.'
count = 1

[[issues]]
file = "src/TableGateway/AbstractTableGateway.php"
code = "missing-api-or-internal"
Expand Down
7 changes: 4 additions & 3 deletions docs/book/sql/where-having.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,11 +614,12 @@ Produces:
WHERE status = 'active' AND age > 18 OR role = 'admin'
```

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

```php title="Case-insensitive magic property usage"
```php title="Lower- and upper-case magic property usage"
$where->and->equalTo('a', 1);
$where->AND->equalTo('b', 2');
$where->AND->equalTo('b', 2);
```

### Deep nesting of predicates
Expand Down
18 changes: 0 additions & 18 deletions lint-baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -426,24 +426,6 @@ code = "too-many-methods"
message = "Class has too many methods."
count = 1

[[issues]]
file = "src/Sql/Predicate/Predicate.php"
code = "too-many-methods"
message = "Class has too many methods."
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "halstead"
message = "Method has a high halstead difficulty"
count = 1

[[issues]]
file = "src/Sql/Predicate/PredicateSet.php"
code = "kan-defect"
message = "Class has a high kan defect score."
count = 1

[[issues]]
file = "src/Sql/Select.php"
code = "cyclomatic-complexity"
Expand Down
13 changes: 11 additions & 2 deletions mago.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,17 @@ baseline = "lint-baseline.toml"
# Exception classes are message catalogues: one template and one named constructor per
# condition, per the convention in docs/book/exceptions.md. The method count tracks the
# number of distinct failures a component can report, so the maintainability signal does
# not apply. "test/" repeats the phpdb-qa-tools default, which this key replaces.
too-many-methods = { exclude = ["test/", "src/Sql/Exception/", "src/TableGateway/Exception/", "src/Adapter/Exception/"] }
# not apply. Predicate is the fluent WHERE/HAVING builder: one public method per predicate
# type, so its method count is the size of its public API. "test/" repeats the
# phpdb-qa-tools default, which this key replaces.
too-many-methods = { exclude = [
"test/",
"src/Sql/Exception/",
"src/Sql/Predicate/Exception/",
"src/TableGateway/Exception/",
"src/Adapter/Exception/",
"src/Sql/Predicate/Predicate.php",
] }

# The capability interfaces return `ResultSetInterface&<themselves>`, and PHP rejects
# `self` as part of an intersection type, so this rule's advice cannot be taken there.
Expand Down
2 changes: 1 addition & 1 deletion src/Adapter/Driver/Pdo/AbstractPdo.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public function checkEnvironment(): bool
/**
* todo: this needs improved
*
* @param PDOStatement|string $sqlOrResource
* @param PDOStatement|resource|string|null $sqlOrResource A non-PDO resource is ignored, as with null
*/
#[Override]
public function createStatement($sqlOrResource = null): StatementInterface
Expand Down
3 changes: 2 additions & 1 deletion src/Sql/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
/**
* @property Where $where
* @psalm-import-type Specification from AbstractSql
* @psalm-import-type PredicateValue from Predicate\PredicateSet
*/
class Delete extends AbstractPreparableSql
{
Expand Down Expand Up @@ -79,7 +80,7 @@ public function getRawState(?string $key = null): mixed
/**
* Create where clause
*
* @param PredicateInterface|array<array-key, mixed>|Closure|string|Where $predicate
* @param PredicateInterface|array<array-key, PredicateValue>|Closure|string|Where $predicate
* @param string $combination One of the OP_* constants from Predicate\PredicateSet
*/
public function where(
Expand Down
3 changes: 3 additions & 0 deletions src/Sql/Predicate/Between.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
use PhpDb\Sql\ArgumentInterface;
use PhpDb\Sql\Predicate\Exception\InvalidArgumentException;

/**
* @api
*/
class Between extends AbstractExpression implements PredicateInterface
{
protected string $operator = 'BETWEEN';
Expand Down
19 changes: 19 additions & 0 deletions src/Sql/Predicate/Exception/InvalidArgumentException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@

use PhpDb\Sql\Exception;

use function sprintf;

/**
* @api
*/
class InvalidArgumentException extends Exception\InvalidArgumentException
{
final public const string INVALID_COMBINATION = "Invalid combination: expected 'AND' or 'OR'";

final public const string INVALID_LIST_PREDICATE = 'A predicate without a string key must be a predicate, an expression or a string';

final public const string MIXED_CASE_MAGIC_PROPERTY = 'Magic property "%s" must be given in lower or upper case';

final public const string MISSING_IDENTIFIER = 'Identifier must be specified';

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

public static function forInvalidListPredicate(): self
{
return new self(self::INVALID_LIST_PREDICATE);
}

public static function forMissingIdentifier(): self
{
return new self(self::MISSING_IDENTIFIER);
Expand Down Expand Up @@ -66,6 +80,11 @@ public static function forMissingValueSet(): self
return new self(self::MISSING_VALUE_SET);
}

public static function forMixedCaseMagicProperty(string $name): self
{
return new self(sprintf(self::MIXED_CASE_MAGIC_PROPERTY, $name));
}

public static function forPredicateWithStringKey(): self
{
return new self(self::PREDICATE_WITH_STRING_KEY);
Expand Down
3 changes: 3 additions & 0 deletions src/Sql/Predicate/Exception/RuntimeException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use PhpDb\Sql\Exception;

/**
* @api
*/
class RuntimeException extends Exception\RuntimeException
{
final public const string NOT_NESTED = 'Not nested';
Expand Down
3 changes: 3 additions & 0 deletions src/Sql/Predicate/Expression.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@

use PhpDb\Sql\Expression as BaseExpression;

/**
* @api
*/
class Expression extends BaseExpression implements PredicateInterface {}
3 changes: 3 additions & 0 deletions src/Sql/Predicate/In.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
use PhpDb\Sql\Predicate\Exception\InvalidArgumentException;
use PhpDb\Sql\Select;

/**
* @api
*/
class In extends AbstractExpression implements PredicateInterface
{
protected ?ArgumentInterface $identifier = null;
Expand Down Expand Up @@ -53,7 +56,7 @@
$valueSetSpec = $this->valueSet->getSpecification();

return [
'spec' => $this->specification ?? "{$identifierSpec} {$this->operator} {$valueSetSpec}",

Check warning on line 59 in src/Sql/Predicate/In.php

View workflow job for this annotation

GitHub Actions / qa / Mutation testing

Escaped Mutant for Mutator "Coalesce": @@ @@ $valueSetSpec = $this->valueSet->getSpecification(); return [ - 'spec' => $this->specification ?? "{$identifierSpec} {$this->operator} {$valueSetSpec}", + 'spec' => "{$identifierSpec} {$this->operator} {$valueSetSpec}" ?? $this->specification, 'values' => [$this->identifier, $this->valueSet], ]; }
'values' => [$this->identifier, $this->valueSet],
];
}
Expand Down
Loading
Loading