Note: In Kohana 3.1, Validation is handled differently, and the Validate class has been replaced with the Validation class.
The Validate class is used to validate any array of data following given rules. It is mainly used to validate forms $_POST data. The class has a few rules already available to us and allows user made callback functions. The class enables to affect each field of your array its own error message that you can then populate with your forms.
Filters are processed first, then rules, and finally callbacks.
// create a new Validate object using the $_POST variable $post = new Validate($_POST); // combine different arrays $post = new Validate(array_merge($_POST, $_FILES)); // using the factory enables method chaining $post = Validate::factory($_POST)->rule('field_name', 'not_empty') ->rule('field_name2', 'email'); // add an array of rules $rules = array( 'field_name' => array ( 'not_empty' => NULL, 'max_length' => array(32), ), 'field_name2' => array ( 'min_length' => array(4), ), ); $post = Validate::factory($_POST)->rules('field_name', $rules['field_name']) ->rules('field_name2', $rules['field_name2']); // you can also use the $_POST array directly (not recommended) $_POST = new Validate($_POST);
After creating a Validate object you need to add some rules to it. Some of them are defined by the class itself.
Example: These are all equivalent:
$post = new Validate($_POST); $post->rule('username', 'not_empty') ->rule('password', 'not_empty') ->rule('password', 'min_length', array(5)) ->rule('password', 'max_length', array(42)); $post = Validate::factory($_POST)->rule('username', 'not_empty') ->rules( 'password', array( 'min_length' => array(5), 'max_length' => array(42), ) );
Rules are callbacks to functions and work like them but they return a boolean so they can validate the passed data. We can use pre-defined rules by the class, php, or our own rules.
Pre-defined function:
$post = Validate::factory($_POST)->rule('age', 'numeric');
PHP function:
$post = Validate::factory($_POST)->rule('age', 'is_numeric');
User defined function:
$post = Validate::factory($_POST)->rule('age', 'Model_User::check_numeric'); public static function check_numeric($str) { // Get the decimal point for the current locale list($decimal) = array_values(localeconv()); return (bool) preg_match('/^-?[0-9'.$decimal.']++$/D', (string) $str); }
The same rule can be defined to all fields at once by using TRUE as the field name:
//apply the not_empty rule to all fields in $_POST $post = Validate::factory($_POST)->rule(TRUE, 'not_empty');
Filters are processed before validation. You can apply the filter to all fields by using TRUE for the first parameter. Valid filters are any PHP functions that return a string.
You can also pass an array of parameters to the function with the optional third parameter. The functions you used must receive the value to the first parameter of the function call.
Note that Version 3 of Kohana does not have the pre_filter() and post_filter() any longer. This means that you will use filter() for pre-filtering and, if necessary, callbacks for post-filtering.
You can add a filter to all fields by using TRUE as a field name.
// trim username for white spaces $post = Validate::factory($_POST)->filter('username', 'trim'); // pass parameters to the filter function. // will call: htmlspecialchars($_POST['username'], ENT_QUOTES) $post = Validate::factory($_POST)->filter('username', 'htmlspecialchars', array(ENT_QUOTES)); // use a static defined function for filtering all fields $post = Validate::factory($_POST)->filter(TRUE, 'Model_User::myfilter', array($param1, $param2)); class Model_User extends Model_Auth_User { public static function myfilter($value, $param1, $param2) { $value = ... //some code// ... return $value; } } // use filters() to defined an array of filters $post = Validate::factory($_POST)->filters( 'username' => array( 'Model_User::myfilter' => array($param1, $param2), 'trim' => NULL, ) );
Unlike rules, callbacks are used to do some more complex checks on fields and if necessary add errors to Validate object passed as the first argument.
You can add a callback to all fields by using TRUE as a field name.
// call user defined function named reserved_username $post = Validate::factory($_POST)->callback( 'username', 'reserved_username');
$post = Validate::factory($_POST)->callback('username', 'Model_User::mystatic_callback');
$post = Validate::factory($_POST)->callback( 'username', array($this, 'username_available'));
$post = Validate::factory($_POST)->callbacks( 'username', array( 'reserved_username' 'Model_User::mystatic_callback', array($this, 'username_available'), ) );
Examples:
// Validate our $_POST data using a email_change() callback that checks whether the user // can change its email address if the given one is not currently already used by an other user. $post = Validate::factory($_POST)->callback('email', array($this, 'email_change')); /** * Check if user can change its email to this one * * @param Validate $array validate object * @param string $field field name */ public function email_change(Validate $array, $field) { $exists = (bool) DB::select(array('COUNT("*")', 'total_count')) ->from($this->_table_name) ->where('email', '=', $array[$field]) ->where('id', '!=', $this->id) ->execute($this->_db) ->get('total_count'); if ($exists) $array->error($field, 'email_change', array($array[$field])); }
Validating is done by the check() method. It first process the filters, then the rules and last callbacks.
If it encounters any errors on an input field, it adds the field name as an array key to the Validation errors array.
If any error was found, boolean FALSE is returned. if there are no errors, returns TRUE.
#Check that all fields are valid. if ($post->check()) { echo 'No validation errors found '; } else { #Affects errors for further display $this->errors = $post->errors('register'); }
You can add errors with the error() method:
error($field, $error, array $params = NULL)
Example:
$post->error('username', 'username_available', array('johndoe'));
Internationalization files can be found in the messages directories. These directories can be found in system, application or modules directories. Kohana's own messages files can be found in the system directory.
The default key will be used when the error key is not found in the array.
Example: application/messages/register.php
<?php defined('SYSPATH') OR die('No direct access allowed.'); return array ( 'email' => array( 'email_available' => 'The email address already exists', 'default' => 'Invalid Input.', ), 'username' => array( 'username_available' => 'The username already exists', 'default' => 'Invalid Input.', ), ); // end of application/messages/register.php
The error messages can be automatically translated if their value exists in your i18n directory. See Translate messages.
Error messages are retrieved with the errors() method. By default an array is returned, with the field name as key, and the defined rule as value.
To retrieve customized error messages, an error messages file must be passed to the errors() method.
$errors = $validation->errors();
Assuming one rule defined, rule('username', 'username_available') $errors array contains:
array( ('username' => 'username_available') )
$errors = $validation->errors('register')
Assuming a register.php file exists in your messages directory and containing as the one written above. Then $errors will contain:
array( ('username' => 'The username already exists') )
Validation input data is accessible via the as_array() method. This is very useful for re-populating form fields, for example:
$_POST = array_intersect_key( $post->as_array(), $_POST);
| Rule | Parameter | Description | Example |
|---|---|---|---|
| not_empty | No | Returns FALSE if form field is empty | |
| min_length | Yes | Returns FALSE if the field is too short | length[5] - minimum 5 characters long |
| max_length | Yes | Returns FALSE if the field is too long | length[30] - maximum 30 characters long |
| exact_length | Yes | Returns FALSE if the field is too short or too long | length[25] - 25 characters only |
| matches | Yes | Returns FALSE if field does not match field(s) in parameter | matches[password_again] |
| date | No | Returns FALSE if form field is not a valid date | |
| regex | Yes | Returns FALSE if form field does not fulfill the regular expression | regex[expression] - regular expression to match (including delimiters) |
| Optional | Returns FALSE if email is not valid | mail[TRUE] - rfc822 strict |
|
| email_domain | No | Returns FALSE if domain of an email does not have valid MX record | |
| url | No | Returns FALSE if url is not valid | |
| ip | Optional | Returns FALSE if ip is not valid | ip[TRUE] - allow private IP networks |
| credit_card | Yes | Returns FALSE if credit card is not valid | credit_card[mastercard] - card type, or an array of card types |
| phone | Optional | Returns FALSE if phone number is not a valid length | phone[7,10,11,14] - either 7, 10, 11 or 14 digits long (default is 7, 10 and 11) |
| alpha | Optional | Returns FALSE if form field does not consist only of alphabetical characters only | alpha[TRUE] - trigger UTF-8 compatibility |
| alpha_numeric | Optional | Returns FALSE if form field does not consist only of alphabetical or numeric characters | alpha_numeric[TRUE] - trigger UTF-8 compatibility |
| alpha_dash | Optional | Returns FALSE if form field does not consist only of alphabetical, numeric, underscore and dash characters | alpha_dash[TRUE] - trigger UTF-8 compatibility |
| digit | Optional | Returns FALSE if form field does not consist only of digit characters (no dots or dashes). | digit[TRUE] - trigger UTF-8 compatibility |
| numeric | No | Returns FALSE if form field is not a valid number (positive, negative or decimal) | |
| decimal | Optional | Returns FALSE if form field is not in proper decimal format Optional parameter is for a specific decimal format | decimal - is any valid decimal formatdecimal[4,2] - is 4 digits and 2 decimal places |
| range | Yes | Returns FALSE if form field is not withing the range min and max | range[1,10] - between 1 and 10 include |
| color | No | Returns FALSE if form field is not proper hexadecimal HTML color value |
//validate and check a user registration function action_register() { #Instantiate a new user $user = ORM::factory('user'); #Load the validation rules, filters and callbacks $post = $user->validate_create($_POST); #Check that all fields are valid. if ($post->check()) { #Affects the sanitized vars to the user object $user->values($post); #create the account $user->save(); #Add the login role to the user $login_role = new Model_Role(array('name' =>'login')); $user->add('roles',$login_role); #sign the user in Auth::instance()->login($post['username'], $post['password']); #redirect to the user account Request::instance()->redirect('myaccount'); } else { #Repopulate $_POST data $_POST = array_intersect_key( $post->as_array(), $_POST); #Affects errors for further display $this->errors = $post->errors('register'); } }
Our Model_User:
class Model_User extends Model_Auth_User { protected $_rules = array ( 'username' => array ( 'not_empty' => NULL, 'min_length' => array(4), 'max_length' => array(32), 'regex' => array('/^[-\pL\pN_.]++$/uD'), ), 'password' => array ( 'not_empty' => NULL, 'min_length' => array(5), 'max_length' => array(42), ), 'password_confirm' => array ( 'matches' => array('password'), ), 'email' => array ( 'not_empty' => NULL, 'min_length' => array(4), 'max_length' => array(127), 'validate::email' => NULL, ), ); protected $_callbacks = array ( 'username' => array('username_available'), 'email' => array('email_available'), ); public function validate_create($postvalues) { // Initialise the validation library and setup some rules $array = Validate::factory($postvalues) ->rules('password', $this->_rules['password']) ->rules('username', $this->_rules['username']) ->rules('email', $this->_rules['email']) ->rules('password_confirm', $this->_rules['password_confirm']) ->filter('username', 'trim') ->filter('email', 'trim') ->filter('password', 'trim') ->filter('password_confirm', 'trim'); #Add Model_Auth_User callbacks foreach ($this->_callbacks as $field => $callbacks) { foreach ($callbacks as $callback){ $array->callback($field, array($this, $callback)); } } return $array; } public function validate_change($postvalues, $save = FALSE){ // Initialise the validation library and setup some rules $array = Validate::factory($postvalues)->rules('email', $this->_rules['email']) ->filter('email', 'trim') ->filter('password', 'trim') ->callback('email', array($this, 'email_change')); if(trim($array['password']) != '') $array->rules('password', array('min_length'=> array(5), 'max_length'=>array(42))); return $array; } /** * Check if user can change its email to this one * * @param Validate $array validate object * @param string $field field name */ public function email_change(Validate $array, $field) { $exists = (bool) DB::select(array('COUNT("*")', 'total_count')) ->from($this->_table_name) ->where('email', '=', $array[$field]) ->where('id', '!=', $this->id) ->execute($this->_db) ->get('total_count'); if ($exists) $array->error($field, 'email_change', array($array[$field])); } /** * Does the reverse of unique_key_exists() by triggering error if username exists * Validation Rule * * @param Validate $array validate object * @param string $field field name * @param array $errors current validation errors * @return array */ public function username_available(Validate $array, $field) { if ($this->unique_key_exists($array[$field])) { $array->error($field, 'username_available', array($array[$field])); } } /** * Does the reverse of unique_key_exists() by triggering error if email exists * Validation Rule * * @param Validate $array validate object * @param string $field field name * @param array $errors current validation errors * @return array */ public function email_available(Validate $array, $field) { if ($this->unique_key_exists($array[$field])) { $array->error($field, 'email_available', array($array[$field])); } } /** * Tests if a unique key value exists in the database * * @param mixed value the value to test * @return boolean */ public function unique_key_exists($value) { return (bool) DB::select(array('COUNT("*")', 'total_count')) ->from($this->_table_name) ->where($this->unique_key($value), '=', $value) ->execute($this->_db) ->get('total_count'); } /** * Allows a model use both email and username as unique identifiers for login * * @param string $value unique value * @return string field name */ public function unique_key($value) { return Validate::email($value) ? 'email' : 'username'; } }
Here is an example of validating file uploads.
$validate = Validate::factory($_FILES); $validate->rules('file', array('Upload::valid' => array(), 'Upload::not_empty' => array(), 'Upload::type' =>array('Upload::type' => array('jpg','png','gif')), 'Upload::size' => array('1M')) ); if ($validate->check()) { //ok Upload::save($_FILES['file'],'my_image.png','./',0777); } else { //error $this->errors = $validate->errors('upload'); print_r($this->errors); }
application/messages/upload.php
return array ( 'file' => array( 'Upload::valid' => 'valid msg', 'Upload::not_empty' => 'not_empty msg', 'Upload::type' => 'type msg', 'Upload::size' => 'size msg', 'default' => 'default msg'), );