These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace frictionlessdata\tableschema\Fields; |
||
4 | |||
5 | use frictionlessdata\tableschema\Exceptions\FieldValidationException; |
||
6 | use frictionlessdata\tableschema\SchemaValidationError; |
||
7 | |||
8 | abstract class BaseField |
||
9 | { |
||
10 | public function __construct($descriptor = null) |
||
11 | { |
||
12 | $this->descriptor = empty($descriptor) ? (object) [] : $descriptor; |
||
13 | } |
||
14 | |||
15 | public function descriptor() |
||
16 | { |
||
17 | return $this->descriptor; |
||
18 | } |
||
19 | |||
20 | public function fullDescriptor() |
||
21 | { |
||
22 | $fullDescriptor = $this->descriptor(); |
||
23 | $fullDescriptor->format = $this->format(); |
||
24 | $fullDescriptor->type = $this->type(); |
||
25 | |||
26 | return $fullDescriptor; |
||
27 | } |
||
28 | |||
29 | public function name() |
||
30 | { |
||
31 | return $this->descriptor()->name; |
||
32 | } |
||
33 | |||
34 | public function format() |
||
35 | { |
||
36 | return isset($this->descriptor()->format) ? $this->descriptor()->format : 'default'; |
||
37 | } |
||
38 | |||
39 | public function constraints() |
||
40 | { |
||
41 | if (!$this->constraintsDisabled && isset($this->descriptor()->constraints)) { |
||
42 | return $this->descriptor()->constraints; |
||
43 | } else { |
||
44 | return (object) []; |
||
45 | } |
||
46 | } |
||
47 | |||
48 | public function required() |
||
49 | { |
||
50 | return isset($this->constraints()->required) && $this->constraints()->required; |
||
51 | } |
||
52 | |||
53 | public function unique() |
||
54 | { |
||
55 | return isset($this->constraints()->unique) && $this->constraints()->unique; |
||
56 | } |
||
57 | |||
58 | public function disableConstraints() |
||
59 | { |
||
60 | $this->constraintsDisabled = true; |
||
61 | |||
62 | return $this; |
||
63 | } |
||
64 | |||
65 | public function enum() |
||
66 | { |
||
67 | if (isset($this->constraints()->enum) && !empty($this->constraints()->enum)) { |
||
68 | return $this->constraints()->enum; |
||
69 | } else { |
||
70 | return []; |
||
71 | } |
||
72 | } |
||
73 | |||
74 | /** |
||
75 | * try to create a field object based on the descriptor |
||
76 | * by default uses the type attribute |
||
77 | * return the created field object or false if the descriptor does not match this field. |
||
78 | * |
||
79 | * @param object $descriptor |
||
80 | * |
||
81 | * @return bool|BaseField |
||
82 | */ |
||
83 | public static function inferDescriptor($descriptor) |
||
84 | { |
||
85 | if (isset($descriptor->type) && $descriptor->type == static::type()) { |
||
86 | return new static($descriptor); |
||
87 | } else { |
||
88 | return false; |
||
89 | } |
||
90 | } |
||
91 | |||
92 | /** |
||
93 | * try to create a new field object based on the given value. |
||
94 | * |
||
95 | * @param mixed $val |
||
96 | * @param null|object $descriptor |
||
97 | * @param bool @lenient |
||
98 | * |
||
99 | * @return bool|BaseField |
||
100 | */ |
||
101 | public static function infer($val, $descriptor = null, $lenient = false) |
||
102 | { |
||
103 | $field = new static($descriptor); |
||
104 | try { |
||
105 | $field->castValue($val); |
||
106 | } catch (FieldValidationException $e) { |
||
107 | return false; |
||
108 | } |
||
109 | $field->inferProperties($val, $lenient); |
||
110 | |||
111 | return $field; |
||
112 | } |
||
113 | |||
114 | public function inferProperties($val, $lenient) |
||
115 | { |
||
116 | // should be implemented by extending classes |
||
117 | // allows adding / modfiying descriptor properties based on the given value |
||
118 | $this->descriptor->type = $this->type(); |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * @param mixed $val |
||
123 | * |
||
124 | * @return mixed |
||
125 | * |
||
126 | * @throws \frictionlessdata\tableschema\Exceptions\FieldValidationException; |
||
127 | */ |
||
128 | final public function castValue($val) |
||
129 | { |
||
130 | if ($this->isEmptyValue($val)) { |
||
131 | if ($this->required()) { |
||
132 | throw $this->getValidationException('field is required', $val); |
||
133 | } |
||
134 | |||
135 | return null; |
||
136 | } else { |
||
137 | $val = $this->validateCastValue($val); |
||
138 | if (!$this->constraintsDisabled) { |
||
139 | $validationErrors = $this->checkConstraints($val); |
||
140 | if (count($validationErrors) > 0) { |
||
141 | throw new FieldValidationException($validationErrors); |
||
142 | } |
||
143 | } |
||
144 | |||
145 | return $val; |
||
146 | } |
||
147 | } |
||
148 | |||
149 | public function validateValue($val) |
||
150 | { |
||
151 | try { |
||
152 | $this->castValue($val); |
||
153 | |||
154 | return []; |
||
155 | } catch (FieldValidationException $e) { |
||
156 | return $e->validationErrors; |
||
157 | } |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * get a unique identifier for this field |
||
162 | * used in the inferring process |
||
163 | * this is usually the type, but can be modified to support more advanced inferring process. |
||
164 | * |
||
165 | * @param bool @lenient |
||
166 | * |
||
167 | * @return string |
||
168 | */ |
||
169 | public function getInferIdentifier($lenient = false) |
||
170 | { |
||
171 | return $this->type(); |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * should be implemented by extending classes to return the table schema type of this field. |
||
176 | * |
||
177 | * @return string |
||
178 | */ |
||
179 | public static function type() |
||
180 | { |
||
181 | throw new \Exception('must be implemented by extending classes'); |
||
182 | } |
||
183 | |||
184 | protected $descriptor; |
||
185 | protected $constraintsDisabled = false; |
||
186 | |||
187 | protected function getValidationException($errorMsg = null, $val = null) |
||
188 | { |
||
189 | return new FieldValidationException([ |
||
190 | new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [ |
||
191 | 'field' => isset($this->descriptor()->name) ? $this->name() : 'unknown', |
||
192 | 'value' => $val, |
||
193 | 'error' => is_null($errorMsg) ? 'invalid value' : $errorMsg, |
||
194 | ]), |
||
195 | ]); |
||
196 | } |
||
197 | |||
198 | protected function isEmptyValue($val) |
||
199 | { |
||
200 | return is_null($val); |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * @param mixed $val |
||
205 | * |
||
206 | * @return mixed |
||
207 | * |
||
208 | * @throws \frictionlessdata\tableschema\Exceptions\FieldValidationException; |
||
209 | */ |
||
210 | // extending classes should extend this method |
||
211 | // value is guaranteed not to be an empty value, that is handled elsewhere |
||
212 | // should raise FieldValidationException on any validation errors |
||
213 | // can use getValidationException function to get a simple exception with single validation error message |
||
214 | // you can also throw an exception with multiple validation errors manually |
||
215 | abstract protected function validateCastValue($val); |
||
216 | |||
217 | protected function checkConstraints($val) |
||
218 | { |
||
219 | $validationErrors = []; |
||
220 | $allowedValues = $this->getAllowedValues(); |
||
221 | if (!empty($allowedValues) && !$this->checkAllowedValues($allowedValues, $val)) { |
||
222 | $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [ |
||
223 | 'field' => $this->name(), |
||
224 | 'value' => $val, |
||
225 | 'error' => 'value not in enum', |
||
226 | ]); |
||
227 | } |
||
228 | $constraints = $this->constraints(); |
||
229 | View Code Duplication | if (isset($constraints->pattern)) { |
|
0 ignored issues
–
show
|
|||
230 | if (!$this->checkPatternConstraint($val, $constraints->pattern)) { |
||
231 | $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [ |
||
232 | 'field' => $this->name(), |
||
233 | 'value' => $val, |
||
234 | 'error' => 'value does not match pattern', |
||
235 | ]); |
||
236 | } |
||
237 | } |
||
238 | View Code Duplication | if ( |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
239 | isset($constraints->minimum) |
||
240 | && !$this->checkMinimumConstraint($val, $this->castValueNoConstraints($constraints->minimum)) |
||
241 | ) { |
||
242 | $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [ |
||
243 | 'field' => $this->name(), |
||
244 | 'value' => $val, |
||
245 | 'error' => 'value is below minimum', |
||
246 | ]); |
||
247 | } |
||
248 | View Code Duplication | if ( |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
249 | isset($constraints->maximum) |
||
250 | && !$this->checkMaximumConstraint($val, $this->castValueNoConstraints($constraints->maximum)) |
||
251 | ) { |
||
252 | $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [ |
||
253 | 'field' => $this->name(), |
||
254 | 'value' => $val, |
||
255 | 'error' => 'value is above maximum', |
||
256 | ]); |
||
257 | } |
||
258 | View Code Duplication | if ( |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
259 | isset($constraints->minLength) && !$this->checkMinLengthConstraint($val, $constraints->minLength) |
||
260 | ) { |
||
261 | $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [ |
||
262 | 'field' => $this->name(), |
||
263 | 'value' => $val, |
||
264 | 'error' => 'value is below minimum length', |
||
265 | ]); |
||
266 | } |
||
267 | View Code Duplication | if ( |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
268 | isset($constraints->maxLength) && !$this->checkMaxLengthConstraint($val, $constraints->maxLength) |
||
269 | ) { |
||
270 | $validationErrors[] = new SchemaValidationError(SchemaValidationError::FIELD_VALIDATION, [ |
||
271 | 'field' => $this->name(), |
||
272 | 'value' => $val, |
||
273 | 'error' => 'value is above maximum length', |
||
274 | ]); |
||
275 | } |
||
276 | |||
277 | return $validationErrors; |
||
278 | } |
||
279 | |||
280 | protected function checkPatternConstraint($val, $pattern) |
||
281 | { |
||
282 | return preg_match('/^'.$pattern.'$/', $val) === 1; |
||
283 | } |
||
284 | |||
285 | protected function checkMinimumConstraint($val, $minConstraint) |
||
286 | { |
||
287 | return $val >= $minConstraint; |
||
288 | } |
||
289 | |||
290 | protected function checkMaximumConstraint($val, $maxConstraint) |
||
291 | { |
||
292 | return $val <= $maxConstraint; |
||
293 | } |
||
294 | |||
295 | protected function checkMinLengthConstraint($val, $minLength) |
||
296 | { |
||
297 | if (is_string($val)) { |
||
298 | return strlen($val) >= $minLength; |
||
299 | } elseif (is_array($val)) { |
||
300 | return count($val) >= $minLength; |
||
301 | } elseif (is_object($val)) { |
||
302 | return count($val) >= $minLength; |
||
303 | } else { |
||
304 | throw $this->getValidationException('invalid value for minLength constraint', $val); |
||
305 | } |
||
306 | } |
||
307 | |||
308 | protected function checkMaxLengthConstraint($val, $maxLength) |
||
309 | { |
||
310 | if (is_string($val)) { |
||
311 | return strlen($val) <= $maxLength; |
||
312 | } elseif (is_array($val)) { |
||
313 | return count($val) <= $maxLength; |
||
314 | } elseif (is_object($val)) { |
||
315 | return count($val) <= $maxLength; |
||
316 | } else { |
||
317 | throw $this->getValidationException('invalid value for maxLength constraint', $val); |
||
318 | } |
||
319 | } |
||
320 | |||
321 | protected function getAllowedValues() |
||
322 | { |
||
323 | $allowedValues = []; |
||
324 | foreach ($this->enum() as $val) { |
||
325 | $allowedValues[] = $this->castValueNoConstraints($val); |
||
326 | } |
||
327 | |||
328 | return $allowedValues; |
||
329 | } |
||
330 | |||
331 | protected function checkAllowedValues($allowedValues, $val) |
||
332 | { |
||
333 | return in_array($val, $allowedValues, !is_object($val)); |
||
334 | } |
||
335 | |||
336 | protected function castValueNoConstraints($val) |
||
337 | { |
||
338 | $this->disableConstraints(); |
||
339 | $val = $this->castValue($val); |
||
340 | $this->constraintsDisabled = false; |
||
341 | |||
342 | return $val; |
||
343 | } |
||
344 | } |
||
345 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.