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