diff --git a/src/Assert.php b/src/Assert.php index 31c861b9..91679bea 100644 --- a/src/Assert.php +++ b/src/Assert.php @@ -1114,7 +1114,7 @@ public static function notSame(mixed $value, mixed $expect, string|callable $mes */ public static function greaterThan(mixed $value, mixed $limit, string|callable $message = ''): mixed { - if ($value <= $limit) { + if (!static::comparable($value, $limit) || $value <= $limit) { $message = self::resolveMessage($message); static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value greater than %2$s. Got: %s', @@ -1135,7 +1135,7 @@ public static function greaterThan(mixed $value, mixed $limit, string|callable $ */ public static function greaterThanEq(mixed $value, mixed $limit, string|callable $message = ''): mixed { - if ($value < $limit) { + if (!static::comparable($value, $limit) || $value < $limit) { $message = self::resolveMessage($message); static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value greater than or equal to %2$s. Got: %s', @@ -1156,7 +1156,7 @@ public static function greaterThanEq(mixed $value, mixed $limit, string|callable */ public static function lessThan(mixed $value, mixed $limit, string|callable $message = ''): mixed { - if ($value >= $limit) { + if (!static::comparable($value, $limit) || $value >= $limit) { $message = self::resolveMessage($message); static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value less than %2$s. Got: %s', @@ -1177,7 +1177,7 @@ public static function lessThan(mixed $value, mixed $limit, string|callable $mes */ public static function lessThanEq(mixed $value, mixed $limit, string|callable $message = ''): mixed { - if ($value > $limit) { + if (!static::comparable($value, $limit) || $value > $limit) { $message = self::resolveMessage($message); static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value less than or equal to %2$s. Got: %s', @@ -1200,7 +1200,7 @@ public static function lessThanEq(mixed $value, mixed $limit, string|callable $m */ public static function range(mixed $value, mixed $min, mixed $max, string|callable $message = ''): mixed { - if ($value < $min || $value > $max) { + if (!static::comparable($value, $min) || !static::comparable($value, $max) || $value < $min || $value > $max) { $message = self::resolveMessage($message); static::reportInvalidArgument(\sprintf( $message ?: 'Expected a value between %2$s and %3$s. Got: %s', @@ -2614,6 +2614,24 @@ protected static function resolveMessage(string|callable $message): string return \is_callable($message) ? $message() : $message; } + /** + * Whether two operands can be ordered against one another without coercion. + * + * PHP's relational operators answer for every pair: an object compares as 1, an array + * outranks any scalar, null and bool are compared as bool. The ordering assertions ask + * this first, so such a pair is reported as a failed assertion instead of a passed one. + * + * @psalm-pure + */ + protected static function comparable(mixed $value, mixed $other): bool + { + return (\is_numeric($value) && \is_numeric($other)) + || (\is_string($value) && \is_string($other)) + || (\is_bool($value) && \is_bool($other)) + || (\is_array($value) && \is_array($other)) + || (\is_object($value) && \is_object($other)); + } + private function __construct() { } diff --git a/tests/AssertTest.php b/tests/AssertTest.php index f0ec064d..6ab02459 100644 --- a/tests/AssertTest.php +++ b/tests/AssertTest.php @@ -295,6 +295,19 @@ public static function getTests(): array ['range', [2, 1, 2], true], ['range', [0, 1, 2], false], ['range', [3, 1, 2], false], + ['greaterThan', [new stdClass(), 10], false], + ['greaterThan', [[1, 2], 10], false], + ['greaterThan', ['abc', 10], false], + ['greaterThanEq', [null, 10], false], + ['lessThan', [new stdClass(), 10], false], + ['lessThan', [false, 10], false], + ['range', [new stdClass(), 1, 10], false], + ['range', [5, 1, 'x'], false], + ['lessThan', ['5', 10], true], + ['lessThan', ['abc', 'abd'], true], + ['lessThan', [[1], [1, 2]], true], + ['greaterThan', [true, false], true], + ['lessThan', [new DateTimeImmutable('2020-01-01'), new DateTimeImmutable('2021-01-01')], true], ['oneOf', [1, [1, 2, 3]], true], ['oneOf', [1, ['1', '2', '3']], false], ['notOneOf', [1, [1, 2, 3]], false],