Passed
Push — master ( 3a825d...8b42e8 )
by Sergei
04:02 queued 01:11
created

Error::headerTag()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Field\Part;
6
7
use InvalidArgumentException;
8
use Yiisoft\Form\Field\Base\InputData\InputDataInterface;
9
use Yiisoft\Form\Field\Base\InputData\InputDataTrait;
10
use Yiisoft\Form\ThemeContainer;
11
use Yiisoft\Html\Html;
12
use Yiisoft\Html\Tag\CustomTag;
13
use Yiisoft\Widget\Widget;
14
15
/**
16
 * Represent a field validation error (if there are several errors, the first one is used). If field is no validation
17
 * error, field part will be hidden.
18
 *
19
 * @psalm-type MessageCallback = callable(string, InputDataInterface): string
20
 */
21
final class Error extends Widget
22
{
23
    use InputDataTrait;
24
25
    private bool $onlyFirst = false;
26
27
    /**
28
     * @psalm-var non-empty-string
29
     */
30
    private string $tag = 'div';
31
    private array $attributes = [];
32
33
    private bool $encode = true;
34
35
    private string $separator = "\n<br>\n";
36
37
    private ?string $header = null;
38
39
    /**
40
     * @var non-empty-string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string|null at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string|null.
Loading history...
41
     */
42
    private ?string $headerTag = 'div';
43
    private array $headerAttributes = [];
44
    private bool $headerEncode = true;
45
46
    /**
47
     * @psalm-var non-empty-string
48
     */
49
    private ?string $errorTag = null;
50
    private array $errorAttributes = [];
51
52
    /**
53
     * @psalm-var array<array-key,string>|null
54
     */
55
    private ?array $messages = null;
56
57
    /**
58
     * @var callable|null
59
     * @psalm-var MessageCallback|null
60
     */
61
    private $messageCallback = null;
62
63 1
    public function onlyFirst(bool $value = true): self
64
    {
65 1
        $new = clone $this;
66 1
        $new->onlyFirst = $value;
67 1
        return $new;
68
    }
69
70
    /**
71
     * Set the container tag name for the error.
72
     *
73
     * @param string $tag Container tag name.
74
     */
75 4
    public function tag(string $tag): self
76
    {
77 4
        if ($tag === '') {
78 1
            throw new InvalidArgumentException('Tag name cannot be empty.');
79
        }
80
81 3
        $new = clone $this;
82 3
        $new->tag = $tag;
83 3
        return $new;
84
    }
85
86 4
    public function attributes(array $attributes): self
87
    {
88 4
        $new = clone $this;
89 4
        $new->attributes = $attributes;
90 4
        return $new;
91
    }
92
93 5
    public function addAttributes(array $attributes): self
94
    {
95 5
        $new = clone $this;
96 5
        $new->attributes = array_merge($this->attributes, $attributes);
97 5
        return $new;
98
    }
99
100
    /**
101
     * Set tag ID.
102
     *
103
     * @param string|null $id Tag ID.
104
     */
105 2
    public function id(?string $id): self
106
    {
107 2
        $new = clone $this;
108 2
        $new->attributes['id'] = $id;
109 2
        return $new;
110
    }
111
112
    /**
113
     * Add one or more CSS classes to the tag.
114
     *
115
     * @param string|null ...$class One or many CSS classes.
116
     */
117 10
    public function addClass(?string ...$class): self
118
    {
119 10
        $new = clone $this;
120 10
        Html::addCssClass($new->attributes, $class);
121 10
        return $new;
122
    }
123
124
    /**
125
     * Replace tag CSS classes with a new set of classes.
126
     *
127
     * @param string|null ...$class One or many CSS classes.
128
     */
129 9
    public function class(?string ...$class): self
130
    {
131 9
        $new = clone $this;
132 9
        $new->attributes['class'] = array_filter($class, static fn ($c) => $c !== null);
133 9
        return $new;
134
    }
135
136
    /**
137
     * Whether error messages should be HTML-encoded.
138
     */
139 1
    public function encode(bool $value): self
140
    {
141 1
        $new = clone $this;
142 1
        $new->encode = $value;
143 1
        return $new;
144
    }
145
146 2
    public function separator(string $separator): self
147
    {
148 2
        $new = clone $this;
149 2
        $new->separator = $separator;
150 2
        return $new;
151
    }
152
153 7
    public function header(?string $header): self
154
    {
155 7
        $new = clone $this;
156 7
        $new->header = $header;
157 7
        return $new;
158
    }
159
160
    /**
161
     * Set the header tag name.
162
     *
163
     * @param string|null $tag Header tag name.
164
     */
165 3
    public function headerTag(?string $tag): self
166
    {
167 3
        if ($tag === '') {
168 1
            throw new InvalidArgumentException('Tag name cannot be empty.');
169
        }
170
171 2
        $new = clone $this;
172 2
        $new->headerTag = $tag;
173 2
        return $new;
174
    }
175
176 2
    public function headerAttributes(array $attributes): self
177
    {
178 2
        $new = clone $this;
179 2
        $new->headerAttributes = $attributes;
180 2
        return $new;
181
    }
182
183 1
    public function addHeaderAttributes(array $attributes): self
184
    {
185 1
        $new = clone $this;
186 1
        $new->headerAttributes = array_merge($this->headerAttributes, $attributes);
187 1
        return $new;
188
    }
189
190
    /**
191
     * Whether header content should be HTML-encoded.
192
     */
193 1
    public function headerEncode(bool $encode): self
194
    {
195 1
        $new = clone $this;
196 1
        $new->headerEncode = $encode;
197 1
        return $new;
198
    }
199
200
    /**
201
     * Set an error item tag name.
202
     *
203
     * @param string $tag Error item tag name.
204
     */
205 4
    public function errorTag(string $tag): self
206
    {
207 4
        if ($tag === '') {
208 1
            throw new InvalidArgumentException('Tag name cannot be empty.');
209
        }
210
211 3
        $new = clone $this;
212 3
        $new->errorTag = $tag;
213 3
        return $new;
214
    }
215
216 2
    public function errorAttributes(array $attributes): self
217
    {
218 2
        $new = clone $this;
219 2
        $new->errorAttributes = $attributes;
220 2
        return $new;
221
    }
222
223 1
    public function addErrorAttributes(array $attributes): self
224
    {
225 1
        $new = clone $this;
226 1
        $new->errorAttributes = array_merge($this->errorAttributes, $attributes);
227 1
        return $new;
228
    }
229
230
    /**
231
     * Error messages to display.
232
     */
233 29
    public function message(?string $message, string ...$messages): self
234
    {
235 29
        $new = clone $this;
236 29
        $new->messages = $message === null ? null : [$message, ...$messages];
237 29
        return $new;
238
    }
239
240
    /**
241
     * Callback that will be called to obtain an error message.
242
     *
243
     * @psalm-param MessageCallback|null $callback
244
     */
245 3
    public function messageCallback(?callable $callback): self
246
    {
247 3
        $new = clone $this;
248 3
        $new->messageCallback = $callback;
249 3
        return $new;
250
    }
251
252
    /**
253
     * Generates a tag that contains the first validation error of the specified form attribute.
254
     *
255
     * @return string The generated error tag.
256
     */
257 474
    public function render(): string
258
    {
259 474
        $messages = $this->messages ?? $this->getInputData()->getValidationErrors();
260 474
        if (empty($messages)) {
261 411
            return '';
262
        }
263
264 63
        if ($this->onlyFirst) {
265 1
            $messages = [reset($messages)];
266
        }
267
268 63
        $messageCallback = $this->messageCallback;
269 63
        if ($messageCallback !== null) {
270 3
            $messages = array_map(
271 3
                fn(string $message) => $messageCallback($message, $this->getInputData()),
272 3
                $messages,
273 3
            );
274
        }
275
276 63
        $content = [];
277
278 63
        if ($this->header !== null) {
279 7
            $content[] = $this->headerTag === null
280 1
                ? ($this->headerEncode ? Html::encode($this->header) : $this->header)
281 6
                : CustomTag::name($this->headerTag)
282 6
                    ->attributes($this->headerAttributes)
283 6
                    ->content($this->header)
284 6
                    ->encode($this->headerEncode)
285 6
                    ->render();
286 7
            $content[] = "\n";
287
        }
288
289 63
        $isFirst = true;
290 63
        foreach ($messages as $message) {
291 63
            if ($isFirst) {
292 63
                $isFirst = false;
293
            } else {
294 12
                $content[] = $this->separator;
295
            }
296 63
            $content[] = $this->errorTag === null
297 60
                ? ($this->encode ? Html::encode($message) : $message)
298 3
                : CustomTag::name($this->errorTag)
299 3
                    ->attributes($this->errorAttributes)
300 3
                    ->content($message)
301 3
                    ->encode($this->encode)
302 3
                    ->render();
303
        }
304
305 63
        return CustomTag::name($this->tag)
306 63
            ->addAttributes($this->attributes)
307 63
            ->content(...(count($messages) === 1 ? $content : ["\n", ...$content, "\n"]))
308 63
            ->encode(false)
309 63
            ->render();
310
    }
311
312 477
    protected static function getThemeConfig(?string $theme): array
313
    {
314 477
        return ThemeContainer::getTheme($theme)?->getErrorConfig() ?? [];
315
    }
316
}
317