Added Route::$cache flag, fixes #3212. Includes tests.
[kohana-core.git] / classes / kohana / validation.php
blobfcacd933007da32c477ea56404cd7610e694ad56
1 <?php defined('SYSPATH') or die('No direct script access.');
2 /**
3 * Array and variable validation.
5 * @package Kohana
6 * @category Security
7 * @author Kohana Team
8 * @copyright (c) 2008-2010 Kohana Team
9 * @license http://kohanaframework.org/license
11 class Kohana_Validation extends ArrayObject {
13 /**
14 * Creates a new Validation instance.
16 * @param array array to use for validation
17 * @return Validation
19 public static function factory(array $array)
21 return new Validation($array);
24 // Bound values
25 protected $_bound = array();
27 // Field rules
28 protected $_rules = array();
30 // Field labels
31 protected $_labels = array();
33 // Rules that are executed even when the value is empty
34 protected $_empty_rules = array('not_empty', 'matches');
36 // Error list, field => rule
37 protected $_errors = array();
39 /**
40 * Sets the unique "any field" key and creates an ArrayObject from the
41 * passed array.
43 * @param array array to validate
44 * @return void
46 public function __construct(array $array)
48 parent::__construct($array, ArrayObject::STD_PROP_LIST);
51 /**
52 * Copies the current rule to a new array.
54 * $copy = $array->copy($new_data);
56 * @param array new data set
57 * @return Validation
58 * @since 3.0.5
60 public function copy(array $array)
62 // Create a copy of the current validation set
63 $copy = clone $this;
65 // Replace the data set
66 $copy->exchangeArray($array);
68 return $copy;
71 /**
72 * Returns the array representation of the current object.
74 * @return array
76 public function as_array()
78 return $this->getArrayCopy();
81 /**
82 * Sets or overwrites the label name for a field.
84 * @param string field name
85 * @param string label
86 * @return $this
88 public function label($field, $label)
90 // Set the label for this field
91 $this->_labels[$field] = $label;
93 return $this;
96 /**
97 * Sets labels using an array.
99 * @param array list of field => label names
100 * @return $this
102 public function labels(array $labels)
104 $this->_labels = $labels + $this->_labels;
106 return $this;
110 * Overwrites or appends rules to a field. Each rule will be executed once.
111 * All rules must be string names of functions method names.
113 * // The "username" must not be empty and have a minimum length of 4
114 * $validation->rule('username', 'not_empty')
115 * ->rule('username', 'min_length', array(4));
117 * @param string field name
118 * @param callback valid PHP callback
119 * @param array extra parameters for the rule
120 * @return $this
122 public function rule($field, $rule, array $params = NULL)
124 if ($params === NULL)
126 // Default to array(':value')
127 $params = array(':value');
130 if ($field !== TRUE AND ! isset($this->_labels[$field]))
132 // Set the field label to the field name
133 $this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field);
136 // Store the rule and params for this rule
137 $this->_rules[$field][] = array($rule, $params);
139 return $this;
143 * Add rules using an array.
145 * @param string field name
146 * @param array list of callbacks
147 * @return $this
149 public function rules($field, array $rules)
151 foreach ($rules as $rule)
153 $this->rule($field, $rule[0], Arr::get($rule, 1));
156 return $this;
160 * Bind a value to a parameter definition.
162 * // This allows you to use :model in the parameter definition of rules
163 * $validation->bind(':model', $model)
164 * ->rule('status', 'valid_status', array(':model'));
166 * @param string variable name or an array of variables
167 * @param mixed value
168 * @return $this
170 public function bind($key, $value = NULL)
172 if (is_array($key))
174 foreach ($key as $name => $value)
176 $this->_bound[$name] = $value;
179 else
181 $this->_bound[$key] = $value;
184 return $this;
188 * Executes all validation rules. This should
189 * typically be called within an if/else block.
191 * if ($validation->check())
193 * // The data is valid, do something here
196 * @param boolean allow empty array?
197 * @return boolean
199 public function check()
201 if (Kohana::$profiling === TRUE)
203 // Start a new benchmark
204 $benchmark = Profiler::start('Validation', __FUNCTION__);
207 // New data set
208 $data = $this->_errors = array();
210 // Get a list of the expected fields
211 $expected = array_keys($this->_labels);
213 // Import the rules locally
214 $rules = $this->_rules;
216 foreach ($expected as $field)
218 if (isset($this[$field]))
220 // Use the submitted value
221 $data[$field] = $this[$field];
223 else
225 // No data exists for this field
226 $data[$field] = NULL;
229 if (isset($rules[TRUE]))
231 if ( ! isset($rules[$field]))
233 // Initialize the rules for this field
234 $rules[$field] = array();
237 // Append the rules
238 $rules[$field] = array_merge($rules[$field], $rules[TRUE]);
242 // Overload the current array with the new one
243 $this->exchangeArray($data);
245 // Remove the rules that apply to every field
246 unset($rules[TRUE]);
248 // Bind the validation object to :validation
249 $this->bind(':validation', $this);
251 // Execute the rules
253 foreach ($rules as $field => $set)
255 // Get the field value
256 $value = $this[$field];
258 // Bind the field name and value to :field and :value respectively
259 $this->bind(array
261 ':field' => $field,
262 ':value' => $value,
265 foreach ($set as $array)
267 // Rules are defined as array($rule, $params)
268 list($rule, $params) = $array;
270 if ( ! in_array($rule, $this->_empty_rules) AND ! Valid::not_empty($value))
272 // Skip this rule for empty fields
273 continue;
276 foreach ($params as $key => $param)
278 if (is_string($param) AND array_key_exists($param, $this->_bound))
280 // Replace with bound value
281 $params[$key] = $this->_bound[$param];
285 if (is_array($rule) OR ! is_string($rule))
287 // This is either a callback as an array or a lambda
288 $passed = call_user_func_array($rule, $params);
290 elseif (method_exists('Valid', $rule))
292 // Use a method in this object
293 $method = new ReflectionMethod('Valid', $rule);
295 // Call static::$rule($this[$field], $param, ...) with Reflection
296 $passed = $method->invokeArgs(NULL, $params);
298 elseif (strpos($rule, '::') === FALSE)
300 // Use a function call
301 $function = new ReflectionFunction($rule);
303 // Call $function($this[$field], $param, ...) with Reflection
304 $passed = $function->invokeArgs($params);
306 else
308 // Split the class and method of the rule
309 list($class, $method) = explode('::', $rule, 2);
311 // Use a static method call
312 $method = new ReflectionMethod($class, $method);
314 // Call $Class::$method($this[$field], $param, ...) with Reflection
315 $passed = $method->invokeArgs(NULL, $params);
318 if ($passed === FALSE)
320 // Add the rule to the errors
321 $this->error($field, $rule, $params);
323 // This field has an error, stop executing rules
324 break;
329 if (isset($benchmark))
331 // Stop benchmarking
332 Profiler::stop($benchmark);
335 return empty($this->_errors);
339 * Add an error to a field.
341 * @param string field name
342 * @param string error message
343 * @return $this
345 public function error($field, $error, array $params = NULL)
347 $this->_errors[$field] = array($error, $params);
349 return $this;
353 * Returns the error messages. If no file is specified, the error message
354 * will be the name of the rule that failed. When a file is specified, the
355 * message will be loaded from "field/rule", or if no rule-specific message
356 * exists, "field/default" will be used. If neither is set, the returned
357 * message will be "file/field/rule".
359 * By default all messages are translated using the default language.
360 * A string can be used as the second parameter to specified the language
361 * that the message was written in.
363 * // Get errors from messages/forms/login.php
364 * $errors = $Validation->errors('forms/login');
366 * @uses Kohana::message
367 * @param string file to load error messages from
368 * @param mixed translate the message
369 * @return array
371 public function errors($file = NULL, $translate = TRUE)
373 if ($file === NULL)
375 // Return the error list
376 return $this->_errors;
379 // Create a new message list
380 $messages = array();
382 foreach ($this->_errors as $field => $set)
384 list($error, $params) = $set;
386 // Get the label for this field
387 $label = $this->_labels[$field];
389 if ($translate)
391 if (is_string($translate))
393 // Translate the label using the specified language
394 $label = __($label, NULL, $translate);
396 else
398 // Translate the label
399 $label = __($label);
403 // Start the translation values list
404 $values = array(
405 ':field' => $label,
406 ':value' => $this[$field],
409 if (is_array($values[':value']))
411 // All values must be strings
412 $values[':value'] = implode(', ', Arr::flatten($values[':value']));
415 if ($params)
417 foreach ($params as $key => $value)
419 if (is_array($value))
421 // All values must be strings
422 $value = implode(', ', Arr::flatten($value));
425 // Check if a label for this parameter exists
426 if (isset($this->_labels[$value]))
428 // Use the label as the value, eg: related field name for "matches"
429 $value = $this->_labels[$value];
431 if ($translate)
433 if (is_string($translate))
435 // Translate the value using the specified language
436 $value = __($value, NULL, $translate);
438 else
440 // Translate the value
441 $value = __($value);
446 // Add each parameter as a numbered value, starting from 1
447 $values[':param'.($key + 1)] = $value;
451 if ($message = Kohana::message($file, "{$field}.{$error}"))
453 // Found a message for this field and error
455 elseif ($message = Kohana::message($file, "{$field}.default"))
457 // Found a default message for this field
459 elseif ($message = Kohana::message($file, $error))
461 // Found a default message for this error
463 elseif ($message = Kohana::message('validation', $error))
465 // Found a default message for this error
467 else
469 // No message exists, display the path expected
470 $message = "{$file}.{$field}.{$error}";
473 if ($translate)
475 if (is_string($translate))
477 // Translate the message using specified language
478 $message = __($message, $values, $translate);
480 else
482 // Translate the message using the default language
483 $message = __($message, $values);
486 else
488 // Do not translate, just replace the values
489 $message = strtr($message, $values);
492 // Set the message for this field
493 $messages[$field] = $message;
496 return $messages;
499 } // End Validation