Skip to content

Commit ec70cf7

Browse files
committed
Ensure formatters only format, not modify results
When I changed the library to not overwrite existing names [1], I wasn't happy with how `FirstResultStringFormatter` was changing the results, because the results should be completely ready by the time they arrive in the formatters. This commit changes that behaviour, ensuring the results are complete with all necessary information by the time they reach the formatters. Along with those changes, I refactored some stringifiers and simplified the `InterpolationRenderer`; we were not overwriting the "name" parameter anyway, as it was just an unnecessary overhead. [1] 8332d28
1 parent b5ad7aa commit ec70cf7

File tree

14 files changed

+284
-130
lines changed

14 files changed

+284
-130
lines changed

library/Message/Formatter/FirstResultStringFormatter.php

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,19 @@
1111

1212
use Respect\Validation\Message\Renderer;
1313
use Respect\Validation\Message\StringFormatter;
14-
use Respect\Validation\Name;
1514
use Respect\Validation\Result;
1615

1716
final readonly class FirstResultStringFormatter implements StringFormatter
1817
{
1918
/** @param array<string|int, mixed> $templates */
2019
public function format(Result $result, Renderer $renderer, array $templates): string
21-
{
22-
return $this->formatResult($result, $renderer, $templates, null);
23-
}
24-
25-
/** @param array<string|int, mixed> $templates */
26-
private function formatResult(Result $result, Renderer $renderer, array $templates, Name|null $parentName): string
2720
{
2821
if (!$result->hasCustomTemplate()) {
2922
foreach ($result->children as $child) {
30-
return $this->formatResult($child, $renderer, $templates, $result->name ?? $parentName);
23+
return $this->format($child, $renderer, $templates);
3124
}
3225
}
3326

34-
if ($parentName !== null) {
35-
$result = $result->withName($parentName);
36-
}
37-
3827
return $renderer->render($result, $templates);
3928
}
4029
}

library/Message/Formatter/NestedListStringFormatter.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use Respect\Validation\Message\Renderer;
1313
use Respect\Validation\Message\StringFormatter;
14+
use Respect\Validation\Name;
1415
use Respect\Validation\Result;
1516

1617
use function array_filter;
@@ -27,7 +28,7 @@
2728
/** @param array<string|int, mixed> $templates */
2829
public function format(Result $result, Renderer $renderer, array $templates): string
2930
{
30-
return $this->formatRecursively($result, $renderer, $templates, 0);
31+
return $this->formatRecursively($result, $renderer, $templates, 0, null);
3132
}
3233

3334
/** @param array<string|int, mixed> $templates */
@@ -36,23 +37,31 @@ private function formatRecursively(
3637
Renderer $renderer,
3738
array $templates,
3839
int $depth,
40+
Name|null $lastVisibleName,
3941
Result ...$siblings,
4042
): string {
4143
$formatted = '';
42-
$displayedName = null;
4344
if ($this->isVisible($result, ...$siblings)) {
4445
$indentation = str_repeat(' ', $depth * 2);
45-
$displayedName = $result->name;
46-
$formatted .= sprintf('%s- %s' . PHP_EOL, $indentation, $renderer->render($result, $templates));
46+
$formatted .= sprintf(
47+
'%s- %s' . PHP_EOL,
48+
$indentation,
49+
$renderer->render(
50+
$lastVisibleName === $result->name ? $result->withoutName() : $result,
51+
$templates,
52+
),
53+
);
54+
$lastVisibleName ??= $result->name;
4755
$depth++;
4856
}
4957

5058
foreach ($result->children as $child) {
5159
$formatted .= $this->formatRecursively(
52-
$displayedName === $child->name ? $child->withoutName() : $child,
60+
$child,
5361
$renderer,
5462
$templates,
5563
$depth,
64+
$lastVisibleName,
5665
...array_filter($result->children, static fn(Result $sibling) => $sibling !== $child),
5766
);
5867
$formatted .= PHP_EOL;

library/Message/InterpolationRenderer.php

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
namespace Respect\Validation\Message;
1111

1212
use Respect\Validation\Message\Formatter\TemplateResolver;
13-
use Respect\Validation\Name;
13+
use Respect\Validation\Message\Placeholder\Subject;
1414
use Respect\Validation\Result;
1515

1616
use function array_key_exists;
@@ -31,7 +31,7 @@ public function __construct(
3131
/** @param array<string|int, mixed> $templates */
3232
public function render(Result $result, array $templates): string
3333
{
34-
$parameters = ['path' => $result->path, 'input' => $result->input, 'subject' => $this->getName($result)];
34+
$parameters = ['path' => $result->path, 'input' => $result->input, 'subject' => Subject::fromResult($result)];
3535
$parameters += $result->parameters;
3636

3737
$givenTemplate = $this->templateResolver->getGivenTemplate($result, $templates);
@@ -65,25 +65,4 @@ private function processPlaceholder(array $parameters, array $matches): string
6565

6666
return $this->modifier->modify($parameters[$name], $pipe);
6767
}
68-
69-
private function getName(Result $result): mixed
70-
{
71-
if (array_key_exists('name', $result->parameters) && is_string($result->parameters['name'])) {
72-
return new Name($result->parameters['name']);
73-
}
74-
75-
if (array_key_exists('name', $result->parameters)) {
76-
return $result->parameters['name'];
77-
}
78-
79-
if ($result->name !== null) {
80-
return $result->name;
81-
}
82-
83-
if ($result->path?->value !== null) {
84-
return $result->path;
85-
}
86-
87-
return $result->input;
88-
}
8968
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
use Respect\Validation\Name;
13+
use Respect\Validation\Path;
14+
use Respect\Validation\Result;
15+
16+
final readonly class Subject
17+
{
18+
public function __construct(
19+
public mixed $input,
20+
public Path|null $path = null,
21+
public Name|null $name = null,
22+
public bool $hasPrecedentName = true,
23+
) {
24+
}
25+
26+
public static function fromResult(Result $result): self
27+
{
28+
return new self($result->input, $result->path, $result->name, $result->hasPrecedentName);
29+
}
30+
}

library/Message/Stringifier/NameStringifier.php

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,14 @@
1212
use Respect\Stringifier\Stringifier;
1313
use Respect\Validation\Name;
1414

15-
use function sprintf;
16-
1715
final readonly class NameStringifier implements Stringifier
1816
{
19-
public function __construct(
20-
private Stringifier $stringifier,
21-
) {
22-
}
23-
2417
public function stringify(mixed $raw, int $depth): string|null
2518
{
2619
if (!$raw instanceof Name) {
2720
return null;
2821
}
2922

30-
if ($raw->path === null) {
31-
return $raw->value;
32-
}
33-
34-
return sprintf(
35-
'%s (<- %s)',
36-
$this->stringifier->stringify($raw->path, $depth),
37-
$raw->value,
38-
);
23+
return $raw->value;
3924
}
4025
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 Respect\Stringifier\Stringifier;
13+
use Respect\Validation\Message\Placeholder\Subject;
14+
15+
use function sprintf;
16+
17+
final readonly class SubjectStringifier implements Stringifier
18+
{
19+
public function __construct(
20+
private Stringifier $stringifier,
21+
) {
22+
}
23+
24+
public function stringify(mixed $raw, int $depth): string|null
25+
{
26+
if (!$raw instanceof Subject) {
27+
return null;
28+
}
29+
30+
if ($raw->path === null && $raw->name === null) {
31+
return $this->stringifier->stringify($raw->input, $depth);
32+
}
33+
34+
if ($raw->name === null) {
35+
return $this->stringifier->stringify($raw->path, $depth);
36+
}
37+
38+
if ($raw->path === null || $raw->hasPrecedentName) {
39+
return $this->stringifier->stringify($raw->name, $depth);
40+
}
41+
42+
return sprintf(
43+
'%s (<- %s)',
44+
$this->stringifier->stringify($raw->path, $depth),
45+
$this->stringifier->stringify($raw->name, $depth),
46+
);
47+
}
48+
}

library/Message/ValidationStringifier.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Respect\Validation\Message\Stringifier\NameStringifier;
3636
use Respect\Validation\Message\Stringifier\PathStringifier;
3737
use Respect\Validation\Message\Stringifier\QuotedStringifier;
38+
use Respect\Validation\Message\Stringifier\SubjectStringifier;
3839

3940
final readonly class ValidationStringifier implements Stringifier
4041
{
@@ -95,7 +96,8 @@ private function createStringifier(Quoter $quoter): Stringifier
9596
$stringifier->prependStringifier(new PathStringifier($quoter));
9697
$stringifier->prependStringifier(new QuotedStringifier($quoter));
9798
$stringifier->prependStringifier(new ListedStringifier($stringifier));
98-
$stringifier->prependStringifier(new NameStringifier($stringifier));
99+
$stringifier->prependStringifier(new NameStringifier());
100+
$stringifier->prependStringifier(new SubjectStringifier($stringifier));
99101

100102
return $stringifier;
101103
}

library/Name.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,4 @@ public function __construct(
1616
public Path|null $path = null,
1717
) {
1818
}
19-
20-
public function withPath(Path $path): Name
21-
{
22-
return new self($this->value, $path);
23-
}
2419
}

library/Result.php

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public function __construct(
3030
public array $parameters = [],
3131
public string $template = Rule::TEMPLATE_STANDARD,
3232
public bool $hasInvertedMode = false,
33+
public bool $hasPrecedentName = true,
3334
public Name|null $name = null,
3435
public Result|null $adjacent = null,
3536
public Path|null $path = null,
@@ -124,6 +125,7 @@ public function withPath(Path $path): self
124125
return clone($this, [
125126
'path' => $path,
126127
'adjacent' => $this->adjacent?->withPath($path),
128+
'hasPrecedentName' => $this->name !== null,
127129
'children' => array_map(
128130
static fn(Result $child) => $child->withPath($path),
129131
$this->children,
@@ -149,43 +151,40 @@ public function withoutName(): self
149151

150152
public function withChildren(Result ...$children): self
151153
{
152-
if ($this->path === null) {
153-
return clone($this, ['children' => $children]);
154-
}
155-
156-
return clone($this, ['children' => array_map(fn(Result $child) => $child->withPath($this->path), $children)]);
154+
return clone($this, ['children' => $children]);
157155
}
158156

159157
public function withName(Name $name): self
160158
{
161-
if ($this->path !== null && $this->name?->path !== $this->path) {
162-
$name = $name->withPath($this->path);
159+
if ($this->name !== null) {
160+
return $this;
163161
}
164162

165163
return clone($this, [
166-
'name' => $this->name ?? $name,
164+
'name' => $name,
167165
'adjacent' => $this->adjacent?->withName($name),
168166
'children' => array_map(
169-
static fn(Result $child) => $child->path === null ? $child->withName($child->name ?? $name) : $child,
167+
static fn(Result $child) => $child->withName($name),
170168
$this->children,
171169
),
172170
]);
173171
}
174172

175173
public function withNameFrom(Rule $rule): self
176174
{
177-
if ($rule instanceof Nameable && $rule->getName() !== null) {
178-
return clone($this, [
179-
'name' => $this->name ?? $rule->getName(),
180-
'adjacent' => $this->adjacent?->withNameFrom($rule),
181-
'children' => array_map(
182-
static fn(Result $child) => $child->withNameFrom($rule),
183-
$this->children,
184-
),
185-
]);
175+
if (!$rule instanceof Nameable || $rule->getName() === null) {
176+
return $this;
186177
}
187178

188-
return $this;
179+
return clone($this, [
180+
'name' => $this->name ?? $rule->getName(),
181+
'hasPrecedentName' => true,
182+
'adjacent' => $this->adjacent?->withNameFrom($rule),
183+
'children' => array_map(
184+
static fn(Result $child) => $child->withNameFrom($rule),
185+
$this->children,
186+
),
187+
]);
189188
}
190189

191190
public function withInput(mixed $input): self

library/Rules/Key.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,6 @@ public function evaluate(mixed $input): Result
3838
return $keyExistsResult->withNameFrom($this->rule);
3939
}
4040

41-
return $this->rule->evaluate($input[$this->key])->withPath(new Path($this->key));
41+
return $this->rule->evaluate($input[$this->key])->withPath($keyExistsResult->path ?? new Path($this->key));
4242
}
4343
}

0 commit comments

Comments
 (0)