PartsField::inputContainerClass()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Field\Base;
6
7
use InvalidArgumentException;
8
use Stringable;
9
use Yiisoft\Form\Field\Part\Error;
10
use Yiisoft\Form\Field\Part\Hint;
11
use Yiisoft\Form\Field\Part\Label;
12
use Yiisoft\Html\Html;
13
14
use function in_array;
15
16
abstract class PartsField extends BaseField
17
{
18
    private const BUILTIN_TOKENS = [
19
        '{input}',
20
        '{label}',
21
        '{hint}',
22
        '{error}',
23
    ];
24
25
    /**
26
     * @var string[]|Stringable[]
27
     * @psalm-var array<non-empty-string,string|Stringable>
28
     */
29
    private array $extraTokens = [];
30
31
    protected string $templateBegin = "{label}\n{input}";
32
    protected string $templateEnd = "{input}\n{hint}\n{error}";
33
    protected string $template = "{label}\n{input}\n{hint}\n{error}";
34
    protected ?string $label = null;
35
    protected ?bool $hideLabel = null;
36
37
    /**
38
     * @psalm-var non-empty-string|null
39
     */
40
    protected ?string $inputContainerTag = null;
41
    protected array $inputContainerAttributes = [];
42
43
    private bool $replaceLabelAttributes = false;
44
    private bool $replaceLabelClass = false;
45
    private array $labelAttributes = [];
46
    private array $labelConfig = [];
47
48
    private bool $replaceHintAttributes = false;
49
    private bool $replaceHintClass = false;
50
    private array $hintAttributes = [];
51
    private array $hintConfig = [];
52
53
    private bool $replaceErrorAttributes = false;
54
    private bool $replaceErrorClass = false;
55
    private array $errorAttributes = [];
56
    private array $errorConfig = [];
57
58 5
    final public function tokens(array $tokens): static
59
    {
60 5
        $new = clone $this;
61
62 5
        foreach ($tokens as $token => $value) {
63 4
            if (!is_string($token)) {
64 1
                throw new InvalidArgumentException(
65 1
                    sprintf(
66 1
                        'Token should be string. %s given.',
67 1
                        $token,
68 1
                    )
69 1
                );
70
            }
71
72 3
            if (!is_string($value) && !$value instanceof Stringable) {
73 1
                throw new InvalidArgumentException(
74 1
                    sprintf(
75 1
                        'Token value should be string or Stringable. %s given.',
76 1
                        get_debug_type($value),
77 1
                    )
78 1
                );
79
            }
80
81 2
            $this->validateToken($token);
82
83 2
            $new->extraTokens[$token] = $value;
84
        }
85
86 3
        return $new;
87
    }
88
89 5
    final public function token(string $token, string|Stringable $value): static
90
    {
91 5
        $this->validateToken($token);
92
93 3
        $new = clone $this;
94 3
        $new->extraTokens[$token] = $value;
95 3
        return $new;
96
    }
97
98
    /**
99
     * Set layout template for render a field.
100
     */
101 11
    final public function template(string $template): static
102
    {
103 11
        $new = clone $this;
104 11
        $new->template = $template;
105 11
        return $new;
106
    }
107
108 4
    final public function templateBegin(string $template): static
109
    {
110 4
        $new = clone $this;
111 4
        $new->templateBegin = $template;
112 4
        return $new;
113
    }
114
115 6
    final public function templateEnd(string $template): static
116
    {
117 6
        $new = clone $this;
118 6
        $new->templateEnd = $template;
119 6
        return $new;
120
    }
121
122 163
    final public function hideLabel(?bool $hide = true): static
123
    {
124 163
        $new = clone $this;
125 163
        $new->hideLabel = $hide;
126 163
        return $new;
127
    }
128
129 22
    final public function inputContainerTag(?string $tag): static
130
    {
131 22
        if ($tag === '') {
132 1
            throw new InvalidArgumentException('Tag name cannot be empty.');
133
        }
134
135 21
        $new = clone $this;
136 21
        $new->inputContainerTag = $tag;
137 21
        return $new;
138
    }
139
140 3
    final public function inputContainerAttributes(array $attributes): static
141
    {
142 3
        $new = clone $this;
143 3
        $new->inputContainerAttributes = $attributes;
144 3
        return $new;
145
    }
146
147 2
    final public function addInputContainerAttributes(array $attributes): static
148
    {
149 2
        $new = clone $this;
150 2
        $new->inputContainerAttributes = array_merge($new->inputContainerAttributes, $attributes);
151 2
        return $new;
152
    }
153
154
    /**
155
     * Replace input container tag CSS classes with a new set of classes.
156
     *
157
     * @param string|null ...$class One or many CSS classes.
158
     */
159 10
    final public function inputContainerClass(?string ...$class): static
160
    {
161 10
        $new = clone $this;
162 10
        $new->inputContainerAttributes['class'] = array_filter($class, static fn ($c) => $c !== null);
163 10
        return $new;
164
    }
165
166
    /**
167
     * Add one or more CSS classes to the input container tag.
168
     *
169
     * @param string|null ...$class One or many CSS classes.
170
     */
171 9
    final public function addInputContainerClass(?string ...$class): static
172
    {
173 9
        $new = clone $this;
174 9
        Html::addCssClass($new->inputContainerAttributes, $class);
175 9
        return $new;
176
    }
177
178 6
    final public function labelConfig(array $config): static
179
    {
180 6
        $new = clone $this;
181 6
        $new->labelConfig = $config;
182 6
        return $new;
183
    }
184
185 3
    final public function labelAttributes(array $attributes): static
186
    {
187 3
        $new = clone $this;
188 3
        $new->labelAttributes = $attributes;
189 3
        $new->replaceLabelAttributes = true;
190 3
        return $new;
191
    }
192
193 2
    final public function addLabelAttributes(array $attributes): static
194
    {
195 2
        $new = clone $this;
196 2
        $new->labelAttributes = array_merge($new->labelAttributes, $attributes);
197 2
        return $new;
198
    }
199
200
    /**
201
     * Set label tag ID.
202
     *
203
     * @param string|null $id Label tag ID.
204
     */
205 2
    final public function labelId(?string $id): static
206
    {
207 2
        $new = clone $this;
208 2
        $new->labelAttributes['id'] = $id;
209 2
        return $new;
210
    }
211
212
    /**
213
     * Replace label tag CSS classes with a new set of classes.
214
     *
215
     * @param string|null ...$class One or many CSS classes.
216
     */
217 3
    final public function labelClass(?string ...$class): static
218
    {
219 3
        $new = clone $this;
220 3
        $new->labelAttributes['class'] = $class;
221 3
        $new->replaceLabelClass = true;
222 3
        return $new;
223
    }
224
225
    /**
226
     * Add one or more CSS classes to the label tag.
227
     *
228
     * @param string|null ...$class One or many CSS classes.
229
     */
230 3
    final public function addLabelClass(?string ...$class): static
231
    {
232 3
        $new = clone $this;
233 3
        Html::addCssClass($new->labelAttributes, $class);
234 3
        return $new;
235
    }
236
237 21
    final public function label(?string $content): static
238
    {
239 21
        $new = clone $this;
240 21
        $new->label = $content;
241 21
        return $new;
242
    }
243
244 6
    final public function hintConfig(array $config): static
245
    {
246 6
        $new = clone $this;
247 6
        $new->hintConfig = $config;
248 6
        return $new;
249
    }
250
251 3
    final public function hintAttributes(array $attributes): static
252
    {
253 3
        $new = clone $this;
254 3
        $new->hintAttributes = $attributes;
255 3
        $new->replaceHintAttributes = true;
256 3
        return $new;
257
    }
258
259 2
    final public function addHintAttributes(array $attributes): static
260
    {
261 2
        $new = clone $this;
262 2
        $new->hintAttributes = array_merge($new->hintAttributes, $attributes);
263 2
        return $new;
264
    }
265
266
    /**
267
     * Set hint tag ID.
268
     *
269
     * @param string|null $id Hint tag ID.
270
     */
271 2
    final public function hintId(?string $id): static
272
    {
273 2
        $new = clone $this;
274 2
        $new->hintAttributes['id'] = $id;
275 2
        return $new;
276
    }
277
278
    /**
279
     * Replace hint tag CSS classes with a new set of classes.
280
     *
281
     * @param string|null ...$class One or many CSS classes.
282
     */
283 3
    final public function hintClass(?string ...$class): static
284
    {
285 3
        $new = clone $this;
286 3
        $new->hintAttributes['class'] = $class;
287 3
        $new->replaceHintClass = true;
288 3
        return $new;
289
    }
290
291
    /**
292
     * Add one or more CSS classes to the hint tag.
293
     *
294
     * @param string|null ...$class One or many CSS classes.
295
     */
296 2
    final public function addHintClass(?string ...$class): static
297
    {
298 2
        $new = clone $this;
299 2
        Html::addCssClass($new->hintAttributes, $class);
300 2
        return $new;
301
    }
302
303 12
    final public function hint(?string $content): static
304
    {
305 12
        $new = clone $this;
306 12
        $new->hintConfig['content()'] = [$content];
307 12
        return $new;
308
    }
309
310 6
    final public function errorConfig(array $config): static
311
    {
312 6
        $new = clone $this;
313 6
        $new->errorConfig = $config;
314 6
        return $new;
315
    }
316
317 3
    final public function errorAttributes(array $attributes): static
318
    {
319 3
        $new = clone $this;
320 3
        $new->errorAttributes = $attributes;
321 3
        $new->replaceErrorAttributes = true;
322 3
        return $new;
323
    }
324
325 2
    final public function addErrorAttributes(array $attributes): static
326
    {
327 2
        $new = clone $this;
328 2
        $new->errorAttributes = array_merge($new->errorAttributes, $attributes);
329 2
        return $new;
330
    }
331
332
    /**
333
     * Set error tag ID.
334
     *
335
     * @param string|null $id Error tag ID.
336
     */
337 2
    final public function errorId(?string $id): static
338
    {
339 2
        $new = clone $this;
340 2
        $new->errorAttributes['id'] = $id;
341 2
        return $new;
342
    }
343
344
    /**
345
     * Replace error tag CSS classes with a new set of classes.
346
     *
347
     * @param string|null ...$class One or many CSS classes.
348
     */
349 3
    final public function errorClass(?string ...$class): static
350
    {
351 3
        $new = clone $this;
352 3
        $new->errorAttributes['class'] = $class;
353 3
        $new->replaceErrorClass = true;
354 3
        return $new;
355
    }
356
357
    /**
358
     * Add one or more CSS classes to the error tag.
359
     *
360
     * @param string|null ...$class One or many CSS classes.
361
     */
362 2
    final public function addErrorClass(?string ...$class): static
363
    {
364 2
        $new = clone $this;
365 2
        Html::addCssClass($new->errorAttributes, $class);
366 2
        return $new;
367
    }
368
369 25
    final public function error(?string $message, string ...$messages): static
370
    {
371 25
        $new = clone $this;
372 25
        $new->errorConfig['message()'] = [$message, ...$messages];
373 25
        return $new;
374
    }
375
376 423
    protected function shouldHideLabel(): bool
377
    {
378 423
        return false;
379
    }
380
381 24
    protected function generateInput(): string
382
    {
383 24
        return '';
384
    }
385
386 6
    protected function generateBeginInput(): string
387
    {
388 6
        return '';
389
    }
390
391 4
    protected function generateEndInput(): string
392
    {
393 4
        return '';
394
    }
395
396 148
    protected function renderLabel(Label $label): string
397
    {
398 148
        return $label->render();
399
    }
400
401 154
    protected function renderHint(Hint $hint): string
402
    {
403 154
        return $hint->render();
404
    }
405
406 154
    protected function renderError(Error $error): string
407
    {
408 154
        return $error->render();
409
    }
410
411 645
    final protected function generateContent(): ?string
412
    {
413 645
        $parts = [
414 645
            '{input}' => $this->generateInputContainerBegin()
415 645
                . $this->generateInput()
416 645
                . $this->generateInputContainerEnd(),
417 645
            '{label}' => ($this->hideLabel ?? $this->shouldHideLabel()) ? '' : $this->generateLabel(),
418 645
            '{hint}' => $this->generateHint(),
419 645
            '{error}' => $this->generateError(),
420 645
        ];
421
422 626
        return $this->makeContent($this->template, $parts);
423
    }
424
425 9
    final protected function generateBeginContent(): string
426
    {
427 9
        $parts = [
428 9
            '{input}' => $this->generateBeginInput(),
429 9
            '{label}' => ($this->hideLabel ?? $this->shouldHideLabel()) ? '' : $this->generateLabel(),
430 9
            '{hint}' => $this->generateHint(),
431 9
            '{error}' => $this->generateError(),
432 9
        ];
433
434 9
        return $this->makeContent($this->templateBegin, $parts);
435
    }
436
437 7
    final protected function generateEndContent(): string
438
    {
439 7
        $parts = [
440 7
            '{input}' => $this->generateEndInput(),
441 7
            '{label}' => ($this->hideLabel ?? $this->shouldHideLabel()) ? '' : $this->generateLabel(),
442 7
            '{hint}' => $this->generateHint(),
443 7
            '{error}' => $this->generateError(),
444 7
        ];
445
446 7
        return $this->makeContent($this->templateEnd, $parts);
447
    }
448
449 452
    final protected function hasCustomError(): bool
450
    {
451 452
        return isset($this->errorConfig['message()']);
452
    }
453
454 645
    private function generateInputContainerBegin(): string
455
    {
456 645
        return $this->inputContainerTag === null
457 625
            ? ''
458 645
            : Html::tag($this->inputContainerTag, '', $this->inputContainerAttributes)->open();
459
    }
460
461 626
    private function generateInputContainerEnd(): string
462
    {
463 626
        return $this->inputContainerTag === null ? '' : ('</' . $this->inputContainerTag . '>');
464
    }
465
466 634
    private function makeContent(string $template, array $parts): string
467
    {
468 634
        if (!empty($this->extraTokens)) {
469 2
            $parts += $this->extraTokens;
470
        }
471
472 634
        return preg_replace('/^\h*\v+/m', '', trim(strtr($template, $parts)));
473
    }
474
475 429
    private function generateLabel(): string
476
    {
477 429
        $labelConfig = $this->labelConfig;
478 429
        if ($this->label !== null) {
479 13
            $labelConfig['content()'] = [$this->label];
480
        }
481
482 429
        $label = Label::widget([], $labelConfig);
483
484 429
        $labelAttributes = $this->labelAttributes;
485 429
        if (!empty($labelAttributes)) {
486 7
            if ($this->replaceLabelAttributes) {
487 2
                $label = $label->attributes($labelAttributes);
0 ignored issues
show
Bug introduced by
The method attributes() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Form\Field\Part\Label or Yiisoft\Form\Field\Part\Error or Yiisoft\Form\Field\Part\Hint. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

487
                /** @scrutinizer ignore-call */ 
488
                $label = $label->attributes($labelAttributes);
Loading history...
488
            } else {
489
                /** @psalm-var array<array-key,string|null>|string|null $class */
490 5
                $class = $this->labelAttributes['class'] ?? null;
491 5
                unset($labelAttributes['class']);
492
493 5
                $label = $label->addAttributes($labelAttributes);
0 ignored issues
show
Bug introduced by
The method addAttributes() does not exist on Yiisoft\Widget\Widget. It seems like you code against a sub-type of Yiisoft\Widget\Widget such as Yiisoft\Form\Field\Part\Label or Yiisoft\Form\Field\Part\Error or Yiisoft\Form\Field\Part\Hint. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

493
                /** @scrutinizer ignore-call */ 
494
                $label = $label->addAttributes($labelAttributes);
Loading history...
494
495 5
                if ($this->replaceLabelClass) {
496 2
                    $label = is_array($class) ? $label->class(...$class) : $label->class($class);
497 3
                } elseif ($class !== null) {
498 2
                    $label = is_array($class) ? $label->addClass(...$class) : $label->addClass($class);
499
                }
500
            }
501
        }
502
503 429
        return $this->renderLabel($label);
504
    }
505
506 634
    private function generateHint(): string
507
    {
508 634
        $hint = Hint::widget([], $this->hintConfig);
509
510 634
        $hintAttributes = $this->hintAttributes;
511 634
        if (!empty($hintAttributes)) {
512 7
            if ($this->replaceHintAttributes) {
513 2
                $hint = $hint->attributes($hintAttributes);
514
            } else {
515
                /** @psalm-var array<array-key,string|null>|string|null $class */
516 5
                $class = $this->hintAttributes['class'] ?? null;
517 5
                unset($hintAttributes['class']);
518
519 5
                $hint = $hint->addAttributes($hintAttributes);
520
521 5
                if ($this->replaceHintClass) {
522 2
                    $hint = is_array($class) ? $hint->class(...$class) : $hint->class($class);
523 3
                } elseif ($class !== null) {
524 2
                    $hint = is_array($class) ? $hint->addClass(...$class) : $hint->addClass($class);
525
                }
526
            }
527
        }
528
529 634
        return $this->renderHint($hint);
530
    }
531
532 634
    private function generateError(): string
533
    {
534 634
        $error = Error::widget([], $this->errorConfig);
535
536 634
        $errorAttributes = $this->errorAttributes;
537 634
        if (!empty($errorAttributes)) {
538 7
            if ($this->replaceErrorAttributes) {
539 2
                $error = $error->attributes($errorAttributes);
540
            } else {
541
                /** @psalm-var array<array-key,string|null>|string|null $class */
542 5
                $class = $this->errorAttributes['class'] ?? null;
543 5
                unset($errorAttributes['class']);
544
545 5
                $error = $error->addAttributes($errorAttributes);
546
547 5
                if ($this->replaceErrorClass) {
548 2
                    $error = is_array($class) ? $error->class(...$class) : $error->class($class);
549 3
                } elseif ($class !== null) {
550 2
                    $error = is_array($class) ? $error->addClass(...$class) : $error->addClass($class);
551
                }
552
            }
553
        }
554
555 634
        return $this->renderError($error);
556
    }
557
558
    /**
559
     * @psalm-assert non-empty-string $token
560
     */
561 5
    private function validateToken(string $token): void
562
    {
563 5
        if ($token === '') {
564 1
            throw new InvalidArgumentException('Token must be non-empty string.');
565
        }
566
567 4
        if (in_array($token, self::BUILTIN_TOKENS, true)) {
568 1
            throw new InvalidArgumentException(
569 1
                sprintf(
570 1
                    'Token name "%s" is built-in.',
571 1
                    $token,
572 1
                )
573 1
            );
574
        }
575
    }
576
}
577