Skip to content

Commit a8ae57f

Browse files
committed
Create a stringifier for "listed" values
This can be extremely useful when dealing with custom templates.
1 parent 4379f66 commit a8ae57f

File tree

6 files changed

+164
-2
lines changed

6 files changed

+164
-2
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Respect\Validation\Message\Placeholder;
11+
12+
final class Listed
13+
{
14+
/** @param array<int, mixed> $values */
15+
public function __construct(
16+
public readonly array $values,
17+
public readonly string $lastGlue
18+
) {
19+
}
20+
}

library/Message/StandardRenderer.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111

1212
use ReflectionClass;
1313
use Respect\Stringifier\Stringifier;
14+
use Respect\Validation\Message\Placeholder\Listed;
1415
use Respect\Validation\Message\Placeholder\Quoted;
1516
use Respect\Validation\Mode;
1617
use Respect\Validation\Result;
1718
use Respect\Validation\Rule;
1819

20+
use function is_array;
1921
use function is_bool;
2022
use function is_scalar;
2123
use function is_string;
@@ -76,6 +78,14 @@ private function placeholder(string $name, mixed $value, Translator $translator,
7678
return $this->placeholder($name, new Quoted($value), $translator);
7779
}
7880

81+
if ($modifier === 'listOr' && is_array($value)) {
82+
return $this->placeholder($name, new Listed($value, $translator->translate('or')), $translator);
83+
}
84+
85+
if ($modifier === 'listAnd' && is_array($value)) {
86+
return $this->placeholder($name, new Listed($value, $translator->translate('and')), $translator);
87+
}
88+
7989
if ($modifier === 'raw' && is_scalar($value)) {
8090
return is_bool($value) ? (string) (int) $value : (string) $value;
8191
}

library/Message/StandardStringifier.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Respect\Stringifier\Stringifiers\ResourceStringifier;
3232
use Respect\Stringifier\Stringifiers\StringableObjectStringifier;
3333
use Respect\Stringifier\Stringifiers\ThrowableObjectStringifier;
34+
use Respect\Validation\Message\Stringifier\ListedStringifier;
3435
use Respect\Validation\Message\Stringifier\QuotedStringifier;
3536

3637
final class StandardStringifier implements Stringifier
@@ -88,6 +89,7 @@ private function createStringifier(Quoter $quoter): Stringifier
8889
$stringifier->prependStringifier(new DateTimeStringifier($quoter, DateTimeInterface::ATOM));
8990
$stringifier->prependStringifier(new IteratorObjectStringifier($stringifier, $quoter));
9091
$stringifier->prependStringifier(new QuotedStringifier($quoter));
92+
$stringifier->prependStringifier(new ListedStringifier($stringifier));
9193

9294
return $stringifier;
9395
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
7+
* SPDX-License-Identifier: MIT
8+
*/
9+
10+
namespace Respect\Validation\Message\Stringifier;
11+
12+
use Respect\Stringifier\Stringifier;
13+
use Respect\Validation\Message\Placeholder\Listed;
14+
15+
use function array_map;
16+
use function array_pop;
17+
use function count;
18+
use function implode;
19+
use function sprintf;
20+
21+
final class ListedStringifier implements Stringifier
22+
{
23+
public function __construct(
24+
private readonly Stringifier $stringifier
25+
) {
26+
}
27+
28+
public function stringify(mixed $raw, int $depth): ?string
29+
{
30+
if (!$raw instanceof Listed) {
31+
return null;
32+
}
33+
34+
if (count($raw->values) === 0) {
35+
return null;
36+
}
37+
38+
$strings = array_map(fn ($value) => $this->stringifier->stringify($value, $depth + 1), $raw->values);
39+
if (count($strings) < 3) {
40+
return implode(sprintf(' %s ', $raw->lastGlue), $strings);
41+
}
42+
$lastString = array_pop($strings);
43+
44+
return sprintf('%s, %s %s', implode(', ', $strings), $raw->lastGlue, $lastString);
45+
}
46+
}

tests/feature/TranslatorTest.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use Respect\Validation\Validator;
1212
use Respect\Validation\ValidatorDefaults;
1313

14-
test('Scenario #1', expectFullMessage(
14+
test('Various translations', expectFullMessage(
1515
function (): void {
1616
ValidatorDefaults::setTranslator(new ArrayTranslator([
1717
'All the required rules must pass for {{name}}' => 'Todas as regras requeridas devem passar para {{name}}',
@@ -32,7 +32,7 @@ function (): void {
3232
FULL_MESSAGE,
3333
));
3434

35-
test('Scenario #2', expectMessage(
35+
test('DateTimeDiff', expectMessage(
3636
function (): void {
3737
ValidatorDefaults::setTranslator(new ArrayTranslator([
3838
'years' => 'anos',
@@ -44,3 +44,27 @@ function (): void {
4444
},
4545
'O número de anos entre agora e "1972-02-09" deve ser igual a 2',
4646
));
47+
48+
test('Using "listOr"', expectMessage(
49+
function (): void {
50+
ValidatorDefaults::setTranslator(new ArrayTranslator([
51+
'Your name must be {{haystack|listOr}}' => 'Seu nome deve ser {{haystack|listOr}}',
52+
'or' => 'ou',
53+
]));
54+
55+
v::templated(v::in(['Respect', 'Validation']), 'Your name must be {{haystack|listOr}}')->assert('');
56+
},
57+
'Seu nome deve ser "Respect" ou "Validation"',
58+
));
59+
60+
test('Using "listAnd"', expectMessage(
61+
function (): void {
62+
ValidatorDefaults::setTranslator(new ArrayTranslator([
63+
'{{haystack|listAnd}} are the only possible names' => '{{haystack|listAnd}} são os únicos nomes possíveis',
64+
'and' => 'e',
65+
]));
66+
67+
v::templated(v::in(['Respect', 'Validation']), '{{haystack|listAnd}} are the only possible names')->assert('');
68+
},
69+
'"Respect" e "Validation" são os únicos nomes possíveis',
70+
));
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
5+
* SPDX-License-Identifier: MIT
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Respect\Validation\Message\Stringifier;
11+
12+
use PHPUnit\Framework\Attributes\CoversClass;
13+
use PHPUnit\Framework\Attributes\DataProvider;
14+
use PHPUnit\Framework\Attributes\Test;
15+
use Respect\Stringifier\Stringifiers\JsonEncodableStringifier;
16+
use Respect\Validation\Message\Placeholder\Listed;
17+
use Respect\Validation\Test\TestCase;
18+
19+
#[CoversClass(ListedStringifier::class)]
20+
final class ListedStringifierTest extends TestCase
21+
{
22+
#[Test]
23+
#[DataProvider('providerForAnyValues')]
24+
public function itShouldNotStringifyWhenValueIsNotAnInstanceOfListed(mixed $value): void
25+
{
26+
$quoter = new JsonEncodableStringifier();
27+
$stringifier = new ListedStringifier($quoter);
28+
29+
self::assertNull($stringifier->stringify($value, 0));
30+
}
31+
32+
#[Test]
33+
public function itShouldNotStringifyEmptyListed(): void
34+
{
35+
$stringifier = new ListedStringifier(new JsonEncodableStringifier());
36+
37+
self::assertNull($stringifier->stringify(new Listed([], '-'), 0));
38+
}
39+
40+
#[Test]
41+
#[DataProvider('providerForListed')]
42+
public function itShouldStringifyWhenValueIsAnInstanceOfListed(Listed $listed, string $expected): void
43+
{
44+
$stringifier = new ListedStringifier(new JsonEncodableStringifier());
45+
46+
$actual = $stringifier->stringify($listed, 0);
47+
48+
self::assertSame($expected, $actual);
49+
}
50+
51+
/** @return array<string, array{Listed, string}> */
52+
public static function providerForListed(): array
53+
{
54+
return [
55+
'1 item' => [new Listed([1], 'and'), '1'],
56+
'2 items' => [new Listed([1, 2], 'and'), '1 and 2'],
57+
'3 items' => [new Listed([1, 2, 3], 'or'), '1, 2, or 3'],
58+
];
59+
}
60+
}

0 commit comments

Comments
 (0)