Passed
Push — master ( 16abb1...e52611 )
by Paul
04:03
created

Validator   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 311
Duplicated Lines 0 %

Test Coverage

Coverage 12.75%

Importance

Changes 0
Metric Value
eloc 90
dl 0
loc 311
ccs 13
cts 102
cp 0.1275
rs 9.1199
c 0
b 0
f 0
wmc 41

18 Methods

Rating   Name   Duplication   Size   Complexity  
A shouldStopValidating() 0 5 3
A parseRule() 0 9 2
A getValue() 0 4 2
A setRules() 0 8 3
A getAttributeType() 0 5 2
A validate() 0 11 4
A normalizeData() 0 5 2
A parseParameters() 0 6 2
A getSize() 0 10 4
A strings() 0 15 1
A translator() 0 5 2
A addError() 0 4 1
A addFailure() 0 4 1
A hasRule() 0 3 1
A validateAttribute() 0 10 4
A getRule() 0 8 4
A getSizeMessage() 0 5 1
A getMessage() 0 7 2

How to fix   Complexity   

Complex Class

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
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use BadMethodCallException;
6
use GeminiLabs\SiteReviews\Helper;
7
use GeminiLabs\SiteReviews\Modules\Validator\ValidationRules;
8
use InvalidArgumentException;
9
10
/**
11
 * @see \Illuminate\Validation\Validator (5.3)
12
 */
13
class Validator
14
{
15
	use ValidationRules;
16
17
	/**
18
	 * @var array
19
	 */
20
	public $errors = [];
21
22
	/**
23
	 * The data under validation.
24
	 * @var array
25
	 */
26
	protected $data = [];
27
28
	/**
29
	 * The failed validation rules.
30
	 * @var array
31
	 */
32
	protected $failedRules = [];
33
34
	/**
35
	 * The rules to be applied to the data.
36
	 * @var array
37
	 */
38
	protected $rules = [];
39
40
	/**
41
	 * The size related validation rules.
42
	 * @var array
43
	 */
44
	protected $sizeRules = [
45
		'Between', 'Max', 'Min',
46
	];
47
48
	/**
49
	 * The validation rules that imply the field is required.
50
	 * @var array
51
	 */
52
	protected $implicitRules = [
53
		'Required',
54
	];
55
56
	/**
57
	 * The numeric related validation rules.
58
	 * @var array
59
	 */
60
	protected $numericRules = [
61
		'Number',
62
	];
63
64
	/**
65
	 * @return array
66
	 */
67
	public function strings()
68
	{
69
		$strings = [
70
			'accepted' => __( 'This field must be accepted.', 'site-reviews' ),
71
			'between' => __( 'This field value must be between %s and %s.', 'site-reviews' ),
72
			'betweenlength' => __( 'This field must have between %s and %s characters.', 'site-reviews' ),
73
			'email' => __( 'This field requires a valid e-mail address.', 'site-reviews' ),
74
			'max' => __( 'Maximum value for this field is %s.', 'site-reviews' ),
75
			'maxlength' => __( 'This field allows a maximum of %s characters.', 'site-reviews' ),
76
			'min' => __( 'Minimum value for this field is %s.', 'site-reviews' ),
77
			'minlength' => __( 'This field requires a minimum of %s characters.', 'site-reviews' ),
78
			'number' => __( 'This field requires a number.', 'site-reviews' ),
79
			'required' => __( 'This field is required.', 'site-reviews' ),
80
		];
81
		return apply_filters( 'site-reviews/validation/strings', $strings );
82
	}
83
84
	/**
85
	 * Run the validator's rules against its data.
86
	 * @param mixed $data
87
	 * @return array
88
	 */
89 1
	public function validate( $data, array $rules = [] )
90
	{
91 1
		$this->normalizeData( $data );
92 1
		$this->setRules( $rules );
93 1
		foreach( $this->rules as $attribute => $rules ) {
94
			foreach( $rules as $rule ) {
95
				$this->validateAttribute( $attribute, $rule );
96
				if( $this->shouldStopValidating( $attribute ))break;
97
			}
98
		}
99 1
		return $this->errors;
100
	}
101
102
	/**
103
	 * Validate a given attribute against a rule.
104
	 * @param string $attribute
105
	 * @param string $rule
106
	 * @return void
107
	 * @throws BadMethodCallException
108
	 */
109
	public function validateAttribute( $attribute, $rule )
110
	{
111
		list( $rule, $parameters ) = $this->parseRule( $rule );
112
		if( $rule == '' )return;
113
		$value = $this->getValue( $attribute );
114
		if( !method_exists( $this, $method = 'validate'.$rule )) {
115
			throw new BadMethodCallException( "Method [$method] does not exist." );
116
		}
117
		if( !$this->$method( $value, $attribute, $parameters )) {
118
			$this->addFailure( $attribute, $rule, $parameters );
119
		}
120
	}
121
122
	/**
123
	 * Add an error message to the validator's collection of errors.
124
	 * @param string $attribute
125
	 * @param string $rule
126
	 * @return void
127
	 */
128
	protected function addError( $attribute, $rule, array $parameters )
129
	{
130
		$message = $this->getMessage( $attribute, $rule, $parameters );
131
		$this->errors[$attribute][] = $message;
132
	}
133
134
	/**
135
	 * Add a failed rule and error message to the collection.
136
	 * @param string $attribute
137
	 * @param string $rule
138
	 * @return void
139
	 */
140
	protected function addFailure( $attribute, $rule, array $parameters )
141
	{
142
		$this->addError( $attribute, $rule, $parameters );
143
		$this->failedRules[$attribute][$rule] = $parameters;
144
	}
145
146
	/**
147
	 * Get the data type of the given attribute.
148
	 * @param string $attribute
149
	 * @return string
150
	 */
151
	protected function getAttributeType( $attribute )
152
	{
153
		return !$this->hasRule( $attribute, $this->numericRules )
154
			? 'length'
155
			: '';
156
	}
157
158
	/**
159
	 * Get the validation message for an attribute and rule.
160
	 * @param string $attribute
161
	 * @param string $rule
162
	 * @return string|null
163
	 */
164
	protected function getMessage( $attribute, $rule, array $parameters )
165
	{
166
		if( in_array( $rule, $this->sizeRules )) {
167
			return $this->getSizeMessage( $attribute, $rule, $parameters );
168
		}
169
		$lowerRule = glsr( Helper::class )->snakeCase( $rule );
170
		return $this->translator( $lowerRule, $parameters );
171
	}
172
173
	/**
174
	 * Get a rule and its parameters for a given attribute.
175
	 * @param string $attribute
176
	 * @param string|array $rules
177
	 * @return array|null
178
	 */
179
	protected function getRule( $attribute, $rules )
180
	{
181
		if( !array_key_exists( $attribute, $this->rules ))return;
182
		$rules = (array)$rules;
183
		foreach( $this->rules[$attribute] as $rule ) {
184
			list( $rule, $parameters ) = $this->parseRule( $rule );
185
			if( in_array( $rule, $rules )) {
186
				return [$rule, $parameters];
187
			}
188
		}
189
	}
190
191
	/**
192
	 * Get the size of an attribute.
193
	 * @param string $attribute
194
	 * @param mixed $value
195
	 * @return mixed
196
	 */
197
	protected function getSize( $attribute, $value )
198
	{
199
		$hasNumeric = $this->hasRule( $attribute, $this->numericRules );
200
		if( is_numeric( $value ) && $hasNumeric ) {
201
			return $value;
202
		}
203
		elseif( is_array( $value )) {
204
			return count( $value );
205
		}
206
		return mb_strlen( $value );
207
	}
208
209
	/**
210
	 * Get the proper error message for an attribute and size rule.
211
	 * @param string $attribute
212
	 * @param string $rule
213
	 * @return string|null
214
	 */
215
	protected function getSizeMessage( $attribute, $rule, array $parameters )
216
	{
217
		$type = $this->getAttributeType( $attribute );
218
		$lowerRule = glsr( Helper::class )->snakeCase( $rule.$type );
219
		return $this->translator( $lowerRule, $parameters );
220
	}
221
222
	/**
223
	 * Get the value of a given attribute.
224
	 * @param string $attribute
225
	 * @return mixed
226
	 */
227
	protected function getValue( $attribute )
228
	{
229
		if( isset( $this->data[$attribute] )) {
230
			return $this->data[$attribute];
231
		}
232
	}
233
234
	/**
235
	 * Determine if the given attribute has a rule in the given set.
236
	 * @param string $attribute
237
	 * @param string|array $rules
238
	 * @return bool
239
	 */
240
	protected function hasRule( $attribute, $rules )
241
	{
242
		return !is_null( $this->getRule( $attribute, $rules ));
243
	}
244
245
	/**
246
	 * Normalize the provided data to an array.
247
	 * @param mixed $data
248
	 * @return void
249
	 */
250 1
	protected function normalizeData( $data )
251
	{
252 1
		$this->data = is_object( $data )
253
			? get_object_vars( $data )
254 1
			: $data;
255 1
	}
256
257
	/**
258
	 * Parse a parameter list.
259
	 * @param string $rule
260
	 * @param string $parameter
261
	 * @return array
262
	 */
263
	protected function parseParameters( $rule, $parameter )
264
	{
265
		if( strtolower( $rule ) == 'regex' ) {
266
			return [$parameter];
267
		}
268
		return str_getcsv( $parameter );
269
	}
270
271
	/**
272
	 * Extract the rule name and parameters from a rule.
273
	 * @param string $rule
274
	 * @return array
275
	 */
276
	protected function parseRule( $rule )
277
	{
278
		$parameters = [];
279
		if( strpos( $rule, ':' ) !== false ) {
280
			list( $rule, $parameter ) = explode( ':', $rule, 2 );
281
			$parameters = $this->parseParameters( $rule, $parameter );
282
		}
283
		$rule = glsr( Helper::class )->camelCase( $rule );
284
		return [$rule, $parameters];
285
	}
286
287
	/**
288
	 * Set the validation rules.
289
	 * @return void
290
	 */
291 1
	protected function setRules( array $rules )
292
	{
293 1
		foreach( $rules as $key => $rule ) {
294
			$rules[$key] = is_string( $rule )
295
				? explode( '|', $rule )
296
				: $rule;
297
		}
298 1
		$this->rules = $rules;
299 1
	}
300
301
	/**
302
	 * Check if we should stop further validations on a given attribute.
303
	 * @param string $attribute
304
	 * @return bool
305
	 */
306
	protected function shouldStopValidating( $attribute )
307
	{
308
		return $this->hasRule( $attribute, $this->implicitRules )
309
			&& isset( $this->failedRules[$attribute] )
310
			&& array_intersect( array_keys( $this->failedRules[$attribute] ), $this->implicitRules );
311
	}
312
313
	/**
314
	 * Returns a translated message for the attribute
315
	 * @param string $key
316
	 * @param string $attribute
317
	 * @return void|string
318
	 */
319
	protected function translator( $key, array $parameters )
320
	{
321
		$strings = $this->strings();
322
		if( isset( $strings[$key] )) {
323
			return $this->replace( $strings[$key], $parameters );
324
		}
325
	}
326
}
327
328
		// 'between.numeric' => __( 'This field value must be between %s and %s.', 'site-reviews' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
329
		// 'between.string' => __( 'This field length must be between %s and %s characters.', ':min, and :max are placeholders and should not be translated.', 'site-reviews' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
330
		// 'max.numeric' => __( 'Maximum value for this field is %s.', 'site-reviews' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
331
		// 'max.string' => __( 'This field length must be < %s characters.', 'site-reviews' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
332
		// 'min.numeric' => __( 'Minimum value for this field is %s.', 'site-reviews' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
333
		// 'min.string' => __( 'This field length must be > %s characters.', 'site-reviews' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
334
		// 'regex' => __( 'The format is invalid.', 'site-reviews' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
335