Skip to content

Commit 86bacf9

Browse files
committed
Implement a custom stringifier
The standards `CompositeStringifier` from "respect/stringifier" has lots of interesting stringifiers. However, this library is not 100% focused on engineers. Someone could type a string that matches a callable, and then you will overexpose the system. This commit makes sure that callables are not interpreted as callables.
1 parent a3c197f commit 86bacf9

File tree

8 files changed

+104
-14
lines changed

8 files changed

+104
-14
lines changed

library/Message/StandardRenderer.php

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

1212
use ReflectionClass;
1313
use Respect\Stringifier\Stringifier;
14-
use Respect\Stringifier\Stringifiers\CompositeStringifier;
1514
use Respect\Validation\Mode;
1615
use Respect\Validation\Result;
1716
use Respect\Validation\Rule;
@@ -27,11 +26,9 @@ final class StandardRenderer implements Renderer
2726
/** @var array<string, array<Template>> */
2827
private array $templates = [];
2928

30-
private readonly Stringifier $stringifier;
31-
32-
public function __construct(?Stringifier $stringifier = null)
33-
{
34-
$this->stringifier = $stringifier ?? CompositeStringifier::createDefault();
29+
public function __construct(
30+
private readonly Stringifier $stringifier = new StandardStringifier(),
31+
) {
3532
}
3633

3734
public function render(Result $result, Translator $translator, ?string $template = null): string
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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;
11+
12+
use DateTimeInterface;
13+
use Respect\Stringifier\Quoter;
14+
use Respect\Stringifier\Quoters\StandardQuoter;
15+
use Respect\Stringifier\Stringifier;
16+
use Respect\Stringifier\Stringifiers\ArrayObjectStringifier;
17+
use Respect\Stringifier\Stringifiers\ArrayStringifier;
18+
use Respect\Stringifier\Stringifiers\BoolStringifier;
19+
use Respect\Stringifier\Stringifiers\CompositeStringifier;
20+
use Respect\Stringifier\Stringifiers\DateTimeStringifier;
21+
use Respect\Stringifier\Stringifiers\DeclaredStringifier;
22+
use Respect\Stringifier\Stringifiers\EnumerationStringifier;
23+
use Respect\Stringifier\Stringifiers\InfiniteNumberStringifier;
24+
use Respect\Stringifier\Stringifiers\IteratorObjectStringifier;
25+
use Respect\Stringifier\Stringifiers\JsonEncodableStringifier;
26+
use Respect\Stringifier\Stringifiers\JsonSerializableObjectStringifier;
27+
use Respect\Stringifier\Stringifiers\NotANumberStringifier;
28+
use Respect\Stringifier\Stringifiers\NullStringifier;
29+
use Respect\Stringifier\Stringifiers\ObjectStringifier;
30+
use Respect\Stringifier\Stringifiers\ObjectWithDebugInfoStringifier;
31+
use Respect\Stringifier\Stringifiers\ResourceStringifier;
32+
use Respect\Stringifier\Stringifiers\StringableObjectStringifier;
33+
use Respect\Stringifier\Stringifiers\ThrowableObjectStringifier;
34+
35+
final class StandardStringifier implements Stringifier
36+
{
37+
private const MAXIMUM_DEPTH = 3;
38+
private const MAXIMUM_NUMBER_OF_ITEMS = 5;
39+
private const MAXIMUM_NUMBER_OF_PROPERTIES = self::MAXIMUM_NUMBER_OF_ITEMS;
40+
private const MAXIMUM_LENGTH = 120;
41+
42+
private readonly Stringifier $stringifier;
43+
44+
public function __construct(Quoter $quoter = new StandardQuoter(self::MAXIMUM_LENGTH))
45+
{
46+
$this->stringifier = $this->createStringifier($quoter);
47+
}
48+
49+
public function stringify(mixed $raw, int $depth): ?string
50+
{
51+
return $this->stringifier->stringify($raw, $depth);
52+
}
53+
54+
private function createStringifier(Quoter $quoter): Stringifier
55+
{
56+
$jsonEncodableStringifier = new JsonEncodableStringifier();
57+
58+
$stringifier = new CompositeStringifier(
59+
new InfiniteNumberStringifier($quoter),
60+
new NotANumberStringifier($quoter),
61+
new ResourceStringifier($quoter),
62+
new BoolStringifier($quoter),
63+
new NullStringifier($quoter),
64+
new DeclaredStringifier($quoter),
65+
$jsonEncodableStringifier,
66+
);
67+
$stringifier->prependStringifier(
68+
$arrayStringifier = new ArrayStringifier(
69+
$stringifier,
70+
$quoter,
71+
self::MAXIMUM_DEPTH,
72+
self::MAXIMUM_NUMBER_OF_ITEMS,
73+
)
74+
);
75+
$stringifier->prependStringifier(new ObjectStringifier(
76+
$stringifier,
77+
$quoter,
78+
self::MAXIMUM_DEPTH,
79+
self::MAXIMUM_NUMBER_OF_PROPERTIES
80+
)
81+
);
82+
$stringifier->prependStringifier(new EnumerationStringifier($quoter));
83+
$stringifier->prependStringifier(new ObjectWithDebugInfoStringifier($arrayStringifier, $quoter));
84+
$stringifier->prependStringifier(new ArrayObjectStringifier($arrayStringifier, $quoter));
85+
$stringifier->prependStringifier(new JsonSerializableObjectStringifier($jsonEncodableStringifier, $quoter));
86+
$stringifier->prependStringifier(new StringableObjectStringifier($jsonEncodableStringifier, $quoter));
87+
$stringifier->prependStringifier(new ThrowableObjectStringifier($jsonEncodableStringifier, $quoter));
88+
$stringifier->prependStringifier(new DateTimeStringifier($quoter, DateTimeInterface::ATOM));
89+
$stringifier->prependStringifier(new IteratorObjectStringifier($stringifier, $quoter));
90+
91+
return $stringifier;
92+
}
93+
}

tests/feature/Rules/CallTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
test('Scenario #3', expectMessage(
2121
fn() => v::call('stripslashes', v::alwaysValid())->assert([]),
22-
'`[]` must be a suitable argument for `stripslashes(string $string): string`',
22+
'`[]` must be a suitable argument for "stripslashes"',
2323
));
2424

2525
test('Scenario #4', expectFullMessage(
@@ -34,5 +34,5 @@
3434

3535
test('Scenario #6', expectFullMessage(
3636
fn() => v::call('array_shift', v::alwaysValid())->assert(INF),
37-
'- `INF` must be a suitable argument for `array_shift(array &$array): ?mixed`',
37+
'- `INF` must be a suitable argument for "array_shift"',
3838
));

tests/feature/Rules/CallableTypeTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
test('Scenario #2', expectMessage(
1616
fn() => v::not(v::callableType())->assert('trim'),
17-
'`trim(string $string, string $characters = " \\n\\r\\t\\u000b\\u0000"): string` must not be a callable',
17+
'"trim" must not be a callable',
1818
));
1919

2020
test('Scenario #3', expectFullMessage(
@@ -26,5 +26,5 @@
2626
fn() => v::not(v::callableType())->assert(function (): void {
2727
// Do nothing
2828
}),
29-
'- `function (): void` must not be a callable',
29+
'- `Closure {}` must not be a callable',
3030
));

tests/feature/Rules/CnpjTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
test('Scenario #3', expectFullMessage(
2323
fn() => v::cnpj()->assert('test'),
24-
'- `test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must be a valid CNPJ number',
24+
'- "test" must be a valid CNPJ number',
2525
));
2626

2727
test('Scenario #4', expectFullMessage(

tests/feature/Rules/ObjectTypeTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
test('Scenario #3', expectFullMessage(
2121
fn() => v::objectType()->assert('test'),
22-
'- `test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must be an object',
22+
'- "test" must be an object',
2323
));
2424

2525
test('Scenario #4', expectFullMessage(

tests/feature/Rules/ResourceTypeTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
test('Scenario #1', expectMessage(
1111
fn() => v::resourceType()->assert('test'),
12-
'`test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must be a resource',
12+
'"test" must be a resource',
1313
));
1414

1515
test('Scenario #2', expectMessage(

tests/feature/Rules/UniqueTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
test('Scenario #3', expectFullMessage(
2121
fn() => v::unique()->assert('test'),
22-
'- `test(?string $description = null, ?Closure $closure = null): Pest\\Support\\HigherOrderTapProxy|Pest\\PendingCalls\\Te ...` must not contain duplicates',
22+
'- "test" must not contain duplicates',
2323
));
2424

2525
test('Scenario #4', expectFullMessage(

0 commit comments

Comments
 (0)