Skip to content

Commit

Permalink
Create a stringifier for "listed" values
Browse files Browse the repository at this point in the history
This can be extremely useful when dealing with custom templates.
  • Loading branch information
henriquemoody committed Dec 27, 2024
1 parent 4379f66 commit a8ae57f
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 2 deletions.
20 changes: 20 additions & 0 deletions library/Message/Placeholder/Listed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Message\Placeholder;

final class Listed
{
/** @param array<int, mixed> $values */
public function __construct(
public readonly array $values,
public readonly string $lastGlue
) {
}
}
10 changes: 10 additions & 0 deletions library/Message/StandardRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@

use ReflectionClass;
use Respect\Stringifier\Stringifier;
use Respect\Validation\Message\Placeholder\Listed;
use Respect\Validation\Message\Placeholder\Quoted;
use Respect\Validation\Mode;
use Respect\Validation\Result;
use Respect\Validation\Rule;

use function is_array;
use function is_bool;
use function is_scalar;
use function is_string;
Expand Down Expand Up @@ -76,6 +78,14 @@ private function placeholder(string $name, mixed $value, Translator $translator,
return $this->placeholder($name, new Quoted($value), $translator);
}

if ($modifier === 'listOr' && is_array($value)) {
return $this->placeholder($name, new Listed($value, $translator->translate('or')), $translator);
}

if ($modifier === 'listAnd' && is_array($value)) {
return $this->placeholder($name, new Listed($value, $translator->translate('and')), $translator);
}

if ($modifier === 'raw' && is_scalar($value)) {
return is_bool($value) ? (string) (int) $value : (string) $value;
}
Expand Down
2 changes: 2 additions & 0 deletions library/Message/StandardStringifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use Respect\Stringifier\Stringifiers\ResourceStringifier;
use Respect\Stringifier\Stringifiers\StringableObjectStringifier;
use Respect\Stringifier\Stringifiers\ThrowableObjectStringifier;
use Respect\Validation\Message\Stringifier\ListedStringifier;
use Respect\Validation\Message\Stringifier\QuotedStringifier;

final class StandardStringifier implements Stringifier
Expand Down Expand Up @@ -88,6 +89,7 @@ private function createStringifier(Quoter $quoter): Stringifier
$stringifier->prependStringifier(new DateTimeStringifier($quoter, DateTimeInterface::ATOM));
$stringifier->prependStringifier(new IteratorObjectStringifier($stringifier, $quoter));
$stringifier->prependStringifier(new QuotedStringifier($quoter));
$stringifier->prependStringifier(new ListedStringifier($stringifier));

return $stringifier;
}
Expand Down
46 changes: 46 additions & 0 deletions library/Message/Stringifier/ListedStringifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

namespace Respect\Validation\Message\Stringifier;

use Respect\Stringifier\Stringifier;
use Respect\Validation\Message\Placeholder\Listed;

use function array_map;
use function array_pop;
use function count;
use function implode;
use function sprintf;

final class ListedStringifier implements Stringifier
{
public function __construct(
private readonly Stringifier $stringifier
) {
}

public function stringify(mixed $raw, int $depth): ?string
{
if (!$raw instanceof Listed) {
return null;
}

if (count($raw->values) === 0) {
return null;
}

$strings = array_map(fn ($value) => $this->stringifier->stringify($value, $depth + 1), $raw->values);
if (count($strings) < 3) {
return implode(sprintf(' %s ', $raw->lastGlue), $strings);
}
$lastString = array_pop($strings);

return sprintf('%s, %s %s', implode(', ', $strings), $raw->lastGlue, $lastString);
}
}
28 changes: 26 additions & 2 deletions tests/feature/TranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use Respect\Validation\Validator;
use Respect\Validation\ValidatorDefaults;

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

test('Scenario #2', expectMessage(
test('DateTimeDiff', expectMessage(
function (): void {
ValidatorDefaults::setTranslator(new ArrayTranslator([
'years' => 'anos',
Expand All @@ -44,3 +44,27 @@ function (): void {
},
'O número de anos entre agora e "1972-02-09" deve ser igual a 2',
));

test('Using "listOr"', expectMessage(
function (): void {
ValidatorDefaults::setTranslator(new ArrayTranslator([
'Your name must be {{haystack|listOr}}' => 'Seu nome deve ser {{haystack|listOr}}',
'or' => 'ou',
]));

v::templated(v::in(['Respect', 'Validation']), 'Your name must be {{haystack|listOr}}')->assert('');
},
'Seu nome deve ser "Respect" ou "Validation"',
));

test('Using "listAnd"', expectMessage(
function (): void {
ValidatorDefaults::setTranslator(new ArrayTranslator([
'{{haystack|listAnd}} are the only possible names' => '{{haystack|listAnd}} são os únicos nomes possíveis',
'and' => 'e',
]));

v::templated(v::in(['Respect', 'Validation']), '{{haystack|listAnd}} are the only possible names')->assert('');
},
'"Respect" e "Validation" são os únicos nomes possíveis',
));
60 changes: 60 additions & 0 deletions tests/unit/Message/Stringifier/ListedStringifierTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* 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\Stringifiers\JsonEncodableStringifier;
use Respect\Validation\Message\Placeholder\Listed;
use Respect\Validation\Test\TestCase;

#[CoversClass(ListedStringifier::class)]
final class ListedStringifierTest extends TestCase
{
#[Test]
#[DataProvider('providerForAnyValues')]
public function itShouldNotStringifyWhenValueIsNotAnInstanceOfListed(mixed $value): void
{
$quoter = new JsonEncodableStringifier();
$stringifier = new ListedStringifier($quoter);

self::assertNull($stringifier->stringify($value, 0));
}

#[Test]
public function itShouldNotStringifyEmptyListed(): void
{
$stringifier = new ListedStringifier(new JsonEncodableStringifier());

self::assertNull($stringifier->stringify(new Listed([], '-'), 0));
}

#[Test]
#[DataProvider('providerForListed')]
public function itShouldStringifyWhenValueIsAnInstanceOfListed(Listed $listed, string $expected): void
{
$stringifier = new ListedStringifier(new JsonEncodableStringifier());

$actual = $stringifier->stringify($listed, 0);

self::assertSame($expected, $actual);
}

/** @return array<string, array{Listed, string}> */
public static function providerForListed(): array
{
return [
'1 item' => [new Listed([1], 'and'), '1'],
'2 items' => [new Listed([1, 2], 'and'), '1 and 2'],
'3 items' => [new Listed([1, 2, 3], 'or'), '1, 2, or 3'],
];
}
}

0 comments on commit a8ae57f

Please sign in to comment.