Passed
Push — master ( 087da0...dafd8c )
by Alexander
17:08 queued 14:17
created

PartsField::addInputContainerClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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

488
                /** @scrutinizer ignore-call */ 
489
                $label = $label->attributes($labelAttributes);
Loading history...
489
            } else {
490
                /** @var string|string[]|null $class */
491 4
                $class = $this->labelAttributes['class'] ?? null;
492 4
                unset($labelAttributes['class']);
493
494 4
                $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

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