Completed
Push — master ( d11137...b8c38a )
by Steve
04:25
created

Validator::validateField()   D

Complexity

Conditions 9
Paths 30

Size

Total Lines 44
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 9

Importance

Changes 8
Bugs 1 Features 2
Metric Value
c 8
b 1
f 2
dl 0
loc 44
ccs 23
cts 23
cp 1
rs 4.909
cc 9
eloc 18
nc 30
nop 3
crap 9
1
<?php
2
/**
3
 * @package   Fuel\Validation
4
 * @version   2.0
5
 * @author    Fuel Development Team
6
 * @license   MIT License
7
 * @copyright 2010 - 2013 Fuel Development Team
8
 * @link      http://fuelphp.com
9
 */
10
11
namespace Fuel\Validation;
12
13
use InvalidArgumentException;
14
use LogicException;
15
16
/**
17
 * Main entry point for the validation functionality. Handles registering validation rules and loading validation
18
 * adaptors.
19
 *
20
 * @package Fuel\Validation
21
 * @author  Fuel Development Team
22
 * @since   2.0
23
 *
24
 * @method $this email()
25
 * @method $this ip()
26
 * @method $this matchField(string $matchAgainst)
27
 * @method $this minLength(integer $minLength)
28
 * @method $this maxLength(integer $maxLength)
29
 * @method $this number()
30
 * @method $this numericBetween(integer $min, integer $max)
31
 * @method $this numericMax(integer $max)
32
 * @method $this numericMin(integer $min)
33
 * @method $this regex(string $regex)
34
 * @method $this required()
35
 * @method $this url()
36
 * @method $this date(string $format)
37
 * @method $this type(string $type)
38
 * @method $this enum(array $values)
39
 * @method $this enumMulti(array $values)
40
 * @method $this validator(ValidatableInterface $validator)
41
 */
42
class Validator implements ValidatableInterface
43
{
44
45
	/**
46
	 * Contains a list of fields to be validated
47
	 *
48
	 * @var FieldInterface[]
49
	 */
50
	protected $fields = array();
51
52
	/**
53
	 * Contains a list of any custom validation rules
54
	 *
55
	 * @var string[]
56
	 */
57
	protected $customRules = array();
58
59
	/**
60
	 * @var string[]
61
	 */
62
	protected $messages = array();
63
64
	/**
65
	 * Keeps track of the last field added for magic method chaining
66
	 *
67
	 * @var FieldInterface
68
	 */
69
	protected $lastAddedField;
70
71
	/**
72
	 * Keeps track of the last rule added for message setting
73
	 *
74
	 * @var RuleInterface
75
	 */
76
	protected $lastAddedRule;
77
78
	/**
79
	 * Default namespace to look for rules in when a rule is not known
80
	 *
81
	 * @var string
82
	 */
83
	protected $ruleNamespace = 'Fuel\Validation\Rule\\';
84
85
	/**
86
	 * Adds a rule that can be used to validate a field
87
	 *
88
	 * @param string|FieldInterface $field
89
	 * @param RuleInterface         $rule
90
	 *
91
	 * @return $this
92
	 *
93
	 * @since 2.0
94
	 */
95 19
	public function addRule($field, RuleInterface $rule)
96
	{
97 19
		if (is_string($field))
98 19
		{
99
			try
100
			{
101 11
				$field = $this->getField($field);
102
			}
103 11
			catch (InvalidFieldException $ife)
104
			{
105
				// The field does not exist so create it
106 5
				$this->addField($field);
107 5
				$field = $this->getField($field);
108
			}
109 11
		}
110
111
		// We have a valid field now so add the rule
112 19
		$field->addRule($rule);
113
114 19
		$this->lastAddedRule = $rule;
115
116 19
		return $this;
117
	}
118
119
	/**
120
	 * Adds a new field to the validation object
121
	 *
122
	 * @param string|FieldInterface $field
123
	 * @param string                $label Field name to use in messages, set to null to use $field
124
	 *
125
	 * @return $this
126
	 *
127
	 * @throws InvalidArgumentException
128
	 *
129
	 * @since 2.0
130
	 */
131 22
	public function addField($field, $label = null)
132
	{
133 22
		if (is_string($field))
134 22
		{
135 20
			$field = new Field($field, $label);
136 20
		}
137
138 22
		if ( ! $field instanceof FieldInterface)
139 22
		{
140 1
			throw new InvalidArgumentException('VAL-007: Only FieldInterfaces can be added as a field.');
141
		}
142
143 21
		$this->fields[$field->getName()] = $field;
144 21
		$this->lastAddedField = $field;
145
146 21
		return $this;
147
	}
148
149
	/**
150
	 * Returns the given field
151
	 *
152
	 * @param $name
153
	 *
154
	 * @return FieldInterface
155
	 *
156
	 * @throws InvalidFieldException
157
	 *
158
	 * @since 2.0
159
	 */
160 23
	public function getField($name)
161
	{
162 23
		if ( ! isset($this->fields[$name]))
163 23
		{
164 7
			throw new InvalidFieldException($name);
165
		}
166
167 21
		return $this->fields[$name];
168
	}
169
170
	/**
171
	 * Takes an array of data and validates that against the assigned rules.
172
	 * The array is expected to have keys named after fields.
173
	 * This function will call reset() before it runs.
174
	 *
175
	 * @param array           $data
176
	 * @param ResultInterface $result
177
	 *
178
	 * @return ResultInterface
179
	 *
180
	 * @since 2.0
181
	 */
182 14
	public function run($data, ResultInterface $result = null)
183
	{
184 14
		if ($result === null)
185 14
		{
186 14
			$result = new Result;
187 14
		}
188
189 14
		$result->setResult(true);
190
191 14
		foreach ($this->fields as $fieldName => $rules)
192
		{
193 14
			$fieldResult = $this->validateField($fieldName, $data, $result);
194
195 14
			if ( ! $fieldResult)
196 14
			{
197
				// There was a failure so log it to the result object
198 6
				$result->setResult(false);
199 6
			}
200 14
		}
201
202 14
		return $result;
203
	}
204
205
	/**
206
	 * Takes a field name and an array of data and validates the field against the assigned rules.
207
	 * The array is expected to have keys named after fields.
208
	 * This function will call reset() before it runs.
209
	 *
210
	 * @param string          $field
211
	 * @param array           $data
212
	 * @param ResultInterface $result
213
	 *
214
	 * @return ResultInterface
215
	 *
216
	 * @since 2.0
217
	 */
218 2
	public function runField($field, array $data, ResultInterface $result = null)
219
	{
220 2
		if ($result === null)
221 2
		{
222 2
			$result = new Result;
223 2
		}
224
225 2
		$fieldResult = false;
226
227 2
		if (isset($data[$field]))
228 2
		{
229 1
			$fieldResult = $this->validateField($field, $data, $result);
230 1
		}
231
232
		// Log the result
233 2
		$result->setResult($fieldResult);
234
235 2
		return $result;
236
	}
237
238
	/**
239
	 * Validates a single field
240
	 *
241
	 * @param string          $field
242
	 * @param mixed[]         $data
243
	 * @param ResultInterface $result
244
	 *
245
	 * @return bool
246
	 *
247
	 * @since 2.0
248
	 */
249 15
	protected function validateField($field, $data, ResultInterface $result)
250
	{
251 15
		$value = null;
252
253
		// If there is data, and the data is not empty and not numeric. This allows for strings such as '0' to be passed
254
		// as valid values.
255 15
		$dataPresent = isset($data[$field]) && ! (empty($data[$field]) && ! is_numeric($data[$field]));
256
257
		if ($dataPresent)
258 15
		{
259 13
			$value = $data[$field];
260 13
		}
261
262 15
		$rules = $this->getFieldRules($field);
263
264 15
		foreach ($rules as $rule)
265
		{
266 15
			if ( ! $dataPresent && ! $rule->canAlwaysRun())
267 15
			{
268 4
				continue;
269
			}
270
271 14
			$validateResult = $rule->validate($value, $field, $data);
272
273 14
			if ($validateResult instanceof ResultInterface)
274 14
			{
275 2
				$result->merge($validateResult, $field . '.');
276 2
				return $validateResult->isValid();
277
			}
278
279 14
			if ( ! $validateResult)
280 14
			{
281
				// Don't allow any others to run if this one failed
282 7
				$result->setError($field, $this->buildMessage($this->getField($field), $rule, $value), $rule);
0 ignored issues
show
Documentation introduced by
$rule is of type object<Fuel\Validation\RuleInterface>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
283
284 7
				return false;
285
			}
286 12
		}
287
288
		// All is good so make sure the field gets added as one of the validated fields
289 12
		$result->setValidated($field);
290
291 12
		return true;
292
	}
293
294
	/**
295
	 * Gets a Rule's message and processes that with various tokens
296
	 *
297
	 * @param FieldInterface $field
298
	 * @param RuleInterface  $rule
299
	 *
300
	 * @return string
301
	 */
302 7
	protected function buildMessage(FieldInterface $field, RuleInterface $rule, $value)
303
	{
304
		// Build an array with all the token values
305
		$tokens = array(
306 7
			'name' => $field->getName(),
307 7
			'label' => $field->getLabel(),
308 7
			'value' => $value,
309 7
		) + $rule->getMessageParameters();
310
311 7
		return $this->processMessageTokens($tokens, $rule->getMessage());
312
	}
313
314
	/**
315
	 * Replaces any {} tokens with the matching value from $tokens.
316
	 *
317
	 * @param array $tokens   Associative array of token names and values
318
	 * @param string $message
319
	 *
320
	 * @return string
321
	 *
322
	 * @since 2.0
323
	 */
324 7
	protected function processMessageTokens(array $tokens, $message)
325
	{
326 7
		foreach ($tokens as $token => $value)
327
		{
328 7
			$message = str_replace('{' . $token . '}', $value, $message);
329 7
		}
330
331 7
		return $message;
332
	}
333
334
	/**
335
	 * @param string $fieldName
336
	 *
337
	 * @return RuleInterface[]
338
	 */
339 18
	public function getFieldRules($fieldName)
340
	{
341
		try
342
		{
343 18
			$field = $this->getField($fieldName);
344
		}
345 18
		catch (InvalidFieldException $ife)
346
		{
347
			// No field found so no rules
348 1
			return array();
349
		}
350
351 17
		return $field->getRules();
352
	}
353
354
	/**
355
	 * Allows validation rules to be dynamically added using method chaining.
356
	 *
357
	 * @param string $name
358
	 * @param array  $arguments
359
	 *
360
	 * @return $this
361
	 * @throws InvalidRuleException
362
	 *
363
	 * @since 2.0
364
	 */
365 9
	public function __call($name, $arguments)
366
	{
367
		// Create and then add the new rule to the last added field
368 9
		$rule = $this->createRuleInstance($name, $arguments);
369
370 8
		$this->addRule($this->lastAddedField, $rule);
371
372 8
		return $this;
373
	}
374
375
	/**
376
	 * Sets the failure message for the last added rule
377
	 *
378
	 * @param string $message
379
	 *
380
	 * @return $this
381
	 *
382
	 * @throws LogicException
383
	 *
384
	 * @since 2.0
385
	 */
386 3
	public function setMessage($message)
387
	{
388 3
		if ( ! $this->lastAddedRule)
389 3
		{
390 1
			throw new LogicException('VAL-006: A rule should be added before setting a message.');
391
		}
392
393 2
		$this->lastAddedRule->setMessage($message);
394
395 2
		return $this;
396
	}
397
398
	/**
399
	 * Creates an instance of the given rule name
400
	 *
401
	 * @param string $name
402
	 * @param mixed  $parameters
403
	 *
404
	 * @return RuleInterface
405
	 *
406
	 * @throws InvalidRuleException
407
	 *
408
	 * @since 2.0
409
	 */
410 13
	public function createRuleInstance($name, $parameters = [])
411
	{
412 13
		$className = $this->getRuleClassName($name);
413
414 13
		if ( ! class_exists($className))
415 13
		{
416 2
			throw new InvalidRuleException($name);
417
		}
418
419
		/* @var RuleInterface $instance */
420 11
		$reflection = new \ReflectionClass($className);
421 11
		$instance = $reflection->newInstanceArgs($parameters);
422
423
		// Check if there is a custom message
424 11
		$message = $this->getGlobalMessage($name);
425
426 11
		if ($message !== null)
427 11
		{
428 1
			$instance->setMessage($message);
429 1
		}
430
431 11
		return $instance;
432
	}
433
434
	/**
435
	 * Returns the full class name for the given validation rule
436
	 *
437
	 * @param string $name
438
	 *
439
	 * @return string
440
	 *
441
	 * @since 2.0
442
	 */
443 13
	protected function getRuleClassName($name)
444
	{
445
		// Check if we have a custom rule registered
446 13
		if (isset($this->customRules[$name]))
447 13
		{
448
			// We do so grab the class name from the store
449 3
			return $this->customRules[$name];
450
		}
451
452 10
		return $this->ruleNamespace . ucfirst($name);
453
	}
454
455
	/**
456
	 * Adds custom validation rules and allows for core rules to be overridden.
457
	 * When wanting to override a core rule just specify the rule name as $name.
458
	 * Eg, 'required', 'minLength'. Note the lowercase first letter.
459
	 *
460
	 * The name of the rule should not contain any whitespace or special characters as the name will be available
461
	 * to use as a function name in the method chaining syntax.
462
	 *
463
	 * @param string $name
464
	 * @param string $class
465
	 *
466
	 * @return $this
467
	 *
468
	 * @since 2.0
469
	 */
470 3
	public function addCustomRule($name, $class)
471
	{
472 3
		$this->customRules[$name] = $class;
473
474 3
		return $this;
475
	}
476
477
	/**
478
	 * Sets a custom message for all fields of the given type that are created after the message has been set.
479
	 *
480
	 * @param string      $ruleName Name of the rule to set a message for, eg, required, number, exactLength
481
	 * @param string|null $message  Set to null to disable the custom message
482
	 *
483
	 * @return $this
484
	 *
485
	 * @since 2.0
486
	 */
487 2
	public function setGlobalMessage($ruleName, $message)
488
	{
489 2
		$this->messages[$ruleName] = $message;
490
491 2
		if ($message === null)
492 2
		{
493 1
			$this->removeGlobalMessage($ruleName);
494 1
		}
495
496 2
		return $this;
497
	}
498
499
	/**
500
	 * Sets custom messages for one or more rules. Setting the value to "null" will remove the message
501
	 *
502
	 * @param string[] $messages
503
	 *
504
	 * @return $this
505
	 *
506
	 * @since 2.0
507
	 */
508
	public function setGlobalMessages($messages)
509
	{
510
		foreach ($messages as $name => $value)
511
		{
512
			$this->setGlobalMessage($name, $value);
513
		}
514
515
		return $this;
516
	}
517
518
	/**
519
	 * Removes a global rule message
520
	 *
521
	 * @param string $ruleName
522
	 *
523
	 * @return $this
524
	 *
525
	 * @since 2.0
526
	 */
527 1
	public function removeGlobalMessage($ruleName)
528
	{
529 1
		unset($this->messages[$ruleName]);
530
531 1
		return $this;
532
	}
533
534
	/**
535
	 * Gets the global message set for a rule
536
	 *
537
	 * @param string $ruleName
538
	 *
539
	 * @return null|string Will be null if there is no message
540
	 */
541 12
	public function getGlobalMessage($ruleName)
542
	{
543 12
		if ( ! isset($this->messages[$ruleName]))
544 12
		{
545 11
			return null;
546
		}
547
548 2
		return $this->messages[$ruleName];
549
	}
550
551
}
552