Enable adding modifiers without changing InterpolationRenderer

The `InterpolationRenderer` was violating the open-closed principle,
because every time we would want to add a new modifier, we would need to
change its implementation.

This commit changes that behaviour by creating a `Modifier` interface.
The classes implementing that interface are using a chain of
responsibility to pass the data to the next one. Using a chain of
responsibility makes a lot of sense, since it's only possible to have
one modifier at a time.
This commit is contained in:
Henrique Moody 2025-12-30 10:33:05 +01:00
commit cd6bcd470b
No known key found for this signature in database
GPG key ID: 221E9281655813A6
18 changed files with 825 additions and 131 deletions

View file

@ -19,6 +19,13 @@ use Respect\Validation\Message\Formatter\NestedArrayFormatter;
use Respect\Validation\Message\Formatter\NestedListStringFormatter;
use Respect\Validation\Message\Formatter\TemplateResolver;
use Respect\Validation\Message\InterpolationRenderer;
use Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Modifier\ListAndModifier;
use Respect\Validation\Message\Modifier\ListOrModifier;
use Respect\Validation\Message\Modifier\QuoteModifier;
use Respect\Validation\Message\Modifier\RawModifier;
use Respect\Validation\Message\Modifier\StringifyModifier;
use Respect\Validation\Message\Modifier\TransModifier;
use Respect\Validation\Message\Renderer;
use Respect\Validation\Message\Translator;
use Respect\Validation\Message\Translator\DummyTranslator;
@ -49,6 +56,20 @@ final class ContainerRegistry
'respect.validation.formatter.full_message' => autowire(NestedListStringFormatter::class),
'respect.validation.formatter.messages' => autowire(NestedArrayFormatter::class),
'respect.validation.ignored_backtrace_paths' => [__DIR__ . '/Validator.php'],
Modifier::class => factory(static fn(Container $container) => new TransModifier(
$container->get(Translator::class),
new ListOrModifier(
$container->get(Translator::class),
new ListAndModifier(
$container->get(Translator::class),
new QuoteModifier(
new RawModifier(
new StringifyModifier($container->get(Stringifier::class)),
),
),
),
),
)),
Validator::class => factory(static fn(Container $container) => new Validator(
$container->get(Factory::class),
$container->get(Renderer::class),

View file

@ -9,26 +9,21 @@ declare(strict_types=1);
namespace Respect\Validation\Message;
use Respect\Stringifier\Stringifier;
use Respect\Validation\Message\Formatter\TemplateResolver;
use Respect\Validation\Message\Placeholder\Listed;
use Respect\Validation\Message\Placeholder\Quoted;
use Respect\Validation\Name;
use Respect\Validation\Result;
use function array_key_exists;
use function is_array;
use function is_bool;
use function is_scalar;
use function array_pad;
use function assert;
use function is_string;
use function preg_replace_callback;
use function print_r;
final readonly class InterpolationRenderer implements Renderer
{
public function __construct(
private Translator $translator,
private Stringifier $stringifier = new ValidationStringifier(),
private Modifier $modifier,
private TemplateResolver $templateResolver = new TemplateResolver(),
) {
}
@ -44,13 +39,7 @@ final readonly class InterpolationRenderer implements Renderer
$rendered = (string) preg_replace_callback(
'/{{(\w+)(\|([^}]+))?}}/',
function (array $matches) use ($parameters) {
if (!array_key_exists($matches[1], $parameters)) {
return $matches[0];
}
return $this->placeholder($matches[1], $parameters[$matches[1]], $matches[3] ?? null);
},
fn(array $matches) => $this->processPlaceholder($parameters, $matches),
$this->translator->translate($givenTemplate ?? $ruleTemplate),
);
@ -61,32 +50,20 @@ final readonly class InterpolationRenderer implements Renderer
return $rendered;
}
private function placeholder(
string $name,
mixed $value,
string|null $modifier = null,
): string {
if ($modifier === 'quote' && is_string($value)) {
return $this->placeholder($name, new Quoted($value));
/**
* @param array<string, mixed> $parameters
* @param array<int, string|null> $matches
*/
private function processPlaceholder(array $parameters, array $matches): string
{
[$placeholder, $name, , $pipe] = array_pad($matches, 4, null);
assert(is_string($placeholder));
assert(is_string($name));
if (!array_key_exists($name, $parameters)) {
return $placeholder;
}
if ($modifier === 'listOr' && is_array($value)) {
return $this->placeholder($name, new Listed($value, $this->translator->translate('or')));
}
if ($modifier === 'listAnd' && is_array($value)) {
return $this->placeholder($name, new Listed($value, $this->translator->translate('and')));
}
if ($modifier === 'raw' && is_scalar($value)) {
return is_bool($value) ? (string) (int) $value : (string) $value;
}
if ($modifier === 'trans' && is_string($value)) {
return $this->translator->translate($value);
}
return $this->stringifier->stringify($value, 0) ?? print_r($value, true);
return $this->modifier->modify($parameters[$name], $pipe);
}
private function getName(Result $result): mixed

View file

@ -0,0 +1,15 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message;
interface Modifier
{
public function modify(mixed $value, string|null $pipe): string;
}

View file

@ -0,0 +1,37 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Placeholder\Listed;
use Respect\Validation\Message\Translator;
use function is_array;
final readonly class ListAndModifier implements Modifier
{
public function __construct(
private Translator $translator,
private Modifier $nextModifier,
) {
}
public function modify(mixed $value, string|null $pipe): string
{
if ($pipe !== 'listAnd' || !is_array($value)) {
return $this->nextModifier->modify($value, $pipe);
}
return $this->nextModifier->modify(
new Listed($value, $this->translator->translate('and')),
null,
);
}
}

View file

@ -0,0 +1,37 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Placeholder\Listed;
use Respect\Validation\Message\Translator;
use function is_array;
final readonly class ListOrModifier implements Modifier
{
public function __construct(
private Translator $translator,
private Modifier $nextModifier,
) {
}
public function modify(mixed $value, string|null $pipe): string
{
if ($pipe !== 'listOr' || !is_array($value)) {
return $this->nextModifier->modify($value, $pipe);
}
return $this->nextModifier->modify(
new Listed($value, $this->translator->translate('or')),
null,
);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Placeholder\Quoted;
use function is_string;
final readonly class QuoteModifier implements Modifier
{
public function __construct(
private Modifier $nextModifier,
) {
}
public function modify(mixed $value, string|null $pipe): string
{
if ($pipe !== 'quote' || !is_string($value)) {
return $this->nextModifier->modify($value, $pipe);
}
return $this->nextModifier->modify(new Quoted($value), null);
}
}

View file

@ -0,0 +1,36 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Modifier;
use function is_bool;
use function is_scalar;
final readonly class RawModifier implements Modifier
{
public function __construct(
private Modifier $nextModifier,
) {
}
public function modify(mixed $value, string|null $pipe): string
{
if ($pipe !== 'raw') {
return $this->nextModifier->modify($value, $pipe);
}
if (!is_scalar($value)) {
return $this->nextModifier->modify($value, null);
}
return is_bool($value) ? (string) (int) $value : (string) $value;
}
}

View file

@ -0,0 +1,37 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use Respect\Stringifier\Stringifier;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Message\Modifier;
use function print_r;
use function sprintf;
final readonly class StringifyModifier implements Modifier
{
public function __construct(
private Stringifier $stringifier,
) {
}
public function modify(mixed $value, string|null $pipe): string
{
if ($pipe !== null) {
throw new ComponentException(sprintf(
'StringifyModifier only accepts null as pipe but "%s" was given.',
$pipe,
));
}
return $this->stringifier->stringify($value, 0) ?? print_r($value, true);
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Modifier;
use Respect\Validation\Message\Translator;
use function is_string;
final readonly class TransModifier implements Modifier
{
public function __construct(
private Translator $translator,
private Modifier $nextModifier,
) {
}
public function modify(mixed $value, string|null $pipe): string
{
if ($pipe !== 'trans' || !is_string($value)) {
return $this->nextModifier->modify($value, $pipe);
}
return $this->translator->translate($value);
}
}

View file

@ -0,0 +1,20 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Test\Message;
use Respect\Stringifier\Stringifier;
final class NullStringifier implements Stringifier
{
public function stringify(mixed $raw, int $depth): string|null
{
return null;
}
}

View file

@ -0,0 +1,23 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Test\Message;
use Respect\Validation\Message\Modifier;
use function print_r;
use function sprintf;
final readonly class TestingModifier implements Modifier
{
public function modify(mixed $value, string|null $pipe): string
{
return $pipe ? sprintf('%s(%s)', $pipe, print_r($value, true)) : print_r($value, true);
}
}

View file

@ -15,7 +15,7 @@ use Respect\Validation\Message\Translator\ArrayTranslator;
use Respect\Validation\Message\Translator\DummyTranslator;
use Respect\Validation\Name;
use Respect\Validation\Test\Builders\ResultBuilder;
use Respect\Validation\Test\Message\TestingStringifier;
use Respect\Validation\Test\Message\TestingModifier;
use Respect\Validation\Test\TestCase;
use function sprintf;
@ -26,7 +26,7 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultWithCustomTemplate(): void
{
$renderer = new InterpolationRenderer(new DummyTranslator(), new TestingStringifier());
$renderer = new InterpolationRenderer(new DummyTranslator(), new TestingModifier());
$result = (new ResultBuilder())->template('This is my template')->build();
@ -36,9 +36,8 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultProcessingParametersInTheTemplate(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$key = 'foo';
$value = true;
@ -49,70 +48,27 @@ final class InterpolationRendererTest extends TestCase
->build();
self::assertSame(
'Will replace ' . $stringifier->stringify($value, 0),
'Will replace ' . $modifier->modify($value, null),
$renderer->render($result, []),
);
}
#[Test]
public function itShouldRenderResultProcessingRawParametersInTheTemplate(): void
public function itShouldRenderResultProcessingModifierParametersInTheTemplate(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$key = 'foo';
$value = 0.1;
$result = (new ResultBuilder())
->template(sprintf('Will replace {{%1$s}} and {{%1$s|raw}}', $key))
->template(sprintf('Will replace {{%1$s}} and {{%1$s|modifier}}', $key))
->parameters([$key => $value])
->build();
self::assertSame(
sprintf('Will replace %s and 0.1', $stringifier->stringify($value, 0)),
$renderer->render($result, []),
);
}
#[Test]
public function itShouldRenderResultProcessingRawBooleanParametersInTheTemplate(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$key = 'foo';
$value = false;
$result = (new ResultBuilder())
->template(sprintf('Will replace {{%1$s}} and {{%1$s|raw}}', $key))
->parameters([$key => $value])
->build();
self::assertSame(
sprintf('Will replace %s and 0', $stringifier->stringify($value, 0)),
$renderer->render($result, []),
);
}
#[Test]
public function itShouldRenderResultProcessingTranslatableParametersInTheTemplate(): void
{
$key = 'foo';
$value = 'original';
$translation = 'translated';
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new ArrayTranslator([$value => $translation]), $stringifier);
$result = (new ResultBuilder())
->template(sprintf('Will replace {{%1$s}} and {{%1$s|trans}}', $key))
->parameters([$key => $value])
->build();
self::assertSame(
sprintf('Will replace %s and %s', $stringifier->stringify($value, 0), $translation),
sprintf('Will replace %s and %s', $modifier->modify($value, null), $modifier->modify($value, 'modifier')),
$renderer->render($result, []),
);
}
@ -120,9 +76,8 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultProcessingNameParameterWhenItIsInTheTemplateAndItIsString(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$value = 'original';
@ -132,7 +87,7 @@ final class InterpolationRendererTest extends TestCase
->build();
self::assertSame(
sprintf('Will replace %s', $stringifier->stringify(new Name($value), 0) ?? 'FAILED'),
sprintf('Will replace %s', $modifier->modify(new Name($value), null)),
$renderer->render($result, []),
);
}
@ -140,9 +95,8 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultProcessingNameParameterWhenItIsInTheTemplateAndItIsNotString(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$value = true;
@ -154,7 +108,7 @@ final class InterpolationRendererTest extends TestCase
self::assertSame(
sprintf(
'Will replace %s',
$stringifier->stringify($value, 0),
$modifier->modify($value, null),
),
$renderer->render($result, []),
);
@ -163,8 +117,8 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultProcessingNameAsSomeParameterInTheTemplate(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$name = 'my name';
@ -174,7 +128,7 @@ final class InterpolationRendererTest extends TestCase
->build();
self::assertSame(
'Will replace ' . $stringifier->stringify(new Name($name), 0),
'Will replace ' . $modifier->modify(new Name($name), null),
$renderer->render($result, []),
);
}
@ -182,9 +136,8 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultProcessingInputAsNameWhenResultHasNoName(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$input = 42;
@ -196,7 +149,7 @@ final class InterpolationRendererTest extends TestCase
self::assertSame(
sprintf(
'Will replace %s',
$stringifier->stringify($input, 0),
$modifier->modify($input, null),
),
$renderer->render($result, []),
);
@ -205,9 +158,8 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultProcessingInputAsSomeParameterInTheTemplate(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$input = 42;
@ -217,7 +169,7 @@ final class InterpolationRendererTest extends TestCase
->build();
self::assertSame(
sprintf('Will replace %s', $stringifier->stringify($input, 0)),
sprintf('Will replace %s', $modifier->modify($input, null)),
$renderer->render($result, []),
);
}
@ -227,9 +179,8 @@ final class InterpolationRendererTest extends TestCase
{
$parameterNameValue = 'fake name';
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$result = (new ResultBuilder())
->template('Will replace {{subject}}')
@ -238,7 +189,7 @@ final class InterpolationRendererTest extends TestCase
->build();
self::assertSame(
sprintf('Will replace %s', $stringifier->stringify(new Name($parameterNameValue), 0)),
sprintf('Will replace %s', $modifier->modify(new Name($parameterNameValue), null)),
$renderer->render($result, []),
);
}
@ -246,9 +197,8 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultNotOverwritingInputParameterWithRealInput(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$input = 'real input';
@ -259,7 +209,7 @@ final class InterpolationRendererTest extends TestCase
->build();
self::assertSame(
sprintf('Will replace %s', $stringifier->stringify($input, 0)),
sprintf('Will replace %s', $modifier->modify($input, null)),
$renderer->render($result, []),
);
}
@ -267,7 +217,8 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultProcessingNonExistingParameters(): void
{
$renderer = new InterpolationRenderer(new DummyTranslator(), new TestingStringifier());
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $modifier);
$result = (new ResultBuilder())
->template('Will not replace {{unknown}}')
@ -283,7 +234,8 @@ final class InterpolationRendererTest extends TestCase
$translations = [$template => 'This is my translated template with {{foo}}'];
$translator = new ArrayTranslator($translations);
$renderer = new InterpolationRenderer($translator, new TestingStringifier());
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer($translator, $modifier);
$result = (new ResultBuilder())
->template($template)
@ -295,15 +247,16 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultWithNonCustomTemplate(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$translator = new DummyTranslator();
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer($translator, $modifier);
$result = (new ResultBuilder())->build();
self::assertSame(
sprintf(
'%s must be a valid stub',
$stringifier->stringify($result->input, 0),
$modifier->modify($result->input, null),
),
$renderer->render($result, []),
);
@ -312,15 +265,16 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultWithNonCustomTemplateAndInvertedMode(): void
{
$stringifier = new TestingStringifier();
$renderer = new InterpolationRenderer(new DummyTranslator(), $stringifier);
$translator = new DummyTranslator();
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer($translator, $modifier);
$result = (new ResultBuilder())->hasInvertedMode()->build();
self::assertSame(
sprintf(
'%s must not be a valid stub',
$stringifier->stringify($result->input, 0),
$modifier->modify($result->input, null),
),
$renderer->render($result, []),
);
@ -329,7 +283,9 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultWithNonCustomTemplateWhenCannotFindAttachedTemplate(): void
{
$renderer = new InterpolationRenderer(new DummyTranslator(), new TestingStringifier());
$translator = new DummyTranslator();
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer($translator, $modifier);
$result = (new ResultBuilder())->template('__not_standard__')->hasInvertedMode()->build();
@ -342,7 +298,9 @@ final class InterpolationRendererTest extends TestCase
#[Test]
public function itShouldRenderResultWithItsAdjacentsWhenItHasNoCustomTemplate(): void
{
$renderer = new InterpolationRenderer(new DummyTranslator(), new TestingStringifier());
$translator = new DummyTranslator();
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer($translator, $modifier);
$result = (new ResultBuilder())->template('__1st__')
->adjacent(
@ -368,7 +326,9 @@ final class InterpolationRendererTest extends TestCase
->adjacent((new ResultBuilder())->template('and this is a adjacent')->build())
->build();
$renderer = new InterpolationRenderer(new DummyTranslator(), new TestingStringifier());
$translator = new DummyTranslator();
$modifier = new TestingModifier();
$renderer = new InterpolationRenderer($translator, $modifier);
self::assertSame($template, $renderer->render($result, []));
}

View file

@ -0,0 +1,69 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Message\Placeholder\Listed;
use Respect\Validation\Message\Translator\ArrayTranslator;
use Respect\Validation\Test\Message\TestingModifier;
use Respect\Validation\Test\TestCase;
#[CoversClass(ListAndModifier::class)]
final class ListAndModifierTest extends TestCase
{
#[Test]
public function itShouldNotModifyWhenModifierIsNotListAnd(): void
{
$translator = new ArrayTranslator(['and' => 'and']);
$nextModifier = new TestingModifier();
$modifier = new ListAndModifier($translator, $nextModifier);
$value = ['item1', 'item2'];
$pipe = 'listOr';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, $pipe), $result);
}
#[Test]
public function itShouldNotModifyWhenValueIsNotArray(): void
{
$translator = new ArrayTranslator(['and' => 'and']);
$nextModifier = new TestingModifier();
$modifier = new ListAndModifier($translator, $nextModifier);
$value = 'not an array';
$pipe = 'listAnd';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, $pipe), $result);
}
#[Test]
public function itShouldModifyWhenModifierIsListAndAndValueIsArray(): void
{
$translator = new ArrayTranslator(['and' => 'and']);
$nextModifier = new TestingModifier();
$modifier = new ListAndModifier($translator, $nextModifier);
$value = ['item1', 'item2', 'item3'];
$pipe = 'listAnd';
$result = $modifier->modify($value, $pipe);
$expectedValue = new Listed($value, $translator->translate('and'));
$expected = $nextModifier->modify($expectedValue, null);
self::assertSame($expected, $result);
}
}

View file

@ -0,0 +1,69 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Message\Placeholder\Listed;
use Respect\Validation\Message\Translator\ArrayTranslator;
use Respect\Validation\Test\Message\TestingModifier;
use Respect\Validation\Test\TestCase;
#[CoversClass(ListOrModifier::class)]
final class ListOrModifierTest extends TestCase
{
#[Test]
public function itShouldNotModifyWhenModifierIsNotListOr(): void
{
$translator = new ArrayTranslator(['or' => 'or']);
$nextModifier = new TestingModifier();
$modifier = new ListOrModifier($translator, $nextModifier);
$value = ['item1', 'item2'];
$pipe = 'listAnd';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, $pipe), $result);
}
#[Test]
public function itShouldNotModifyWhenValueIsNotArray(): void
{
$translator = new ArrayTranslator(['or' => 'or']);
$nextModifier = new TestingModifier();
$modifier = new ListOrModifier($translator, $nextModifier);
$value = 'not an array';
$pipe = 'listOr';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, $pipe), $result);
}
#[Test]
public function itShouldModifyWhenModifierIsListOrAndValueIsArray(): void
{
$translator = new ArrayTranslator(['or' => 'or']);
$nextModifier = new TestingModifier();
$modifier = new ListOrModifier($translator, $nextModifier);
$value = ['item1', 'item2', 'item3'];
$pipe = 'listOr';
$result = $modifier->modify($value, $pipe);
$expectedValue = new Listed($value, $translator->translate('or'));
$expected = $nextModifier->modify($expectedValue, null);
self::assertSame($expected, $result);
}
}

View file

@ -0,0 +1,65 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Message\Placeholder\Quoted;
use Respect\Validation\Test\Message\TestingModifier;
use Respect\Validation\Test\TestCase;
#[CoversClass(QuoteModifier::class)]
final class QuoteModifierTest extends TestCase
{
#[Test]
public function itShouldNotModifyWhenModifierIsNotQuote(): void
{
$nextModifier = new TestingModifier();
$modifier = new QuoteModifier($nextModifier);
$value = 'some string';
$pipe = 'notQuote';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, $pipe), $result);
}
#[Test]
public function itShouldNotModifyWhenValueIsNotString(): void
{
$nextModifier = new TestingModifier();
$modifier = new QuoteModifier($nextModifier);
$value = ['not', 'a', 'string'];
$pipe = 'quote';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, $pipe), $result);
}
#[Test]
public function itShouldModifyWhenModifierIsQuoteAndValueIsString(): void
{
$nextModifier = new TestingModifier();
$modifier = new QuoteModifier($nextModifier);
$value = 'some string';
$pipe = 'quote';
$result = $modifier->modify($value, $pipe);
$expectedValue = new Quoted($value);
$expected = $nextModifier->modify($expectedValue, null);
self::assertSame($expected, $result);
}
}

View file

@ -0,0 +1,117 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Test\Message\TestingModifier;
use Respect\Validation\Test\TestCase;
#[CoversClass(RawModifier::class)]
final class RawModifierTest extends TestCase
{
#[Test]
public function itShouldNotModifyWhenModifierIsNotRaw(): void
{
$nextModifier = new TestingModifier();
$modifier = new RawModifier($nextModifier);
$value = 'some value';
$pipe = 'notRaw';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, $pipe), $result);
}
#[Test]
public function itShouldNotModifyWhenValueIsNotScalar(): void
{
$nextModifier = new TestingModifier();
$modifier = new RawModifier($nextModifier);
$value = ['not', 'scalar'];
$pipe = 'raw';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, null), $result);
}
#[Test]
public function itShouldModifyWhenModifierIsRawAndValueIsScalarString(): void
{
$nextModifier = new TestingModifier();
$modifier = new RawModifier($nextModifier);
$value = 'some string';
$pipe = 'raw';
$result = $modifier->modify($value, $pipe);
self::assertSame($value, $result);
}
#[Test]
public function itShouldModifyWhenModifierIsRawAndValueIsScalarInt(): void
{
$nextModifier = new TestingModifier();
$modifier = new RawModifier($nextModifier);
$value = 123;
$pipe = 'raw';
$result = $modifier->modify($value, $pipe);
self::assertSame('123', $result);
}
#[Test]
public function itShouldModifyWhenModifierIsRawAndValueIsScalarFloat(): void
{
$nextModifier = new TestingModifier();
$modifier = new RawModifier($nextModifier);
$value = 123.456;
$pipe = 'raw';
$result = $modifier->modify($value, $pipe);
self::assertSame('123.456', $result);
}
#[Test]
public function itShouldModifyWhenModifierIsRawAndValueIsScalarBoolTrue(): void
{
$nextModifier = new TestingModifier();
$modifier = new RawModifier($nextModifier);
$value = true;
$pipe = 'raw';
$result = $modifier->modify($value, $pipe);
self::assertSame('1', $result);
}
#[Test]
public function itShouldModifyWhenModifierIsRawAndValueIsScalarBoolFalse(): void
{
$nextModifier = new TestingModifier();
$modifier = new RawModifier($nextModifier);
$value = false;
$pipe = 'raw';
$result = $modifier->modify($value, $pipe);
self::assertSame('0', $result);
}
}

View file

@ -0,0 +1,63 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Test\Message\NullStringifier;
use Respect\Validation\Test\Message\TestingStringifier;
use Respect\Validation\Test\TestCase;
use function print_r;
use function sprintf;
#[CoversClass(StringifyModifier::class)]
final class StringifyModifierTest extends TestCase
{
#[Test]
public function itShouldUseStringifierWhenAvailable(): void
{
$value = ['some', 'array'];
$stringifier = new TestingStringifier();
$modifier = new StringifyModifier($stringifier);
$expected = $stringifier->stringify($value, 0);
self::assertSame($expected, $modifier->modify($value, null));
}
#[Test]
public function itShouldUseFallbackWhenStringifierIsNull(): void
{
$value = ['some', 'array'];
$expected = print_r($value, true);
$modifier = new StringifyModifier(new NullStringifier());
self::assertSame($expected, $modifier->modify($value, null));
}
#[Test]
public function itShouldFailWhenPipeParameterIsGiven(): void
{
$pipe = 'someModifier';
$modifier = new StringifyModifier(new TestingStringifier());
$this->expectExceptionObject(new ComponentException(sprintf(
'StringifyModifier only accepts null as pipe but "%s" was given.',
$pipe,
)));
$modifier->modify(['some', 'array'], $pipe);
}
}

View file

@ -0,0 +1,83 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Message\Modifier;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Message\Translator\ArrayTranslator;
use Respect\Validation\Test\Message\TestingModifier;
use Respect\Validation\Test\TestCase;
#[CoversClass(TransModifier::class)]
final class TransModifierTest extends TestCase
{
#[Test]
public function itShouldNotModifyWhenModifierIsNotTrans(): void
{
$translator = new ArrayTranslator(['message' => 'translated message']);
$nextModifier = new TestingModifier();
$modifier = new TransModifier($translator, $nextModifier);
$value = 'message';
$pipe = 'notTrans';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, $pipe), $result);
}
#[Test]
public function itShouldNotModifyWhenValueIsNotString(): void
{
$translator = new ArrayTranslator(['message' => 'translated message']);
$nextModifier = new TestingModifier();
$modifier = new TransModifier($translator, $nextModifier);
$value = ['not', 'a', 'string'];
$pipe = 'trans';
$result = $modifier->modify($value, $pipe);
self::assertSame($nextModifier->modify($value, $pipe), $result);
}
#[Test]
public function itShouldModifyWhenModifierIsTransAndValueIsString(): void
{
$key = 'message';
$translatedMessage = 'translated message';
$translator = new ArrayTranslator([$key => $translatedMessage]);
$nextModifier = new TestingModifier();
$modifier = new TransModifier($translator, $nextModifier);
$value = $key;
$pipe = 'trans';
$result = $modifier->modify($value, $pipe);
self::assertSame($translatedMessage, $result);
}
#[Test]
public function itShouldReturnKeyWhenTranslationNotFound(): void
{
$key = 'nonexistent';
$translator = new ArrayTranslator(['message' => 'translated message']);
$nextModifier = new TestingModifier();
$modifier = new TransModifier($translator, $nextModifier);
$value = $key;
$pipe = 'trans';
$result = $modifier->modify($value, $pipe);
self::assertSame($key, $result);
}
}