diff --git a/README.md b/README.md index b595a8b..826979d 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ Fluent assertion methods, inspired by `webmozart/assert`: - `notStartsWith` - `endsWith` - `notEndsWith` +- `regexMatch` +- `notRegexMatch` - `boolean` - `true` - `false` diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 20af096..38bf8c1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -55,9 +55,9 @@ parameters: path: src/PHPStan/Extension/ArraysRenameKeyReturnExtension.php - - message: '#^Using nullsafe property access on non\-nullable type PhpParser\\Node\\ArrayItem\. Use \-\> instead\.$#' + message: '#^Using nullsafe property access on non\-nullable type mixed\. Use \-\> instead\.$#' identifier: nullsafe.neverNull - count: 2 + count: 1 path: src/PHPStan/Lib/TypeNarrower.php - diff --git a/src/Assert.php b/src/Assert.php index bafdc21..2139c03 100644 --- a/src/Assert.php +++ b/src/Assert.php @@ -85,7 +85,7 @@ public static function isArray(mixed $value, ?string $message = null): array * @template T * @phpstan-assert T&list $value * - * @param T $value + * @param T $value * * @return T&list */ @@ -333,6 +333,53 @@ public static function stringable(mixed $value, ?string $message = null): string return $value; } + /** + * Assert value is a string AND matches regex pattern + * @template T + * @phpstan-assert string $value + * + * @param T $value + * + * @return T&string + */ + public static function regexMatch(mixed $value, string $pattern, ?string $message = null): string + { + self::string($value, $message); + $match = self::notFalse(preg_match($pattern, $value), $pattern . ' is not valid regex pattern'); + if ($match !== 1) { + throw ExceptionFactory::createException('a regex match for `' . $pattern . '`', $value, $message); + } + + return $value; + } + + /** + * Assert value is a string AND not matches regex pattern + * @template T + * @phpstan-assert string $value + * + * @param T $value + * + * @return T&string + */ + public static function notRegexMatch(mixed $value, string $pattern, ?string $message = null): string + { + self::string($value, $message); + $match = self::notFalse(preg_match($pattern, $value), $pattern . ' is not valid regex pattern'); + if ($match === 1) { + throw new RuntimeException( + sprintf( + 'Expecting value not to be a regex match for `%s`, `%s` was given%s', + $pattern, + $value, + $message === null ? '' : '. ' . $message + ) + ); + } + + return $value; + } + /** * Assert value is at least one of the given types * @template T diff --git a/tests/Unit/AssertTest.php b/tests/Unit/AssertTest.php index ec62a2f..c1c9ac1 100644 --- a/tests/Unit/AssertTest.php +++ b/tests/Unit/AssertTest.php @@ -164,6 +164,34 @@ public function testStringable(): void static::assertSame($stringable, Assert::stringable($stringable)); } + public function testRegex(): void + { + static::assertSame('string123', Assert::regexMatch('string123', '/^string\d+$/')); + } + + public function testRegexFailure(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Expecting value to be a regex match for `/^string\\d+$/`, `string (string)` was given. More context about failure.' + ); + Assert::regexMatch('string', '/^string\d+$/', 'More context about failure.'); + } + + public function testNotRegex(): void + { + static::assertSame('string', Assert::notRegexMatch('string', '/^string\d+$/')); + } + + public function testNotRegexFailure(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Expecting value not to be a regex match for `/^string\\d+$/`, `string123` was given. More context about failure.' + ); + Assert::notRegexMatch('string123', '/^string\d+$/', 'More context about failure.'); + } + #[TestWith([Assert::class])] #[TestWith(['DR\Utils\Assert'])] public function testClassString(string $value): void