diff --git a/library/Message/Placeholder/Quoted.php b/library/Message/Placeholder/Quoted.php new file mode 100644 index 000000000..31781b3c9 --- /dev/null +++ b/library/Message/Placeholder/Quoted.php @@ -0,0 +1,23 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +namespace Respect\Validation\Message\Placeholder; + +final class Quoted +{ + public function __construct( + private readonly string $value + ) { + } + + public function getValue(): string + { + return $this->value; + } +} diff --git a/library/Message/StandardRenderer.php b/library/Message/StandardRenderer.php index 195410d77..9251fa384 100644 --- a/library/Message/StandardRenderer.php +++ b/library/Message/StandardRenderer.php @@ -11,6 +11,7 @@ use ReflectionClass; use Respect\Stringifier\Stringifier; +use Respect\Validation\Message\Placeholder\Quoted; use Respect\Validation\Mode; use Respect\Validation\Result; use Respect\Validation\Rule; @@ -71,6 +72,10 @@ private function extractTemplates(Rule $rule): array private function placeholder(string $name, mixed $value, Translator $translator, ?string $modifier = null): string { + if ($modifier === 'quote' && is_string($value)) { + return $this->placeholder($name, new Quoted($value), $translator); + } + if ($modifier === 'raw' && is_scalar($value)) { return is_bool($value) ? (string) (int) $value : (string) $value; } diff --git a/library/Message/StandardStringifier.php b/library/Message/StandardStringifier.php index ce634a35d..8a9f8c50a 100644 --- a/library/Message/StandardStringifier.php +++ b/library/Message/StandardStringifier.php @@ -31,6 +31,7 @@ use Respect\Stringifier\Stringifiers\ResourceStringifier; use Respect\Stringifier\Stringifiers\StringableObjectStringifier; use Respect\Stringifier\Stringifiers\ThrowableObjectStringifier; +use Respect\Validation\Message\Stringifier\QuotedStringifier; final class StandardStringifier implements Stringifier { @@ -86,6 +87,7 @@ private function createStringifier(Quoter $quoter): Stringifier $stringifier->prependStringifier(new ThrowableObjectStringifier($jsonEncodableStringifier, $quoter)); $stringifier->prependStringifier(new DateTimeStringifier($quoter, DateTimeInterface::ATOM)); $stringifier->prependStringifier(new IteratorObjectStringifier($stringifier, $quoter)); + $stringifier->prependStringifier(new QuotedStringifier($quoter)); return $stringifier; } diff --git a/library/Message/Stringifier/QuotedStringifier.php b/library/Message/Stringifier/QuotedStringifier.php new file mode 100644 index 000000000..437a8a33d --- /dev/null +++ b/library/Message/Stringifier/QuotedStringifier.php @@ -0,0 +1,31 @@ + + * SPDX-License-Identifier: MIT + */ + +namespace Respect\Validation\Message\Stringifier; + +use Respect\Stringifier\Quoter; +use Respect\Stringifier\Stringifier; +use Respect\Validation\Message\Placeholder\Quoted; + +final class QuotedStringifier implements Stringifier +{ + public function __construct( + private readonly Quoter $quoter + ) { + } + + public function stringify(mixed $raw, int $depth): ?string + { + if (!$raw instanceof Quoted) { + return null; + } + + return $this->quoter->quote($raw->getValue(), $depth); + } +} diff --git a/library/Rules/Instance.php b/library/Rules/Instance.php index b62412109..57e47372c 100644 --- a/library/Rules/Instance.php +++ b/library/Rules/Instance.php @@ -16,8 +16,8 @@ #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] #[Template( - '{{name}} must be an instance of `{{class|raw}}`', - '{{name}} must not be an instance of `{{class|raw}}`', + '{{name}} must be an instance of {{class|quote}}', + '{{name}} must not be an instance of {{class|quote}}', )] final class Instance extends Standard { diff --git a/library/Rules/Regex.php b/library/Rules/Regex.php index 1364d6739..c11b95a6c 100644 --- a/library/Rules/Regex.php +++ b/library/Rules/Regex.php @@ -19,8 +19,8 @@ #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] #[Template( - '{{name}} must match the pattern `{{regex|raw}}`', - '{{name}} must not match the pattern `{{regex|raw}}`', + '{{name}} must match the pattern {{regex|quote}}', + '{{name}} must not match the pattern {{regex|quote}}', )] final class Regex extends Standard { diff --git a/tests/unit/Message/Stringifier/QuotedStringifierTest.php b/tests/unit/Message/Stringifier/QuotedStringifierTest.php new file mode 100644 index 000000000..6e1980b5c --- /dev/null +++ b/tests/unit/Message/Stringifier/QuotedStringifierTest.php @@ -0,0 +1,45 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +namespace Respect\Validation\Message\Stringifier; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; +use Respect\Stringifier\Quoters\StandardQuoter; +use Respect\Validation\Message\Placeholder\Quoted; +use Respect\Validation\Test\TestCase; + +#[CoversClass(QuotedStringifier::class)] +final class QuotedStringifierTest extends TestCase +{ + #[Test] + #[DataProvider('providerForAnyValues')] + public function itShouldNotStringifyWhenValueIsNotAnInstanceOfQuoted(mixed $value): void + { + $quoter = new StandardQuoter(1); + $stringifier = new QuotedStringifier($quoter); + + self::assertNull($stringifier->stringify($value, 0)); + } + + #[Test] + #[DataProvider('providerForStringTypes')] + public function itShouldStringifyWhenValueIsAnInstanceOfQuoted(string $value): void + { + $quoted = new Quoted($value); + $quoter = new StandardQuoter(1); + $stringifier = new QuotedStringifier($quoter); + + $expected = $quoter->quote($quoted->getValue(), 0); + $actual = $stringifier->stringify($quoted, 0); + + self::assertSame($expected, $actual); + } +}