Test Failed
Pull Request — master (#192)
by Sergei
02:48
created

PartsField::hintId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

402
            /** @scrutinizer ignore-call */ 
403
            $label = $label->attributes($this->labelAttributes);
Loading history...
403
        }
404
405
        return $this->renderLabel($label);
406
    }
407
408
    private function generateHint(): string
409
    {
410
        $hint = Hint::widget($this->hintConfig);
411
412
        if (!empty($this->hintAttributes)) {
413
            $hint = $hint->attributes($this->hintAttributes);
414
        }
415
416
        return $this->renderHint($hint);
417
    }
418
419
    private function generateError(): string
420
    {
421
        $error = Error::widget($this->errorConfig);
422
423
        if (!empty($this->errorAttributes)) {
424
            $error = $error->attributes($this->errorAttributes);
425
        }
426
427
        return $this->renderError($error);
428
    }
429
430
    /**
431
     * @psalm-assert non-empty-string $token
432
     */
433
    private function validateToken(string $token): void
434
    {
435
        if ($token === '') {
436
            throw new InvalidArgumentException('Token must be non-empty string.');
437
        }
438
439
        if (in_array($token, self::BUILTIN_TOKENS, true)) {
440
            throw new InvalidArgumentException(
441
                sprintf(
442
                    'Token name "%s" is built-in.',
443
                    $token,
444
                )
445
            );
446
        }
447
    }
448
}
449