Passed
Push — master ( 5e485e...857d21 )
by Sergei
02:50
created

FormModelInputData::getId()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 9
ccs 5
cts 6
cp 0.8333
crap 2.0185
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Form\Field\Base\InputData;
6
7
use InvalidArgumentException;
8
use Yiisoft\Form\Exception\PropertyNotSupportNestedValuesException;
9
use Yiisoft\Form\Exception\StaticObjectPropertyException;
10
use Yiisoft\Form\Exception\UndefinedObjectPropertyException;
11
use Yiisoft\Form\Exception\ValueNotFoundException;
12
use Yiisoft\Form\FormModelInterface;
13
use Yiisoft\Validator\Helper\RulesNormalizer;
14
15
/**
16
 * @psalm-import-type NormalizedRulesList from RulesNormalizer
17
 */
18
final class FormModelInputData implements InputDataInterface
19
{
20
    /**
21
     * @psalm-var NormalizedRulesList|null
22
     */
23
    private ?iterable $validationRules = null;
24
25 499
    public function __construct(
26
        private FormModelInterface $model,
27
        private string $property,
28
    ) {
29 499
    }
30
31 45
    public function getValidationRules(): iterable
32
    {
33 45
        if ($this->validationRules === null) {
34 45
            $rules = RulesNormalizer::normalize(null, $this->model);
35 45
            $this->validationRules = $rules[$this->property] ?? [];
36
        }
37 45
        return $this->validationRules;
38
    }
39
40
    /**
41
     * Generates an appropriate input name.
42
     *
43
     * This method generates a name that can be used as the input name to collect user input. The name is generated
44
     * according to the form and the property names. For example, if the form name is `Post`
45
     * then the input name generated for the `content` property would be `Post[content]`.
46
     *
47
     * See {@see getPropertyName()} for explanation of attribute expression.
48
     *
49
     * @throws InvalidArgumentException If the attribute name contains non-word characters or empty form name for
50
     * tabular inputs.
51
     * @return string|null The generated input name.
52
     */
53 415
    public function getName(): ?string
54
    {
55 415
        $data = $this->parseProperty($this->property);
56 415
        $formName = $this->model->getFormName();
57
58 415
        if ($formName === '' && $data['prefix'] === '') {
59
            return $this->property;
60
        }
61
62 415
        if ($formName !== '') {
63 415
            return "$formName{$data['prefix']}[{$data['name']}]{$data['suffix']}";
64
        }
65
66
        throw new InvalidArgumentException('formName() cannot be empty for tabular inputs.');
67
    }
68
69
    /**
70
     * @throws UndefinedObjectPropertyException
71
     * @throws StaticObjectPropertyException
72
     * @throws PropertyNotSupportNestedValuesException
73
     * @throws ValueNotFoundException
74
     */
75 383
    public function getValue(): mixed
76
    {
77 383
        $parsedName = $this->parseProperty($this->property);
78 383
        return $this->model->getAttributeValue($parsedName['name'] . $parsedName['suffix']);
79
    }
80
81 214
    public function getLabel(): ?string
82
    {
83 214
        return $this->model->getAttributeLabel($this->getPropertyName());
84
    }
85
86 411
    public function getHint(): ?string
87
    {
88 411
        return $this->model->getAttributeHint($this->getPropertyName());
89
    }
90
91 185
    public function getPlaceholder(): ?string
92
    {
93 185
        $placeholder = $this->model->getAttributePlaceholder($this->getPropertyName());
94 185
        return $placeholder === '' ? null : $placeholder;
95
    }
96
97
    /**
98
     * Generates an appropriate input ID.
99
     *
100
     * This method converts the result {@see getName()} into a valid input ID.
101
     *
102
     * For example, if {@see getInputName()} returns `Post[content]`, this method will return `post-content`.
103
     *
104
     * @throws InvalidArgumentException If the attribute name contains non-word characters.
105
     * @return string|null The generated input ID.
106
     */
107 369
    public function getId(): ?string
108
    {
109 369
        $name = $this->getName();
110 369
        if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
111
            return null;
112
        }
113
114 369
        $name = mb_strtolower($name, 'UTF-8');
115 369
        return str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], $name);
116
    }
117
118 339
    public function isValidated(): bool
119
    {
120 339
        return $this->model->getValidationResult() !== null;
121
    }
122
123 413
    public function getValidationErrors(): array
124
    {
125
        /** @psalm-var list<string> */
126 413
        return $this->model
127 413
            ->getValidationResult()
128 413
            ?->getAttributeErrorMessages($this->getPropertyName())
129 413
            ?? [];
130
    }
131
132 471
    private function getPropertyName(): string
133
    {
134 471
        $property = $this->parseProperty($this->property)['name'];
135
136 471
        if (!$this->model->hasAttribute($property)) {
137
            throw new InvalidArgumentException('Property "' . $property . '" does not exist.');
138
        }
139
140 471
        return $property;
141
    }
142
143
    /**
144
     * This method parses a property expression and returns an associative array containing
145
     * real property name, prefix and suffix.
146
     * For example: `['name' => 'content', 'prefix' => '', 'suffix' => '[0]']`
147
     *
148
     * A property expression is a property name prefixed and/or suffixed with array indexes. It is mainly used in
149
     * tabular data input and/or input of array type. Below are some examples:
150
     *
151
     * - `[0]content` is used in tabular data input to represent the "content" property for the first model in tabular
152
     *    input;
153
     * - `dates[0]` represents the first array element of the "dates" property;
154
     * - `[0]dates[0]` represents the first array element of the "dates" property for the first model in tabular
155
     *    input.
156
     *
157
     * @param string $property The property name or expression
158
     *
159
     * @throws InvalidArgumentException If the property name contains non-word characters.
160
     * @return string[] The property name, prefix and suffix.
161
     */
162 490
    private function parseProperty(string $property): array
163
    {
164 490
        if (!preg_match('/(^|.*\])([\w\.\+\-_]+)(\[.*|$)/u', $property, $matches)) {
165
            throw new InvalidArgumentException('Property name must contain word characters only.');
166
        }
167 490
        return [
168 490
            'name' => $matches[2],
169 490
            'prefix' => $matches[1],
170 490
            'suffix' => $matches[3],
171 490
        ];
172
    }
173
}
174