Passed
Push — master ( 94344c...86ce1f )
by Alexander
07:20 queued 04:31
created

PartsField::tokens()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 15
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 29
ccs 13
cts 13
cp 1
crap 5
rs 9.4555
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 190
    final public function hideLabel(?bool $hide = true): static
122
    {
123 190
        $new = clone $this;
124 190
        $new->hideLabel = $hide;
125 190
        return $new;
126
    }
127
128 5
    final public function inputContainerTag(?string $tag): static
129
    {
130 5
        if ($tag === '') {
131 1
            throw new InvalidArgumentException('Tag name cannot be empty.');
132
        }
133
134 4
        $new = clone $this;
135 4
        $new->inputContainerTag = $tag;
136 4
        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 4
    final public function labelConfig(array $config): static
154
    {
155 4
        $new = clone $this;
156 4
        $new->labelConfig = $config;
157 4
        return $new;
158
    }
159
160 2
    final public function labelAttributes(array $attributes): static
161
    {
162 2
        $new = clone $this;
163 2
        $new->labelAttributes = $attributes;
164 2
        $new->replaceLabelAttributes = true;
165 2
        return $new;
166
    }
167
168 2
    final public function addLabelAttributes(array $attributes): static
169
    {
170 2
        $new = clone $this;
171 2
        $new->labelAttributes = array_merge($new->labelAttributes, $attributes);
172 2
        return $new;
173
    }
174
175
    /**
176
     * Set label tag ID.
177
     *
178
     * @param string|null $id Label tag ID.
179
     */
180 2
    final public function labelId(?string $id): static
181
    {
182 2
        $new = clone $this;
183 2
        $new->labelAttributes['id'] = $id;
184 2
        return $new;
185
    }
186
187
    /**
188
     * Replace label tag CSS classes with a new set of classes.
189
     *
190
     * @param string|null ...$class One or many CSS classes.
191
     */
192 2
    final public function labelClass(?string ...$class): static
193
    {
194 2
        $new = clone $this;
195 2
        $new->labelAttributes['class'] = array_filter($class, static fn ($c) => $c !== null);
196 2
        $new->replaceLabelClass = true;
197 2
        return $new;
198
    }
199
200
    /**
201
     * Add one or more CSS classes to the label tag.
202
     *
203
     * @param string|null ...$class One or many CSS classes.
204
     */
205 3
    final public function addLabelClass(?string ...$class): static
206
    {
207 3
        $new = clone $this;
208 3
        Html::addCssClass(
209 3
            $new->labelAttributes,
210 3
            array_filter($class, static fn ($c) => $c !== null),
211
        );
212 3
        return $new;
213
    }
214
215 8
    final public function label(?string $content): static
216
    {
217 8
        $new = clone $this;
218 8
        $new->labelConfig['content()'] = [$content];
219 8
        return $new;
220
    }
221
222 3
    final public function hintConfig(array $config): static
223
    {
224 3
        $new = clone $this;
225 3
        $new->hintConfig = $config;
226 3
        return $new;
227
    }
228
229 2
    final public function hintAttributes(array $attributes): static
230
    {
231 2
        $new = clone $this;
232 2
        $new->hintAttributes = $attributes;
233 2
        $new->replaceHintAttributes = true;
234 2
        return $new;
235
    }
236
237 2
    final public function addHintAttributes(array $attributes): static
238
    {
239 2
        $new = clone $this;
240 2
        $new->hintAttributes = array_merge($new->hintAttributes, $attributes);
241 2
        return $new;
242
    }
243
244
    /**
245
     * Set hint tag ID.
246
     *
247
     * @param string|null $id Hint tag ID.
248
     */
249 2
    final public function hintId(?string $id): static
250
    {
251 2
        $new = clone $this;
252 2
        $new->hintAttributes['id'] = $id;
253 2
        return $new;
254
    }
255
256
    /**
257
     * Replace hint tag CSS classes with a new set of classes.
258
     *
259
     * @param string|null ...$class One or many CSS classes.
260
     */
261 2
    final public function hintClass(?string ...$class): static
262
    {
263 2
        $new = clone $this;
264 2
        $new->hintAttributes['class'] = array_filter($class, static fn ($c) => $c !== null);
265 2
        $new->replaceHintClass = true;
266 2
        return $new;
267
    }
268
269
    /**
270
     * Add one or more CSS classes to the hint tag.
271
     *
272
     * @param string|null ...$class One or many CSS classes.
273
     */
274 2
    final public function addHintClass(?string ...$class): static
275
    {
276 2
        $new = clone $this;
277 2
        Html::addCssClass(
278 2
            $new->hintAttributes,
279 2
            array_filter($class, static fn ($c) => $c !== null),
280
        );
281 2
        return $new;
282
    }
283
284 8
    final public function hint(?string $content): static
285
    {
286 8
        $new = clone $this;
287 8
        $new->hintConfig['content()'] = [$content];
288 8
        return $new;
289
    }
290
291 3
    final public function errorConfig(array $config): static
292
    {
293 3
        $new = clone $this;
294 3
        $new->errorConfig = $config;
295 3
        return $new;
296
    }
297
298 2
    final public function errorAttributes(array $attributes): static
299
    {
300 2
        $new = clone $this;
301 2
        $new->errorAttributes = $attributes;
302 2
        $new->replaceErrorAttributes = true;
303 2
        return $new;
304
    }
305
306 2
    final public function addErrorAttributes(array $attributes): static
307
    {
308 2
        $new = clone $this;
309 2
        $new->errorAttributes = array_merge($new->errorAttributes, $attributes);
310 2
        return $new;
311
    }
312
313
    /**
314
     * Set error tag ID.
315
     *
316
     * @param string|null $id Error tag ID.
317
     */
318 2
    final public function errorId(?string $id): static
319
    {
320 2
        $new = clone $this;
321 2
        $new->errorAttributes['id'] = $id;
322 2
        return $new;
323
    }
324
325
    /**
326
     * Replace error tag CSS classes with a new set of classes.
327
     *
328
     * @param string|null ...$class One or many CSS classes.
329
     */
330 2
    final public function errorClass(?string ...$class): static
331
    {
332 2
        $new = clone $this;
333 2
        $new->errorAttributes['class'] = array_filter($class, static fn ($c) => $c !== null);
334 2
        $new->replaceErrorClass = true;
335 2
        return $new;
336
    }
337
338
    /**
339
     * Add one or more CSS classes to the error tag.
340
     *
341
     * @param string|null ...$class One or many CSS classes.
342
     */
343 2
    final public function addErrorClass(?string ...$class): static
344
    {
345 2
        $new = clone $this;
346 2
        Html::addCssClass(
347 2
            $new->errorAttributes,
348 2
            array_filter($class, static fn ($c) => $c !== null),
349
        );
350 2
        return $new;
351
    }
352
353 7
    final public function error(?string $message): static
354
    {
355 7
        $new = clone $this;
356 7
        $new->errorConfig['message()'] = [$message];
357 7
        return $new;
358
    }
359
360 228
    protected function shouldHideLabel(): bool
361
    {
362 228
        return false;
363
    }
364
365 16
    protected function generateInput(): string
366
    {
367 16
        return '';
368
    }
369
370 1
    protected function generateBeginInput(): string
371
    {
372 1
        return '';
373
    }
374
375 1
    protected function generateEndInput(): string
376
    {
377 1
        return '';
378
    }
379
380 99
    protected function renderLabel(Label $label): string
381
    {
382 99
        return $label->render();
383
    }
384
385 99
    protected function renderHint(Hint $hint): string
386
    {
387 99
        return $hint->render();
388
    }
389
390 99
    protected function renderError(Error $error): string
391
    {
392 99
        return $error->render();
393
    }
394
395 472
    final protected function generateContent(): ?string
396
    {
397 455
        $parts = [
398 472
            '{input}' => $this->generateInputContainerBegin()
399 472
                . $this->generateInput()
400 455
                . $this->generateInputContainerEnd(),
401 455
            '{label}' => ($this->hideLabel ?? $this->shouldHideLabel()) ? '' : $this->generateLabel(),
402 455
            '{hint}' => $this->generateHint(),
403 455
            '{error}' => $this->generateError(),
404
        ];
405
406 455
        return $this->makeContent($this->template, $parts);
407
    }
408
409 3
    final protected function generateBeginContent(): string
410
    {
411 3
        $parts = [
412 3
            '{input}' => $this->generateBeginInput(),
413 3
            '{label}' => ($this->hideLabel ?? $this->shouldHideLabel()) ? '' : $this->generateLabel(),
414 3
            '{hint}' => $this->generateHint(),
415 3
            '{error}' => $this->generateError(),
416
        ];
417
418 3
        return $this->makeContent($this->templateBegin, $parts);
419
    }
420
421 3
    final protected function generateEndContent(): string
422
    {
423 3
        $parts = [
424 3
            '{input}' => $this->generateEndInput(),
425 3
            '{label}' => ($this->hideLabel ?? $this->shouldHideLabel()) ? '' : $this->generateLabel(),
426 3
            '{hint}' => $this->generateHint(),
427 3
            '{error}' => $this->generateError(),
428
        ];
429
430 3
        return $this->makeContent($this->templateEnd, $parts);
431
    }
432
433 472
    private function generateInputContainerBegin(): string
434
    {
435 472
        return $this->inputContainerTag === null
436 469
            ? ''
437 472
            : Html::tag($this->inputContainerTag, '', $this->inputContainerAttributes)->open();
438
    }
439
440 455
    private function generateInputContainerEnd(): string
441
    {
442 455
        return $this->inputContainerTag === null ? '' : ('</' . $this->inputContainerTag . '>');
443
    }
444
445 457
    private function makeContent(string $template, array $parts): string
446
    {
447 457
        if (!empty($this->extraTokens)) {
448 2
            $parts += $this->extraTokens;
449
        }
450
451 457
        return preg_replace('/^\h*\v+/m', '', trim(strtr($template, $parts)));
452
    }
453
454 233
    private function generateLabel(): string
455
    {
456 233
        $label = Label::widget($this->labelConfig);
457
458 233
        $labelAttributes = $this->labelAttributes;
459 233
        if (!empty($labelAttributes)) {
460 5
            if ($this->replaceLabelAttributes) {
461 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

461
                /** @scrutinizer ignore-call */ 
462
                $label = $label->attributes($labelAttributes);
Loading history...
462
            } else {
463
                /** @var string|string[]|null $class */
464 4
                $class = $this->labelAttributes['class'] ?? null;
465 4
                unset($labelAttributes['class']);
466
467 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

467
                /** @scrutinizer ignore-call */ 
468
                $label = $label->addAttributes($labelAttributes);
Loading history...
468
469 4
                if ($this->replaceLabelClass) {
470 1
                    $label = is_array($class) ? $label->class(...$class) : $label->class($class);
471 3
                } elseif ($class !== null) {
472 2
                    $label = is_array($class) ? $label->addClass(...$class) : $label->addClass($class);
473
                }
474
            }
475
        }
476
477 233
        return $this->renderLabel($label);
478
    }
479
480 457
    private function generateHint(): string
481
    {
482 457
        $hint = Hint::widget($this->hintConfig);
483
484 457
        $hintAttributes = $this->hintAttributes;
485 457
        if (!empty($hintAttributes)) {
486 5
            if ($this->replaceHintAttributes) {
487 1
                $hint = $hint->attributes($hintAttributes);
488
            } else {
489
                /** @var string|string[]|null $class */
490 4
                $class = $this->hintAttributes['class'] ?? null;
491 4
                unset($hintAttributes['class']);
492
493 4
                $hint = $hint->addAttributes($hintAttributes);
494
495 4
                if ($this->replaceHintClass) {
496 1
                    $hint = is_array($class) ? $hint->class(...$class) : $hint->class($class);
497 3
                } elseif ($class !== null) {
498 2
                    $hint = is_array($class) ? $hint->addClass(...$class) : $hint->addClass($class);
499
                }
500
            }
501
        }
502
503 457
        return $this->renderHint($hint);
504
    }
505
506 457
    private function generateError(): string
507
    {
508 457
        $error = Error::widget($this->errorConfig);
509
510 457
        $errorAttributes = $this->errorAttributes;
511 457
        if (!empty($errorAttributes)) {
512 5
            if ($this->replaceErrorAttributes) {
513 1
                $error = $error->attributes($errorAttributes);
514
            } else {
515
                /** @var string|string[]|null $class */
516 4
                $class = $this->errorAttributes['class'] ?? null;
517 4
                unset($errorAttributes['class']);
518
519 4
                $error = $error->addAttributes($errorAttributes);
520
521 4
                if ($this->replaceErrorClass) {
522 1
                    $error = is_array($class) ? $error->class(...$class) : $error->class($class);
523 3
                } elseif ($class !== null) {
524 2
                    $error = is_array($class) ? $error->addClass(...$class) : $error->addClass($class);
525
                }
526
            }
527
        }
528
529 457
        return $this->renderError($error);
530
    }
531
532
    /**
533
     * @psalm-assert non-empty-string $token
534
     */
535 5
    private function validateToken(string $token): void
536
    {
537 5
        if ($token === '') {
538 1
            throw new InvalidArgumentException('Token must be non-empty string.');
539
        }
540
541 4
        if (in_array($token, self::BUILTIN_TOKENS, true)) {
542 1
            throw new InvalidArgumentException(
543 1
                sprintf(
544
                    'Token name "%s" is built-in.',
545
                    $token,
546
                )
547
            );
548
        }
549
    }
550
}
551