Passed
Pull Request — master (#281)
by Sergei
02:50
created

FormModelInputData::withName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
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 1
dl 0
loc 6
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\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
    private bool $useCustomName = false;
26
    private ?string $customName = null;
27
    private bool $useCustomValue = false;
28
    private mixed $customValue = null;
29
30 499
    public function __construct(
31
        private FormModelInterface $model,
32
        private string $property,
33
    ) {
34 499
    }
35
36 45
    public function getValidationRules(): iterable
37
    {
38 45
        if ($this->validationRules === null) {
39 45
            $rules = RulesNormalizer::normalize(null, $this->model);
40 45
            $this->validationRules = $rules[$this->property] ?? [];
41
        }
42 45
        return $this->validationRules;
43
    }
44
45
    /**
46
     * Generates an appropriate input name.
47
     *
48
     * This method generates a name that can be used as the input name to collect user input. The name is generated
49
     * according to the form and the property names. For example, if the form name is `Post`
50
     * then the input name generated for the `content` property would be `Post[content]`.
51
     *
52
     * See {@see getPropertyName()} for explanation of attribute expression.
53
     *
54
     * @throws InvalidArgumentException If the attribute name contains non-word characters or empty form name for
55
     * tabular inputs.
56
     * @return string|null The generated input name.
57
     */
58 415
    public function getName(): ?string
59
    {
60 415
        if ($this->useCustomName) {
61 1
            return $this->customName;
62
        }
63
64 414
        $data = $this->parseProperty($this->property);
65 414
        $formName = $this->model->getFormName();
66
67 414
        if ($formName === '' && $data['prefix'] === '') {
68
            return $this->property;
69
        }
70
71 414
        if ($formName !== '') {
72 414
            return "$formName{$data['prefix']}[{$data['name']}]{$data['suffix']}";
73
        }
74
75
        throw new InvalidArgumentException('formName() cannot be empty for tabular inputs.');
76
    }
77
78
    /**
79
     * @throws UndefinedObjectPropertyException
80
     * @throws StaticObjectPropertyException
81
     * @throws PropertyNotSupportNestedValuesException
82
     * @throws ValueNotFoundException
83
     */
84 384
    public function getValue(): mixed
85
    {
86 384
        if ($this->useCustomValue) {
87 1
            return $this->customValue;
88
        }
89
90 383
        $parsedName = $this->parseProperty($this->property);
91 383
        return $this->model->getAttributeValue($parsedName['name'] . $parsedName['suffix']);
92
    }
93
94 214
    public function getLabel(): ?string
95
    {
96 214
        return $this->model->getAttributeLabel($this->getPropertyName());
97
    }
98
99 411
    public function getHint(): ?string
100
    {
101 411
        return $this->model->getAttributeHint($this->getPropertyName());
102
    }
103
104 185
    public function getPlaceholder(): ?string
105
    {
106 185
        $placeholder = $this->model->getAttributePlaceholder($this->getPropertyName());
107 185
        return $placeholder === '' ? null : $placeholder;
108
    }
109
110
    /**
111
     * Generates an appropriate input ID.
112
     *
113
     * This method converts the result {@see getName()} into a valid input ID.
114
     *
115
     * For example, if {@see getInputName()} returns `Post[content]`, this method will return `post-content`.
116
     *
117
     * @throws InvalidArgumentException If the attribute name contains non-word characters.
118
     * @return string|null The generated input ID.
119
     */
120 369
    public function getId(): ?string
121
    {
122 369
        $name = $this->getName();
123 369
        if ($name === null) {
124
            return null;
125
        }
126
127 369
        $name = mb_strtolower($name, 'UTF-8');
128 369
        return str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], $name);
129
    }
130
131 339
    public function isValidated(): bool
132
    {
133 339
        return $this->model->getValidationResult() !== null;
134
    }
135
136 413
    public function getValidationErrors(): array
137
    {
138
        /** @psalm-var list<string> */
139 413
        return $this->model
140 413
            ->getValidationResult()
141 413
            ?->getAttributeErrorMessages($this->getPropertyName())
142 413
            ?? [];
143
    }
144
145 471
    private function getPropertyName(): string
146
    {
147 471
        $property = $this->parseProperty($this->property)['name'];
148
149 471
        if (!$this->model->hasAttribute($property)) {
150
            throw new InvalidArgumentException('Property "' . $property . '" does not exist.');
151
        }
152
153 471
        return $property;
154
    }
155
156
    /**
157
     * This method parses a property expression and returns an associative array containing
158
     * real property name, prefix and suffix.
159
     * For example: `['name' => 'content', 'prefix' => '', 'suffix' => '[0]']`
160
     *
161
     * A property expression is a property name prefixed and/or suffixed with array indexes. It is mainly used in
162
     * tabular data input and/or input of array type. Below are some examples:
163
     *
164
     * - `[0]content` is used in tabular data input to represent the "content" property for the first model in tabular
165
     *    input;
166
     * - `dates[0]` represents the first array element of the "dates" property;
167
     * - `[0]dates[0]` represents the first array element of the "dates" property for the first model in tabular
168
     *    input.
169
     *
170
     * @param string $property The property name or expression
171
     *
172
     * @throws InvalidArgumentException If the property name contains non-word characters.
173
     * @return string[] The property name, prefix and suffix.
174
     */
175 490
    private function parseProperty(string $property): array
176
    {
177 490
        if (!preg_match('/(^|.*\])([\w\.\+\-_]+)(\[.*|$)/u', $property, $matches)) {
178
            throw new InvalidArgumentException('Property name must contain word characters only.');
179
        }
180 490
        return [
181 490
            'name' => $matches[2],
182 490
            'prefix' => $matches[1],
183 490
            'suffix' => $matches[3],
184 490
        ];
185
    }
186
187 1
    public function withName(?string $name): static
188
    {
189 1
        $new = clone $this;
190 1
        $new->customName = $name;
191 1
        $new->useCustomName = true;
192 1
        return $new;
193
    }
194
195 1
    public function withValue(mixed $value): static
196
    {
197 1
        $new = clone $this;
198 1
        $new->customValue = $value;
199 1
        $new->useCustomValue = true;
200 1
        return $new;
201
    }
202
}
203