Passed
Pull Request — master (#275)
by Sergei
02:49
created

FormModelInputData::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 2
dl 0
loc 4
ccs 1
cts 1
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 496
    public function __construct(
26
        private FormModelInterface $model,
27
        private string $property,
28
    ) {
29 496
    }
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 The generated input name.
52
     */
53 412
    public function getName(): string
54
    {
55 412
        $data = $this->parseProperty($this->property);
56 412
        $formName = $this->model->getFormName();
57
58 412
        if ($formName === '' && $data['prefix'] === '') {
59
            return $this->property;
60
        }
61
62 412
        if ($formName !== '') {
63 412
            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 380
    public function getValue(): mixed
76
    {
77 380
        $parsedName = $this->parseProperty($this->property);
78 380
        return $this->model->getAttributeValue($parsedName['name'] . $parsedName['suffix']);
79
    }
80
81 210
    public function getLabel(): ?string
82
    {
83 210
        return $this->model->getAttributeLabel($this->getPropertyName());
84
    }
85
86 408
    public function getHint(): ?string
87
    {
88 408
        return $this->model->getAttributeHint($this->getPropertyName());
89
    }
90
91 181
    public function getPlaceholder(): ?string
92
    {
93 181
        $placeholder = $this->model->getAttributePlaceholder($this->getPropertyName());
94 181
        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 The generated input ID.
106
     */
107 366
    public function getId(): string
108
    {
109 366
        $name = mb_strtolower($this->getName(), 'UTF-8');
110 366
        return str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], $name);
111
    }
112
113 336
    public function isValidated(): bool
114
    {
115 336
        return $this->model->getValidationResult() !== null;
116
    }
117
118 410
    public function getValidationErrors(): array
119
    {
120
        /** @psalm-var list<string> */
121 410
        return $this->model
122 410
            ->getValidationResult()
123 410
            ?->getAttributeErrorMessages($this->getPropertyName())
124 410
            ?? [];
125
    }
126
127 468
    private function getPropertyName(): string
128
    {
129 468
        $property = $this->parseProperty($this->property)['name'];
130
131 468
        if (!$this->model->hasAttribute($property)) {
132
            throw new InvalidArgumentException('Property "' . $property . '" does not exist.');
133
        }
134
135 468
        return $property;
136
    }
137
138
    /**
139
     * This method parses a property expression and returns an associative array containing
140
     * real property name, prefix and suffix.
141
     * For example: `['name' => 'content', 'prefix' => '', 'suffix' => '[0]']`
142
     *
143
     * A property expression is a property name prefixed and/or suffixed with array indexes. It is mainly used in
144
     * tabular data input and/or input of array type. Below are some examples:
145
     *
146
     * - `[0]content` is used in tabular data input to represent the "content" property for the first model in tabular
147
     *    input;
148
     * - `dates[0]` represents the first array element of the "dates" property;
149
     * - `[0]dates[0]` represents the first array element of the "dates" property for the first model in tabular
150
     *    input.
151
     *
152
     * @param string $property The property name or expression
153
     *
154
     * @throws InvalidArgumentException If the property name contains non-word characters.
155
     * @return string[] The property name, prefix and suffix.
156
     */
157 487
    private function parseProperty(string $property): array
158
    {
159 487
        if (!preg_match('/(^|.*\])([\w\.\+\-_]+)(\[.*|$)/u', $property, $matches)) {
160
            throw new InvalidArgumentException('Property name must contain word characters only.');
161
        }
162 487
        return [
163 487
            'name' => $matches[2],
164 487
            'prefix' => $matches[1],
165 487
            'suffix' => $matches[3],
166 487
        ];
167
    }
168
}
169