diff --git a/README.md b/README.md index fef4eda5..af48b0a4 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,7 @@ Method | Description -------------------------------------------------- | ------------------------------------------------------------------ `keyExists($array, $key, $message = '')` | Check that a key exists in an array `keyNotExists($array, $key, $message = '')` | Check that a key does not exist in an array +`keysExist($array, $keys, $message = '')` | Check that all the given keys exist in an array `validArrayKey($key, $message = '')` | Check that a value is a valid array key (int or string) `count($array, $number, $message = '')` | Check that an array contains a specific number of elements `minCount($array, $min, $message = '')` | Check that an array contains at least a certain number of elements diff --git a/src/Assert.php b/src/Assert.php index 31c861b9..8ba819a7 100644 --- a/src/Assert.php +++ b/src/Assert.php @@ -2146,6 +2146,40 @@ public static function keyExists(mixed $array, string|int $key, string|callable return $array; } + /** + * @psalm-pure + * + * @param iterable $keys + * @param string|callable():string $message + * + * @throws InvalidArgumentException + */ + public static function keysExist(mixed $array, mixed $keys, string|callable $message = ''): array + { + static::isArray($array, $message); + static::isIterable($keys); + + $missing = []; + + foreach ($keys as $key) { + static::validArrayKey($key, 'Expected an array key. Got: %s'); + + if (!(isset($array[$key]) || \array_key_exists($key, $array))) { + $missing[] = $key; + } + } + + if ([] !== $missing) { + $message = self::resolveMessage($message); + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the keys %s to exist.', + \implode(', ', \array_map(static::valueToString(...), $missing)) + )); + } + + return $array; + } + /** * @psalm-pure * diff --git a/src/Mixin.php b/src/Mixin.php index a714f16c..fe9310a0 100644 --- a/src/Mixin.php +++ b/src/Mixin.php @@ -5021,6 +5021,65 @@ public static function allNullOrKeyExists(mixed $array, string|int $key, callabl return $array; } + /** + * @psalm-pure + * + * @param iterable $keys + * @param string|callable():string $message + * + * @return array|null + * + * @throws InvalidArgumentException + */ + public static function nullOrKeysExist(mixed $array, mixed $keys, callable|string $message = ''): ?array + { + null === $array || static::keysExist($array, $keys, $message); + + return $array; + } + + /** + * @psalm-pure + * + * @param iterable $keys + * @param string|callable():string $message + * + * @return iterable + * + * @throws InvalidArgumentException + */ + public static function allKeysExist(mixed $array, mixed $keys, callable|string $message = ''): iterable + { + static::isIterable($array); + + foreach ($array as $entry) { + static::keysExist($entry, $keys, $message); + } + + return $array; + } + + /** + * @psalm-pure + * + * @param iterable $keys + * @param string|callable():string $message + * + * @return iterable + * + * @throws InvalidArgumentException + */ + public static function allNullOrKeysExist(mixed $array, mixed $keys, callable|string $message = ''): iterable + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::keysExist($entry, $keys, $message); + } + + return $array; + } + /** * @psalm-pure * diff --git a/tests/AssertTest.php b/tests/AssertTest.php index f0ec064d..3719e385 100644 --- a/tests/AssertTest.php +++ b/tests/AssertTest.php @@ -536,6 +536,11 @@ public static function getTests(): array ['keyNotExists', [['key' => 0], 'key'], false], ['keyNotExists', [['key' => null], 'key'], false], ['keyNotExists', [['key' => null], 'foo'], true], + ['keysExist', [['a' => 0, 'b' => null], ['a', 'b']], true], + ['keysExist', [['a' => 0, 'b' => null], []], true], + ['keysExist', [['a' => 0], ['a', 'b']], false], + ['keysExist', [['a' => 0], ['b', 'c']], false], + ['keysExist', [['key' => null], new ArrayIterator(['key'])], true], ['validArrayKey', ['abcd'], true], ['validArrayKey', [1], true], ['validArrayKey', [false], false], @@ -1078,6 +1083,11 @@ public static function getMethodsThatUseOtherMethods(): array 'args' => [111, 'test', 'Value must be an array without key test. Got: %s'], 'exceptionMessage' => 'Value must be an array without key test. Got: integer', ], + [ + 'method' => 'keysExist', + 'args' => [111, ['test'], 'Value must be an array with keys test. Got: %s'], + 'exceptionMessage' => 'Value must be an array with keys test. Got: integer', + ], [ 'method' => 'isInstanceOf', 'args' => [111, 'stdClass', 'Value must be an instance of stdClass. Got: %s'], diff --git a/tests/static-analysis/assert-keysExist.php b/tests/static-analysis/assert-keysExist.php new file mode 100644 index 00000000..fe8a3d26 --- /dev/null +++ b/tests/static-analysis/assert-keysExist.php @@ -0,0 +1,49 @@ + $keys + */ +function keysExist(array $array, iterable $keys): array +{ + return Assert::keysExist($array, $keys); +} + +/** + * @psalm-pure + * + * @param iterable $keys + */ +function nullOrKeysExist(?array $array, iterable $keys): ?array +{ + return Assert::nullOrKeysExist($array, $keys); +} + +/** + * @psalm-pure + * + * @param iterable $array + * @param iterable $keys + */ +function allKeysExist(iterable $array, iterable $keys): iterable +{ + return Assert::allKeysExist($array, $keys); +} + +/** + * @psalm-pure + * + * @param iterable $array + * @param iterable $keys + */ +function allNullOrKeysExist(iterable $array, iterable $keys): iterable +{ + return Assert::allNullOrKeysExist($array, $keys); +} diff --git a/tests/static-analysis/non-return/assert-keysExist.php b/tests/static-analysis/non-return/assert-keysExist.php new file mode 100644 index 00000000..f81d5cc6 --- /dev/null +++ b/tests/static-analysis/non-return/assert-keysExist.php @@ -0,0 +1,57 @@ + $keys + */ +function keysExist(array $array, iterable $keys): array +{ + Assert::keysExist($array, $keys); + + return $array; +} + +/** + * @psalm-pure + * + * @param iterable $keys + */ +function nullOrKeysExist(?array $array, iterable $keys): ?array +{ + Assert::nullOrKeysExist($array, $keys); + + return $array; +} + +/** + * @psalm-pure + * + * @param iterable $array + * @param iterable $keys + */ +function allKeysExist(iterable $array, iterable $keys): iterable +{ + Assert::allKeysExist($array, $keys); + + return $array; +} + +/** + * @psalm-pure + * + * @param iterable $array + * @param iterable $keys + */ +function allNullOrKeysExist(iterable $array, iterable $keys): iterable +{ + Assert::allNullOrKeysExist($array, $keys); + + return $array; +}