1 | <?php |
||
27 | class Validator implements ValidatableInterface |
||
28 | { |
||
29 | |||
30 | /** |
||
31 | * Model instance |
||
32 | * @var AnnotatedInterface |
||
33 | */ |
||
34 | private $model = null; |
||
35 | |||
36 | /** |
||
37 | * Metadata for model |
||
38 | * @var ManganMeta |
||
39 | */ |
||
40 | private $meta = null; |
||
41 | |||
42 | /** |
||
43 | * Array of error messages. |
||
44 | * Keys are field names, secondary keys are numeric |
||
45 | * @var string[][] |
||
46 | */ |
||
47 | private $errors = []; |
||
48 | |||
49 | 128 | public function __construct(AnnotatedInterface $model) |
|
50 | { |
||
51 | 128 | $this->model = $model; |
|
52 | 128 | $this->meta = ManganMeta::create($this->model); |
|
53 | |||
54 | // Ensure that errors array is initialized - even if does not have validators |
||
55 | 128 | foreach (array_keys($this->meta->fields()) as $name) |
|
56 | { |
||
57 | 128 | $this->errors[$name] = []; |
|
58 | 128 | } |
|
59 | 128 | } |
|
60 | |||
61 | /** |
||
62 | * Validate model, optionally only selected fields |
||
63 | * @param string[] $fields |
||
64 | * @return boolean |
||
65 | */ |
||
66 | 92 | public function validate($fields = []) |
|
67 | { |
||
68 | 92 | $valid = []; |
|
69 | 92 | if (empty($fields)) |
|
70 | 92 | { |
|
71 | 92 | $fields = array_keys($this->meta->fields()); |
|
72 | 92 | } |
|
73 | 92 | foreach ($fields as $name) |
|
74 | { |
||
75 | 92 | $fieldMeta = $this->meta->field($name); |
|
76 | |||
77 | // Reset errors |
||
78 | 92 | $this->errors[$name] = []; |
|
79 | |||
80 | // Check if meta for field exists |
||
81 | 92 | if (empty($fieldMeta)) |
|
82 | 92 | { |
|
83 | throw new InvalidArgumentException(sprintf("Unknown field `%s` in model `%s`", $name, get_class($this->model))); |
||
84 | } |
||
85 | |||
86 | // Validate sub documents |
||
87 | // Skip fields that are not updatable |
||
88 | 92 | if ($fieldMeta->owned && $fieldMeta->updatable) |
|
89 | 92 | { |
|
90 | 28 | if (is_array($this->model->$name)) |
|
91 | 28 | { |
|
92 | // Handle arrays of documents |
||
93 | 11 | foreach ($this->model->$name as $fieldIndex => $model) |
|
94 | { |
||
95 | 11 | $validator = new Validator($model); |
|
96 | 11 | $isValid = $validator->validate(); |
|
97 | 11 | $valid[] = (int)$isValid; |
|
98 | 11 | if (!$isValid) |
|
99 | 11 | { |
|
100 | $errors = [ |
||
101 | $name => [ |
||
102 | 3 | $fieldIndex => $validator->getErrors() |
|
103 | 3 | ] |
|
104 | 3 | ]; |
|
105 | 3 | $this->setErrors($errors); |
|
106 | 3 | } |
|
107 | 11 | } |
|
108 | 28 | } elseif (!empty($this->model->$name)) |
|
109 | { |
||
110 | // Handle single documents |
||
111 | 20 | $validator = new Validator($this->model->$name); |
|
112 | 20 | $isValid = $validator->validate(); |
|
113 | 20 | $valid[] = (int)$isValid; |
|
114 | 20 | if (!$isValid) |
|
115 | 20 | { |
|
116 | $errors = [ |
||
117 | 3 | $name => $validator->getErrors() |
|
118 | 3 | ]; |
|
119 | 3 | $this->setErrors($errors); |
|
120 | 3 | } |
|
121 | 20 | } |
|
122 | 28 | } |
|
123 | |||
124 | // Skip field without validators |
||
125 | 92 | if (empty($fieldMeta->validators)) |
|
126 | 92 | { |
|
127 | 91 | continue; |
|
128 | } |
||
129 | 21 | $valid[] = (int)$this->validateEntity($name, $fieldMeta->validators); |
|
130 | 92 | } |
|
131 | |||
132 | // Model validators |
||
133 | 92 | $typeValidators = $this->meta->type()->validators; |
|
134 | 92 | if (!empty($typeValidators)) |
|
135 | 92 | { |
|
136 | 1 | $typeName = $this->meta->type()->name; |
|
137 | // Reset errors |
||
138 | 1 | $this->errors[$typeName] = []; |
|
139 | 1 | $valid[] = (int)$this->validateEntity($typeName, $typeValidators); |
|
140 | 1 | } |
|
141 | 92 | return count($valid) === array_sum($valid); |
|
142 | } |
||
143 | |||
144 | 22 | private function validateEntity($name, $validators) |
|
145 | { |
||
146 | 22 | $valid = []; |
|
147 | 22 | foreach ($validators as $validatorMeta) |
|
148 | { |
||
149 | // Filter out validators based on scenarios |
||
150 | 22 | if (!empty($validatorMeta->on)) |
|
151 | 22 | { |
|
152 | 2 | $on = (array)$validatorMeta->on; |
|
153 | 2 | $enabled = false; |
|
154 | 2 | foreach ($on as $scenario) |
|
155 | { |
||
156 | 2 | if ($scenario === ScenarioManager::getScenario($this->model)) |
|
157 | 2 | { |
|
158 | 2 | $enabled = true; |
|
159 | 2 | break; |
|
160 | } |
||
161 | 2 | } |
|
162 | 2 | if (!$enabled) |
|
163 | 2 | { |
|
164 | 2 | continue; |
|
165 | } |
||
166 | 2 | } |
|
167 | 22 | if (!empty($validatorMeta->except)) |
|
168 | 22 | { |
|
169 | 1 | $except = (array)$validatorMeta->except; |
|
170 | 1 | $enabled = true; |
|
171 | 1 | foreach ($except as $scenario) |
|
172 | { |
||
173 | 1 | if ($scenario === ScenarioManager::getScenario($this->model)) |
|
174 | 1 | { |
|
175 | 1 | $enabled = false; |
|
176 | 1 | break; |
|
177 | } |
||
178 | 1 | } |
|
179 | 1 | if (!$enabled) |
|
180 | 1 | { |
|
181 | 1 | continue; |
|
182 | } |
||
183 | 1 | } |
|
184 | |||
185 | |||
186 | // Create validator and validate |
||
187 | 22 | $validator = Factory::create($this->model, $validatorMeta, $name); |
|
188 | 22 | if ($validator->isValid($this->model, $name)) |
|
189 | 22 | { |
|
190 | 18 | $valid[] = true; |
|
191 | 18 | } else |
|
192 | { |
||
193 | 22 | $valid[] = false; |
|
194 | 22 | $this->errors[$name] = array_merge($this->errors[$name], $validator->getErrors()); |
|
195 | |||
196 | // Set errors to model instance if it implements ValidatableInterface |
||
197 | 22 | if ($this->model instanceof ValidatableInterface) |
|
198 | 22 | { |
|
199 | 8 | $this->model->setErrors($this->errors); |
|
200 | 8 | } |
|
201 | } |
||
202 | 22 | } |
|
203 | 22 | return count($valid) === array_sum($valid); |
|
204 | } |
||
205 | |||
206 | 9 | public function getErrors() |
|
210 | |||
211 | 12 | public function setErrors($errors) |
|
212 | { |
||
213 | 12 | foreach ($errors as $field => $errors) |
|
218 | |||
219 | } |
||
220 |