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