Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
52 / 52
93.33% covered (success)
93.33%
42 / 45
25.68% covered (danger)
25.68%
19 / 74
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
MessageFormatter
100.00% covered (success)
100.00%
52 / 52
93.33% covered (success)
93.33%
42 / 45
25.68% covered (danger)
25.68%
19 / 74
100.00% covered (success)
100.00%
8 / 8
260.49
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%
12 / 12
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 renderNamed
100.00% covered (success)
100.00%
13 / 13
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%
8 / 8
100.00% covered (success)
100.00%
4 / 4
66.67% covered (warning)
66.67%
2 / 3
100.00% covered (success)
100.00%
1 / 1
2.15
 stringify
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
 usesNamedTemplate
100.00% covered (success)
100.00%
5 / 5
78.57% covered (warning)
78.57%
11 / 14
11.11% covered (danger)
11.11%
2 / 18
100.00% covered (success)
100.00%
1 / 1
9.32
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Sire;
6
7/** @api */
8final readonly class MessageFormatter
9{
10    /** @param array<string, string> $messages */
11    public function __construct(
12        private array $messages,
13    ) {}
14
15    /**
16     * Message templates receive label, field, pristine value, then failure or rule arguments.
17     *
18     * @param list<mixed> $args
19     * @param array<string, string> $messages
20     */
21    public function format(
22        Failure $failure,
23        string $label,
24        string $field,
25        mixed $pristine,
26        ?string $defaultKey = null,
27        string $fallback = '{label} is invalid',
28        array $args = [],
29        array $messages = [],
30    ): string {
31        $template = $this->template($failure, $defaultKey, $fallback, $messages);
32        $args = $failure->args === [] ? $args : $failure->args;
33
34        return self::render($template, $label, $field, $pristine, $args);
35    }
36
37    /** @param array<string, string> $messages */
38    private function template(
39        Failure $failure,
40        ?string $defaultKey,
41        string $fallback,
42        array $messages,
43    ): string {
44        if ($failure->key !== '' && array_key_exists($failure->key, $messages)) {
45            return $messages[$failure->key];
46        }
47
48        if ($defaultKey !== null && array_key_exists($defaultKey, $messages)) {
49            return $messages[$defaultKey];
50        }
51
52        if ($failure->key !== '' && array_key_exists($failure->key, $this->messages)) {
53            return $this->messages[$failure->key];
54        }
55
56        if ($defaultKey !== null && array_key_exists($defaultKey, $this->messages)) {
57            return $this->messages[$defaultKey];
58        }
59
60        return $failure->fallback ?? $fallback;
61    }
62
63    /** @param list<mixed> $args */
64    private static function render(
65        string $template,
66        string $label,
67        string $field,
68        mixed $pristine,
69        array $args,
70    ): string {
71        if (self::usesNamedTemplate($template)) {
72            return self::renderNamed(
73                $template,
74                self::placeholders($label, $field, $pristine, $args),
75            );
76        }
77
78        return sprintf(
79            $template,
80            $label,
81            $field,
82            self::stringify($pristine),
83            ...$args,
84        );
85    }
86
87    /** @param array<string, string> $values */
88    private static function renderNamed(string $template, array $values): string
89    {
90        $rendered = preg_replace_callback(
91            '/{{|}}|{([^{}]+)}/',
92            static function (array $matches) use ($values): string {
93                $token = $matches[0];
94
95                return match ($token) {
96                    '{{' => '{',
97                    '}}' => '}',
98                    default => $values[$matches[1] ?? ''] ?? $token,
99                };
100            },
101            $template,
102        );
103
104        return $rendered ?? $template;
105    }
106
107    /**
108     * @param list<mixed> $args
109     * @return array<string, string>
110     */
111    private static function placeholders(
112        string $label,
113        string $field,
114        mixed $pristine,
115        array $args,
116    ): array {
117        $values = [
118            'label' => $label,
119            'field' => $field,
120            'value' => self::stringify($pristine),
121        ];
122
123        foreach ($args as $index => $arg) {
124            $values['arg' . ($index + 1)] = self::stringify($arg);
125        }
126
127        return $values;
128    }
129
130    private static function stringify(mixed $value): string
131    {
132        return print_r($value, true);
133    }
134
135    private static function usesNamedTemplate(string $template): bool
136    {
137        return (
138            str_contains($template, '{{')
139            || str_contains($template, '}}')
140            || preg_match('/{(?:label|field|value|arg[1-9][0-9]*)}/', $template) === 1
141        );
142    }
143}

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
12        private array $messages,
13    ) {}
MessageFormatter->format
22        Failure $failure,
23        string $label,
24        string $field,
25        mixed $pristine,
26        ?string $defaultKey = null,
27        string $fallback = '{label} is invalid',
28        array $args = [],
29        array $messages = [],
30    ): string {
31        $template = $this->template($failure, $defaultKey, $fallback, $messages);
32        $args = $failure->args === [] ? $args : $failure->args;
32        $args = $failure->args === [] ? $args : $failure->args;
32        $args = $failure->args === [] ? $args : $failure->args;
32        $args = $failure->args === [] ? $args : $failure->args;
33
34        return self::render($template, $label, $field, $pristine, $args);
35    }
MessageFormatter->placeholders
112        string $label,
113        string $field,
114        mixed $pristine,
115        array $args,
116    ): array {
117        $values = [
118            'label' => $label,
119            'field' => $field,
120            'value' => self::stringify($pristine),
121        ];
122
123        foreach ($args as $index => $arg) {
123        foreach ($args as $index => $arg) {
123        foreach ($args as $index => $arg) {
123        foreach ($args as $index => $arg) {
124            $values['arg' . ($index + 1)] = self::stringify($arg);
125        }
126
127        return $values;
128    }
MessageFormatter->render
65        string $template,
66        string $label,
67        string $field,
68        mixed $pristine,
69        array $args,
70    ): string {
71        if (self::usesNamedTemplate($template)) {
72            return self::renderNamed(
73                $template,
74                self::placeholders($label, $field, $pristine, $args),
78        return sprintf(
79            $template,
80            $label,
81            $field,
82            self::stringify($pristine),
83            ...$args,
84        );
85    }
MessageFormatter->renderNamed
88    private static function renderNamed(string $template, array $values): string
89    {
90        $rendered = preg_replace_callback(
91            '/{{|}}|{([^{}]+)}/',
92            static function (array $matches) use ($values): string {
93                $token = $matches[0];
94
95                return match ($token) {
96                    '{{' => '{',
97                    '}}' => '}',
98                    default => $values[$matches[1] ?? ''] ?? $token,
99                };
100            },
101            $template,
102        );
103
104        return $rendered ?? $template;
105    }
MessageFormatter->stringify
130    private static function stringify(mixed $value): string
131    {
132        return print_r($value, true);
133    }
MessageFormatter->template
39        Failure $failure,
40        ?string $defaultKey,
41        string $fallback,
42        array $messages,
43    ): string {
44        if ($failure->key !== '' && array_key_exists($failure->key, $messages)) {
44        if ($failure->key !== '' && array_key_exists($failure->key, $messages)) {
44        if ($failure->key !== '' && array_key_exists($failure->key, $messages)) {
45            return $messages[$failure->key];
48        if ($defaultKey !== null && array_key_exists($defaultKey, $messages)) {
48        if ($defaultKey !== null && array_key_exists($defaultKey, $messages)) {
48        if ($defaultKey !== null && array_key_exists($defaultKey, $messages)) {
49            return $messages[$defaultKey];
52        if ($failure->key !== '' && array_key_exists($failure->key, $this->messages)) {
52        if ($failure->key !== '' && array_key_exists($failure->key, $this->messages)) {
52        if ($failure->key !== '' && array_key_exists($failure->key, $this->messages)) {
53            return $this->messages[$failure->key];
56        if ($defaultKey !== null && array_key_exists($defaultKey, $this->messages)) {
56        if ($defaultKey !== null && array_key_exists($defaultKey, $this->messages)) {
56        if ($defaultKey !== null && array_key_exists($defaultKey, $this->messages)) {
57            return $this->messages[$defaultKey];
60        return $failure->fallback ?? $fallback;
61    }
MessageFormatter->usesNamedTemplate
135    private static function usesNamedTemplate(string $template): bool
136    {
137        return (
138            str_contains($template, '{{')
138            str_contains($template, '{{')
138            str_contains($template, '{{')
138            str_contains($template, '{{')
139            || str_contains($template, '}}')
139            || str_contains($template, '}}')
139            || str_contains($template, '}}')
139            || str_contains($template, '}}')
139            || str_contains($template, '}}')
140            || preg_match('/{(?:label|field|value|arg[1-9][0-9]*)}/', $template) === 1
140            || preg_match('/{(?:label|field|value|arg[1-9][0-9]*)}/', $template) === 1
140            || preg_match('/{(?:label|field|value|arg[1-9][0-9]*)}/', $template) === 1
140            || preg_match('/{(?:label|field|value|arg[1-9][0-9]*)}/', $template) === 1
140            || preg_match('/{(?:label|field|value|arg[1-9][0-9]*)}/', $template) === 1
141        );
142    }
{closure:/workspace/celemas/sire/src/MessageFormatter.php:92-100}
92            static function (array $matches) use ($values): string {
93                $token = $matches[0];
94
95                return match ($token) {
96                    '{{' => '{',
97                    '}}' => '}',
98                    default => $values[$matches[1] ?? ''] ?? $token,
98                    default => $values[$matches[1] ?? ''] ?? $token,
99                };
100            },