Passed
Push — master ( c15889...08ecf6 )
by Sergei
03:03
created

FormModelInputData   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Test Coverage

Coverage 90%

Importance

Changes 0
Metric Value
eloc 39
dl 0
loc 156
ccs 45
cts 50
cp 0.9
rs 10
c 0
b 0
f 0
wmc 20

12 Methods

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