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 | 105 | public function __construct(AnnotatedInterface $model) |
|
63 | |||
64 | /** |
||
65 | * Validate model, optionally only selected fields |
||
66 | * @param string[] $fields |
||
67 | * @return boolean |
||
68 | */ |
||
69 | 77 | public function validate($fields = []) |
|
70 | { |
||
71 | 77 | $valid = []; |
|
72 | 77 | if (empty($fields)) |
|
73 | { |
||
74 | 77 | $fields = array_keys($this->meta->fields()); |
|
75 | } |
||
76 | 77 | foreach ($fields as $name) |
|
77 | { |
||
78 | 77 | $fieldMeta = $this->meta->field($name); |
|
79 | |||
80 | // Reset errors |
||
81 | 77 | $this->errors[$name] = []; |
|
82 | |||
83 | // Check if meta for field exists |
||
84 | 77 | if (empty($fieldMeta)) |
|
85 | { |
||
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 | 77 | if ($fieldMeta->owned && $fieldMeta->updatable) |
|
92 | { |
||
93 | 24 | if (is_array($this->model->$name)) |
|
94 | { |
||
95 | // Handle arrays of documents |
||
96 | 10 | foreach ($this->model->$name as $model) |
|
97 | { |
||
98 | 10 | $validator = new Validator($model); |
|
99 | 10 | $isValid = $validator->validate(); |
|
100 | 10 | $valid[] = (int) $isValid; |
|
101 | 10 | if (!$isValid) |
|
102 | { |
||
103 | 2 | $errors = $validator->getErrors(); |
|
104 | 10 | $this->setErrors($errors); |
|
105 | } |
||
106 | } |
||
107 | } |
||
108 | 18 | elseif (!empty($this->model->$name)) |
|
109 | { |
||
110 | // Handle single documents |
||
111 | 18 | $validator = new Validator($this->model->$name); |
|
112 | 18 | $isValid = $validator->validate(); |
|
113 | 18 | $valid[] = (int) $isValid; |
|
114 | 18 | if (!$isValid) |
|
115 | { |
||
116 | 2 | $errors = $validator->getErrors(); |
|
117 | 2 | $this->setErrors($errors); |
|
118 | } |
||
119 | } |
||
120 | } |
||
121 | |||
122 | // Skip field without validators |
||
123 | 77 | if (empty($fieldMeta->validators)) |
|
124 | { |
||
125 | 76 | continue; |
|
126 | } |
||
127 | 16 | $valid[] = (int) $this->validateEntity($name, $fieldMeta->validators); |
|
128 | } |
||
129 | |||
130 | // Model validators |
||
131 | 77 | $typeValidators = $this->meta->type()->validators; |
|
132 | 77 | if (!empty($typeValidators)) |
|
133 | { |
||
134 | 1 | $typeName = $this->meta->type()->name; |
|
135 | // Reset errors |
||
136 | 1 | $this->errors[$typeName] = []; |
|
137 | 1 | $valid[] = (int) $this->validateEntity($typeName, $typeValidators); |
|
138 | } |
||
139 | 77 | return count($valid) === array_sum($valid); |
|
140 | } |
||
141 | |||
142 | 17 | private function validateEntity($name, $validators) |
|
143 | { |
||
144 | 17 | $valid = []; |
|
145 | 17 | foreach ($validators as $validatorMeta) |
|
146 | { |
||
147 | // Filter out validators based on scenarios |
||
148 | 17 | if (!empty($validatorMeta->on)) |
|
149 | { |
||
150 | 2 | $on = (array) $validatorMeta->on; |
|
151 | 2 | $enabled = false; |
|
152 | 2 | foreach ($on as $scenario) |
|
153 | { |
||
154 | 2 | if ($scenario === ScenarioManager::getScenario($this->model)) |
|
155 | { |
||
156 | 2 | $enabled = true; |
|
157 | 2 | break; |
|
158 | } |
||
159 | } |
||
160 | 2 | if (!$enabled) |
|
161 | { |
||
162 | 2 | continue; |
|
163 | } |
||
164 | } |
||
165 | 17 | if (!empty($validatorMeta->except)) |
|
166 | { |
||
167 | 1 | $except = (array) $validatorMeta->except; |
|
168 | 1 | $enabled = true; |
|
169 | 1 | foreach ($except as $scenario) |
|
170 | { |
||
171 | 1 | if ($scenario === ScenarioManager::getScenario($this->model)) |
|
172 | { |
||
173 | 1 | $enabled = false; |
|
174 | 1 | break; |
|
175 | } |
||
176 | } |
||
177 | 1 | if (!$enabled) |
|
178 | { |
||
179 | 1 | continue; |
|
180 | } |
||
181 | } |
||
182 | |||
183 | |||
184 | // Create validator and validate |
||
185 | 17 | $validator = Factory::create($this->model, $validatorMeta, $name); |
|
186 | 17 | if ($validator->isValid($this->model, $name)) |
|
187 | { |
||
188 | 13 | $valid[] = true; |
|
189 | } |
||
190 | else |
||
191 | { |
||
192 | 17 | $valid[] = false; |
|
193 | 17 | $this->errors[$name] = array_merge($this->errors[$name], $validator->getErrors()); |
|
194 | |||
195 | // Set errors to model instance if it implements ValidatableInterface |
||
196 | 17 | if ($this->model instanceof ValidatableInterface) |
|
197 | { |
||
198 | 17 | $this->model->setErrors($this->errors); |
|
199 | } |
||
200 | } |
||
201 | } |
||
202 | 17 | return count($valid) === array_sum($valid); |
|
203 | } |
||
204 | |||
205 | 6 | public function getErrors() |
|
209 | |||
210 | 7 | public function setErrors($errors) |
|
217 | |||
218 | } |
||
219 |