Passed
Push — master ( 6b8ca8...3384db )
by Paul
04:57
created

Validator   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 44
dl 0
loc 309
ccs 0
cts 144
cp 0
rs 8.3396
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A shouldStopValidating() 0 5 3
A parseRule() 0 12 2
A getValue() 0 4 2
A setRules() 0 9 3
A getAttributeType() 0 5 2
A validate() 0 11 4
A normalizeData() 0 6 2
A parseParameters() 0 6 2
A getSize() 0 10 4
A translator() 0 12 4
A addError() 0 6 2
A addFailure() 0 4 1
A hasRule() 0 3 1
B validateAttribute() 0 11 5
A getRule() 0 8 4
A getSizeMessage() 0 6 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\Strings;
0 ignored issues
show
Bug introduced by
The type GeminiLabs\SiteReviews\Strings was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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