Total Complexity | 62 |
Total Lines | 557 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Validator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Validator, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
8 | class Validator |
||
9 | { |
||
10 | /** |
||
11 | * @var array |
||
12 | */ |
||
13 | public $errors = []; |
||
14 | |||
15 | /** |
||
16 | * The data under validation. |
||
17 | * |
||
18 | * @var array |
||
19 | */ |
||
20 | protected $data = []; |
||
21 | |||
22 | /** |
||
23 | * The failed validation rules. |
||
24 | * |
||
25 | * @var array |
||
26 | */ |
||
27 | protected $failedRules = []; |
||
28 | |||
29 | /** |
||
30 | * The rules to be applied to the data. |
||
31 | * |
||
32 | * @var array |
||
33 | */ |
||
34 | protected $rules = []; |
||
35 | |||
36 | /** |
||
37 | * The size related validation rules. |
||
38 | * |
||
39 | * @var array |
||
40 | */ |
||
41 | protected $sizeRules = [ |
||
42 | 'Between', |
||
43 | 'Max', |
||
44 | 'Min', |
||
45 | ]; |
||
46 | |||
47 | /** |
||
48 | * The validation rules that imply the field is required. |
||
49 | * |
||
50 | * @var array |
||
51 | */ |
||
52 | protected $implicitRules = [ |
||
53 | 'Required', |
||
54 | ]; |
||
55 | |||
56 | /** |
||
57 | * The numeric related validation rules. |
||
58 | * |
||
59 | * @var array |
||
60 | */ |
||
61 | protected $numericRules = [ |
||
62 | 'Numeric', |
||
63 | ]; |
||
64 | |||
65 | /** |
||
66 | * Run the validator's rules against its data. |
||
67 | * |
||
68 | * @param mixed $data |
||
69 | * |
||
70 | * @return array |
||
71 | */ |
||
72 | public function validate( $data, array $rules = [] ) |
||
73 | { |
||
74 | $this->normalizeData( $data ); |
||
75 | $this->setRules( $rules ); |
||
76 | |||
77 | foreach( $this->rules as $attribute => $rules ) { |
||
78 | foreach( $rules as $rule ) { |
||
79 | $this->validateAttribute( $rule, $attribute ); |
||
80 | |||
81 | if( $this->shouldStopValidating( $attribute ))break; |
||
82 | } |
||
83 | } |
||
84 | |||
85 | return $this->errors; |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * Add an error message to the validator's collection of errors. |
||
90 | * |
||
91 | * @param string $attribute |
||
92 | * @param string $rule |
||
93 | * |
||
94 | * @return void |
||
95 | */ |
||
96 | protected function addError( $attribute, $rule, array $parameters ) |
||
97 | { |
||
98 | $message = $this->getMessage( $attribute, $rule, $parameters ); |
||
99 | |||
100 | $this->errors[ $attribute ]['errors'][] = $message; |
||
101 | |||
102 | if( !isset( $this->errors[ $attribute ]['value'] )) { |
||
103 | $this->errors[ $attribute ]['value'] = $this->getValue( $attribute ); |
||
104 | } |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * Add a failed rule and error message to the collection. |
||
109 | * |
||
110 | * @param string $attribute |
||
111 | * @param string $rule |
||
112 | * |
||
113 | * @return void |
||
114 | */ |
||
115 | protected function addFailure( $attribute, $rule, array $parameters ) |
||
116 | { |
||
117 | $this->addError( $attribute, $rule, $parameters ); |
||
118 | |||
119 | $this->failedRules[ $attribute ][ $rule ] = $parameters; |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Get the data type of the given attribute. |
||
124 | * |
||
125 | * @param string $attribute |
||
126 | * @return string |
||
127 | */ |
||
128 | protected function getAttributeType( $attribute ) |
||
129 | { |
||
130 | return $this->hasRule( $attribute, $this->numericRules ) |
||
131 | ? 'numeric' |
||
132 | : 'string'; |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * Get the validation message for an attribute and rule. |
||
137 | * |
||
138 | * @param string $attribute |
||
139 | * @param string $rule |
||
140 | * |
||
141 | * @return string|null |
||
142 | */ |
||
143 | protected function getMessage( $attribute, $rule, array $parameters ) |
||
144 | { |
||
145 | if( in_array( $rule, $this->sizeRules )) { |
||
146 | return $this->getSizeMessage( $attribute, $rule, $parameters ); |
||
147 | } |
||
148 | |||
149 | $lowerRule = $this->snakeCase( $rule ); |
||
150 | |||
151 | return $this->translator( $lowerRule, $rule, $attribute, $parameters ); |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Get a rule and its parameters for a given attribute. |
||
156 | * |
||
157 | * @param string $attribute |
||
158 | * @param string|array $rules |
||
159 | * |
||
160 | * @return array|null |
||
161 | */ |
||
162 | protected function getRule( $attribute, $rules ) |
||
163 | { |
||
164 | if( !array_key_exists( $attribute, $this->rules ))return; |
||
165 | |||
166 | $rules = (array) $rules; |
||
167 | |||
168 | foreach( $this->rules[ $attribute ] as $rule ) { |
||
169 | list( $rule, $parameters ) = $this->parseRule( $rule ); |
||
170 | |||
171 | if( in_array( $rule, $rules )) { |
||
172 | return [ $rule, $parameters ]; |
||
173 | } |
||
174 | } |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Get the size of an attribute. |
||
179 | * |
||
180 | * @param string $attribute |
||
181 | * @param mixed $value |
||
182 | * |
||
183 | * @return mixed |
||
184 | */ |
||
185 | protected function getSize( $attribute, $value ) |
||
186 | { |
||
187 | $hasNumeric = $this->hasRule( $attribute, $this->numericRules ); |
||
188 | |||
189 | if( is_numeric( $value ) && $hasNumeric ) { |
||
190 | return $value; |
||
191 | } |
||
192 | elseif( is_array( $value )) { |
||
193 | return count( $value ); |
||
194 | } |
||
195 | |||
196 | return mb_strlen( $value ); |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * Get the proper error message for an attribute and size rule. |
||
201 | * |
||
202 | * @param string $attribute |
||
203 | * @param string $rule |
||
204 | * |
||
205 | * @return string|null |
||
206 | */ |
||
207 | protected function getSizeMessage( $attribute, $rule, array $parameters ) |
||
208 | { |
||
209 | $lowerRule = $this->snakeCase( $rule ); |
||
210 | $type = $this->getAttributeType( $attribute ); |
||
211 | |||
212 | $lowerRule .= ".{$type}"; |
||
213 | |||
214 | return $this->translator( $lowerRule, $rule, $attribute, $parameters ); |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * Get the value of a given attribute. |
||
219 | * |
||
220 | * @param string $attribute |
||
221 | * |
||
222 | * @return mixed |
||
223 | */ |
||
224 | protected function getValue( $attribute ) |
||
225 | { |
||
226 | if( isset( $this->data[ $attribute ] )) { |
||
227 | return $this->data[ $attribute ]; |
||
228 | } |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Determine if the given attribute has a rule in the given set. |
||
233 | * |
||
234 | * @param string $attribute |
||
235 | * @param string|array $rules |
||
236 | * |
||
237 | * @return bool |
||
238 | */ |
||
239 | protected function hasRule( $attribute, $rules ) |
||
240 | { |
||
241 | return !is_null( $this->getRule( $attribute, $rules )); |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Normalize the provided data to an array. |
||
246 | * |
||
247 | * @param mixed $data |
||
248 | * |
||
249 | * @return $this |
||
250 | */ |
||
251 | protected function normalizeData( $data ) |
||
252 | { |
||
253 | // If an object was provided, get its public properties |
||
254 | if( is_object( $data )) { |
||
255 | $this->data = get_object_vars( $data ); |
||
256 | } |
||
257 | else { |
||
258 | $this->data = $data; |
||
259 | } |
||
260 | |||
261 | return $this; |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * Parse a parameter list. |
||
266 | * |
||
267 | * @param string $rule |
||
268 | * @param string $parameter |
||
269 | * |
||
270 | * @return array |
||
271 | */ |
||
272 | protected function parseParameters( $rule, $parameter ) |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Extract the rule name and parameters from a rule. |
||
283 | * |
||
284 | * @param string $rule |
||
285 | * |
||
286 | * @return array |
||
287 | */ |
||
288 | protected function parseRule( $rule ) |
||
289 | { |
||
290 | $parameters = []; |
||
291 | |||
292 | // example: {rule}:{parameters} |
||
|
|||
293 | if( strpos( $rule, ':' ) !== false ) { |
||
294 | list( $rule, $parameter ) = explode( ':', $rule, 2 ); |
||
295 | |||
296 | // example: {parameter1,parameter2,...} |
||
297 | $parameters = $this->parseParameters( $rule, $parameter ); |
||
298 | } |
||
299 | |||
300 | $rule = ucwords( str_replace( ['-', '_'], ' ', trim( $rule ))); |
||
301 | $rule = str_replace( ' ', '', $rule ); |
||
302 | |||
303 | return [ $rule, $parameters ]; |
||
304 | } |
||
305 | |||
306 | /** |
||
307 | * Replace all placeholders for the between rule. |
||
308 | * |
||
309 | * @param string $message |
||
310 | * |
||
311 | * @return string |
||
312 | */ |
||
313 | protected function replaceBetween( $message, array $parameters ) |
||
314 | { |
||
315 | return str_replace([':min', ':max'], $parameters, $message ); |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Replace all placeholders for the max rule. |
||
320 | * |
||
321 | * @param string $message |
||
322 | * |
||
323 | * @return string |
||
324 | */ |
||
325 | protected function replaceMax( $message, array $parameters ) |
||
326 | { |
||
327 | return str_replace( ':max', $parameters[0], $message ); |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Replace all placeholders for the min rule. |
||
332 | * |
||
333 | * @param string $message |
||
334 | * |
||
335 | * @return string |
||
336 | */ |
||
337 | protected function replaceMin( $message, array $parameters ) |
||
338 | { |
||
339 | return str_replace( ':min', $parameters[0], $message ); |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * Require a certain number of parameters to be present. |
||
344 | * |
||
345 | * @param int $count |
||
346 | * @param string $rule |
||
347 | * |
||
348 | * @return void |
||
349 | * @throws InvalidArgumentException |
||
350 | */ |
||
351 | protected function requireParameterCount( $count, array $parameters, $rule ) |
||
352 | { |
||
353 | if( count( $parameters ) < $count ) { |
||
354 | throw new InvalidArgumentException( "Validation rule $rule requires at least $count parameters." ); |
||
355 | } |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Set the validation rules. |
||
360 | * |
||
361 | * @return $this |
||
362 | */ |
||
363 | protected function setRules( array $rules ) |
||
364 | { |
||
365 | foreach( $rules as $key => $rule ) { |
||
366 | $rules[ $key ] = is_string( $rule ) ? explode( '|', $rule ) : $rule; |
||
367 | } |
||
368 | |||
369 | $this->rules = $rules; |
||
370 | |||
371 | return $this; |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Check if we should stop further validations on a given attribute. |
||
376 | * |
||
377 | * @param string $attribute |
||
378 | * |
||
379 | * @return bool |
||
380 | */ |
||
381 | protected function shouldStopValidating( $attribute ) |
||
382 | { |
||
383 | return $this->hasRule( $attribute, $this->implicitRules ) |
||
384 | && isset( $this->failedRules[ $attribute ] ) |
||
385 | && array_intersect( array_keys( $this->failedRules[ $attribute ] ), $this->implicitRules ); |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * Convert a string to snake case. |
||
390 | * |
||
391 | * @param string $string |
||
392 | * |
||
393 | * @return string |
||
394 | */ |
||
395 | protected function snakeCase( $string ) |
||
396 | { |
||
397 | if( !ctype_lower( $string )) { |
||
398 | $string = preg_replace( '/\s+/u', '', $string ); |
||
399 | $string = preg_replace( '/(.)(?=[A-Z])/u', '$1_', $string ); |
||
400 | $string = mb_strtolower( $string, 'UTF-8' ); |
||
401 | } |
||
402 | |||
403 | return $string; |
||
404 | } |
||
405 | |||
406 | /** |
||
407 | * Returns a translated message for the attribute |
||
408 | * |
||
409 | * @param string $key |
||
410 | * @param string $rule |
||
411 | * @param string $attribute |
||
412 | * |
||
413 | * @return string|null |
||
414 | */ |
||
415 | protected function translator( $key, $rule, $attribute, array $parameters ) |
||
416 | { |
||
417 | $strings = glsr_resolve( 'Strings' )->validation(); |
||
418 | |||
419 | $message = isset( $strings[ $key ] ) |
||
420 | ? $strings[ $key ] |
||
421 | : false; |
||
422 | |||
423 | if( !$message )return; |
||
424 | |||
425 | $message = str_replace( ':attribute', $attribute, $message ); |
||
426 | |||
427 | if( method_exists( $this, $replacer = "replace{$rule}" )) { |
||
428 | $message = $this->$replacer( $message, $parameters ); |
||
429 | } |
||
430 | |||
431 | return $message; |
||
432 | } |
||
433 | |||
434 | // Rules Validation |
||
435 | // --------------------------------------------------------------------------------------------- |
||
436 | |||
437 | /** |
||
438 | * Validate that an attribute was "accepted". |
||
439 | * |
||
440 | * This validation rule implies the attribute is "required". |
||
441 | * |
||
442 | * @param mixed $value |
||
443 | * |
||
444 | * @return bool |
||
445 | */ |
||
446 | protected function validateAccepted( $value ) |
||
447 | { |
||
448 | $acceptable = ['yes', 'on', '1', 1, true, 'true']; |
||
449 | |||
450 | return $this->validateRequired( $value ) && in_array( $value, $acceptable, true ); |
||
451 | } |
||
452 | |||
453 | /** |
||
454 | * Validate a given attribute against a rule. |
||
455 | * |
||
456 | * @param string $rule |
||
457 | * @param string $attribute |
||
458 | * |
||
459 | * @return void |
||
460 | * @throws BadMethodCallException |
||
461 | */ |
||
462 | protected function validateAttribute( $rule, $attribute ) |
||
463 | { |
||
464 | list( $rule, $parameters ) = $this->parseRule( $rule ); |
||
465 | |||
466 | if( $rule == '' )return; |
||
467 | |||
468 | $method = "validate{$rule}"; |
||
469 | |||
470 | if( !method_exists( $this, $method )) { |
||
471 | throw new BadMethodCallException( "Method [$method] does not exist." ); |
||
472 | } |
||
473 | |||
474 | if( !$this->$method( $this->getValue( $attribute ), $attribute, $parameters )) { |
||
475 | $this->addFailure( $attribute, $rule, $parameters ); |
||
476 | } |
||
477 | } |
||
478 | |||
479 | /** |
||
480 | * Validate the size of an attribute is between a set of values. |
||
481 | * |
||
482 | * @param mixed $value |
||
483 | * @param string $attribute |
||
484 | * |
||
485 | * @return bool |
||
486 | */ |
||
487 | protected function validateBetween( $value, $attribute, array $parameters ) |
||
488 | { |
||
489 | $this->requireParameterCount( 2, $parameters, 'between' ); |
||
490 | |||
491 | $size = $this->getSize( $attribute, $value ); |
||
492 | |||
493 | return $size >= $parameters[0] && $size <= $parameters[1]; |
||
494 | } |
||
495 | |||
496 | /** |
||
497 | * Validate that an attribute is a valid e-mail address. |
||
498 | * |
||
499 | * @param mixed $value |
||
500 | * |
||
501 | * @return bool |
||
502 | */ |
||
503 | protected function validateEmail( $value ) |
||
504 | { |
||
505 | return filter_var( $value, FILTER_VALIDATE_EMAIL ) !== false; |
||
506 | } |
||
507 | |||
508 | /** |
||
509 | * Validate the size of an attribute is less than a maximum value. |
||
510 | * |
||
511 | * @param mixed $value |
||
512 | * @param string $attribute |
||
513 | * |
||
514 | * @return bool |
||
515 | */ |
||
516 | protected function validateMax( $value, $attribute, array $parameters ) |
||
517 | { |
||
518 | $this->requireParameterCount( 1, $parameters, 'max' ); |
||
519 | |||
520 | return $this->getSize( $attribute, $value ) <= $parameters[0]; |
||
521 | } |
||
522 | |||
523 | /** |
||
524 | * Validate the size of an attribute is greater than a minimum value. |
||
525 | * |
||
526 | * @param mixed $value |
||
527 | * @param string $attribute |
||
528 | * |
||
529 | * @return bool |
||
530 | */ |
||
531 | protected function validateMin( $value, $attribute, array $parameters ) |
||
532 | { |
||
533 | $this->requireParameterCount( 1, $parameters, 'min' ); |
||
534 | |||
535 | return $this->getSize( $attribute, $value ) >= $parameters[0]; |
||
536 | } |
||
537 | |||
538 | /** |
||
539 | * Validate that an attribute is numeric. |
||
540 | * |
||
541 | * @param mixed $value |
||
542 | * |
||
543 | * @return bool |
||
544 | */ |
||
545 | protected function validateNumeric( $value ) |
||
548 | } |
||
549 | |||
550 | /** |
||
551 | * Validate that a required attribute exists. |
||
552 | * |
||
553 | * @param mixed $value |
||
554 | * |
||
555 | * @return bool |
||
556 | */ |
||
557 | protected function validateRequired( $value ) |
||
558 | { |
||
559 | if( is_string( $value )) { |
||
560 | $value = trim( $value ); |
||
561 | } |
||
562 | return is_null( $value ) || empty( $value ) |
||
563 | ? false |
||
564 | : true; |
||
565 | } |
||
566 | } |
||
567 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.