Passed
Pull Request — master (#192)
by Sergei
13:59
created

PartsField::token()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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

416
                /** @scrutinizer ignore-call */ 
417
                $label = $label->replaceAttributes($labelAttributes);
Loading history...
417
            } else {
418
                /** @var string|string[]|null $class */
419 4
                $class = $this->labelAttributes['class'] ?? null;
420 4
                unset($labelAttributes['class']);
421
422 4
                $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

422
                /** @scrutinizer ignore-call */ 
423
                $label = $label->attributes($labelAttributes);
Loading history...
423
424 4
                if ($this->replaceLabelClass) {
425 1
                    $label = is_array($class) ? $label->replaceClass(...$class) : $label->replaceClass($class);
426 3
                } elseif ($class !== null) {
427 2
                    $label = is_array($class) ? $label->class(...$class) : $label->class($class);
428
                }
429
            }
430
        }
431
432 226
        return $this->renderLabel($label);
433
    }
434
435 450
    private function generateHint(): string
436
    {
437 450
        $hint = Hint::widget($this->hintConfig);
438
439 450
        $hintAttributes = $this->hintAttributes;
440 450
        if (!empty($hintAttributes)) {
441 5
            if ($this->replaceHintAttributes) {
442 1
                $hint = $hint->replaceAttributes($hintAttributes);
443
            } else {
444
                /** @var string|string[]|null $class */
445 4
                $class = $this->hintAttributes['class'] ?? null;
446 4
                unset($hintAttributes['class']);
447
448 4
                $hint = $hint->attributes($hintAttributes);
449
450 4
                if ($this->replaceHintClass) {
451 1
                    $hint = is_array($class) ? $hint->replaceClass(...$class) : $hint->replaceClass($class);
452 3
                } elseif ($class !== null) {
453 2
                    $hint = is_array($class) ? $hint->class(...$class) : $hint->class($class);
454
                }
455
            }
456
        }
457
458 450
        return $this->renderHint($hint);
459
    }
460
461 450
    private function generateError(): string
462
    {
463 450
        $error = Error::widget($this->errorConfig);
464
465 450
        $errorAttributes = $this->errorAttributes;
466 450
        if (!empty($errorAttributes)) {
467 5
            if ($this->replaceErrorAttributes) {
468 1
                $error = $error->replaceAttributes($errorAttributes);
469
            } else {
470
                /** @var string|string[]|null $class */
471 4
                $class = $this->errorAttributes['class'] ?? null;
472 4
                unset($errorAttributes['class']);
473
474 4
                $error = $error->attributes($errorAttributes);
475
476 4
                if ($this->replaceErrorClass) {
477 1
                    $error = is_array($class) ? $error->replaceClass(...$class) : $error->replaceClass($class);
478 3
                } elseif ($class !== null) {
479 2
                    $error = is_array($class) ? $error->class(...$class) : $error->class($class);
480
                }
481
            }
482
        }
483
484 450
        return $this->renderError($error);
485
    }
486
487
    /**
488
     * @psalm-assert non-empty-string $token
489
     */
490 5
    private function validateToken(string $token): void
491
    {
492 5
        if ($token === '') {
493 1
            throw new InvalidArgumentException('Token must be non-empty string.');
494
        }
495
496 4
        if (in_array($token, self::BUILTIN_TOKENS, true)) {
497 1
            throw new InvalidArgumentException(
498 1
                sprintf(
499
                    'Token name "%s" is built-in.',
500
                    $token,
501
                )
502
            );
503
        }
504
    }
505
}
506