Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
28 / 28
28.30% covered (danger)
28.30%
15 / 53
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
MessageFormatter
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
28 / 28
28.30% covered (danger)
28.30%
15 / 53
100.00% covered (success)
100.00%
6 / 6
152.05
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 format
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 template
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
17 / 17
17.39% covered (danger)
17.39%
8 / 46
100.00% covered (success)
100.00%
1 / 1
54.66
 render
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
4
 placeholders
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 stringify
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Sire;
6
7use Stringable;
8
9/** @api */
10final readonly class MessageFormatter
11{
12    /** @param array<string, string> $messages */
13    public function __construct(
14        private array $messages,
15    ) {}
16
17    /**
18     * Templates use `{label}`, `{field}`, `{value}`, and the failure or rule
19     * arguments: named ones by their name, positional ones as `{argN}`.
20     *
21     * @param array<array-key, mixed> $args
22     * @param array<string, string> $messages
23     */
24    public function format(
25        Failure $failure,
26        string $label,
27        string $field,
28        mixed $pristine,
29        ?string $defaultKey = null,
30        string $fallback = '{label} is invalid',
31        array $args = [],
32        array $messages = [],
33    ): string {
34        $template = $this->template($failure, $defaultKey, $fallback, $messages);
35        $args = $failure->args === [] ? $args : $failure->args;
36
37        return self::render($template, $label, $field, $pristine, $args);
38    }
39
40    /** @param array<string, string> $messages */
41    private function template(
42        Failure $failure,
43        ?string $defaultKey,
44        string $fallback,
45        array $messages,
46    ): string {
47        if ($failure->key !== '' && array_key_exists($failure->key, $messages)) {
48            return $messages[$failure->key];
49        }
50
51        if ($defaultKey !== null && array_key_exists($defaultKey, $messages)) {
52            return $messages[$defaultKey];
53        }
54
55        if ($failure->key !== '' && array_key_exists($failure->key, $this->messages)) {
56            return $this->messages[$failure->key];
57        }
58
59        if ($defaultKey !== null && array_key_exists($defaultKey, $this->messages)) {
60            return $this->messages[$defaultKey];
61        }
62
63        return $failure->fallback ?? $fallback;
64    }
65
66    /** @param array<array-key, mixed> $args */
67    private static function render(
68        string $template,
69        string $label,
70        string $field,
71        mixed $pristine,
72        array $args,
73    ): string {
74        $values = self::placeholders(
75            $label,
76            $field,
77            self::stringify($pristine),
78            array_map(self::stringify(...), $args),
79        );
80        $rendered = preg_replace_callback(
81            '/{{|}}|{([^{}]+)}/',
82            static function (array $matches) use ($values): string {
83                $token = $matches[0];
84
85                return match ($token) {
86                    '{{' => '{',
87                    '}}' => '}',
88                    default => $values[$matches[1] ?? ''] ?? $token,
89                };
90            },
91            $template,
92        );
93
94        return $rendered ?? $template;
95    }
96
97    /**
98     * Label, field, and value win over arguments of the same name.
99     *
100     * @param array<array-key, string> $args
101     * @return array<string, string>
102     */
103    private static function placeholders(
104        string $label,
105        string $field,
106        string $value,
107        array $args,
108    ): array {
109        return [
110            ...Arguments::keyed($args),
111            'label' => $label,
112            'field' => $field,
113            'value' => $value,
114        ];
115    }
116
117    private static function stringify(mixed $value): string
118    {
119        return $value instanceof Stringable ? (string) $value : print_r($value, true);
120    }
121}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

MessageFormatter->__construct
14        private array $messages,
15    ) {}
MessageFormatter->format
25        Failure $failure,
26        string $label,
27        string $field,
28        mixed $pristine,
29        ?string $defaultKey = null,
30        string $fallback = '{label} is invalid',
31        array $args = [],
32        array $messages = [],
33    ): string {
34        $template = $this->template($failure, $defaultKey, $fallback, $messages);
35        $args = $failure->args === [] ? $args : $failure->args;
35        $args = $failure->args === [] ? $args : $failure->args;
35        $args = $failure->args === [] ? $args : $failure->args;
35        $args = $failure->args === [] ? $args : $failure->args;
36
37        return self::render($template, $label, $field, $pristine, $args);
38    }
MessageFormatter->placeholders
104        string $label,
105        string $field,
106        string $value,
107        array $args,
108    ): array {
109        return [
110            ...Arguments::keyed($args),
111            'label' => $label,
112            'field' => $field,
113            'value' => $value,
114        ];
115    }
MessageFormatter->render
68        string $template,
69        string $label,
70        string $field,
71        mixed $pristine,
72        array $args,
73    ): string {
74        $values = self::placeholders(
75            $label,
76            $field,
77            self::stringify($pristine),
78            array_map(self::stringify(...), $args),
79        );
80        $rendered = preg_replace_callback(
81            '/{{|}}|{([^{}]+)}/',
82            static function (array $matches) use ($values): string {
83                $token = $matches[0];
84
85                return match ($token) {
86                    '{{' => '{',
87                    '}}' => '}',
88                    default => $values[$matches[1] ?? ''] ?? $token,
89                };
90            },
91            $template,
92        );
93
94        return $rendered ?? $template;
95    }
MessageFormatter->stringify
117    private static function stringify(mixed $value): string
118    {
119        return $value instanceof Stringable ? (string) $value : print_r($value, true);
119        return $value instanceof Stringable ? (string) $value : print_r($value, true);
119        return $value instanceof Stringable ? (string) $value : print_r($value, true);
119        return $value instanceof Stringable ? (string) $value : print_r($value, true);
120    }
MessageFormatter->template
42        Failure $failure,
43        ?string $defaultKey,
44        string $fallback,
45        array $messages,
46    ): string {
47        if ($failure->key !== '' && array_key_exists($failure->key, $messages)) {
47        if ($failure->key !== '' && array_key_exists($failure->key, $messages)) {
47        if ($failure->key !== '' && array_key_exists($failure->key, $messages)) {
48            return $messages[$failure->key];
51        if ($defaultKey !== null && array_key_exists($defaultKey, $messages)) {
51        if ($defaultKey !== null && array_key_exists($defaultKey, $messages)) {
51        if ($defaultKey !== null && array_key_exists($defaultKey, $messages)) {
52            return $messages[$defaultKey];
55        if ($failure->key !== '' && array_key_exists($failure->key, $this->messages)) {
55        if ($failure->key !== '' && array_key_exists($failure->key, $this->messages)) {
55        if ($failure->key !== '' && array_key_exists($failure->key, $this->messages)) {
56            return $this->messages[$failure->key];
59        if ($defaultKey !== null && array_key_exists($defaultKey, $this->messages)) {
59        if ($defaultKey !== null && array_key_exists($defaultKey, $this->messages)) {
59        if ($defaultKey !== null && array_key_exists($defaultKey, $this->messages)) {
60            return $this->messages[$defaultKey];
63        return $failure->fallback ?? $fallback;
64    }
{closure:/workspace/celema/sire/src/MessageFormatter.php:82-90}
82            static function (array $matches) use ($values): string {
83                $token = $matches[0];
84
85                return match ($token) {
86                    '{{' => '{',
87                    '}}' => '}',
88                    default => $values[$matches[1] ?? ''] ?? $token,
88                    default => $values[$matches[1] ?? ''] ?? $token,
89                };
90            },