How to use Kohana 3 validation (with forms)

Update: I am gradually updating my Kohana 3.0 articles to Kohana 3.1.

Overview of Kohana 3 validation

Validation is done using the Validation class, or via ORM using $model->check() automatically via exceptions. It uses the messages functionality in KO3, which is a system for specifying validation messages for various forms (I will discuss this).

The steps in validating a form are:

  1. Initialize the Validation class.
  2. Call Validate->check() and check whether the validation succeeded.
  3. If the validation failed, call errors($file) to get the error messages.
  4. Display the validation errors in the correct context on the form.

The first steps are covered by existing documentation, so I will focus on the last two steps here.

Initializing the Validation class

In Kohana 3.1, the Validation class is simply initialized by setting up rules. The class has also changed from Validate (KO 3.0) to Validation (KO 3.1).

Rules. The Validation class has a nice set of basic validation rules, including rules for email addresses, credit cards, URLs and regexps.

KO 3.0 migration notes

KO 3.1 validation no longer has callbacks or filters. Callbacks have been merged into rules, while filters have been removed altogether.

Filters were used to do things like trim() the input in KO 3.0.x – you can do array_map($array, ‘trim’).

Callbacks are now specified using the rule() function, see this excellent tutorial from Lysender for the details!

Some sample code

 

// Create a validation object
$validation = Validation::factory(
   array(
    'email' => 'no-reply@test.com',
    'name' => 'Test'
    );
// Add some basic rules
$validation->rule('name', 'not_empty');
$validation->rule('email', 'email');
// Check the result
$validation->check();
 
// Using a single Kohana_Valid::rule
// for just checking a single field
if(Valid::email('test@test.com')) {
   // do something with the email
}

 

Retrieving and customizing error messages

Validation can be done for both models (on save) and various other forms. Because of this the messages are stored in separate, reusable files. You should put your message files under /application/messages/filename.php.

I think the logic behind this has gotten a bit more complicated for KO 3.1 – in particularly with validation messages from the ORM… have to get back to you on that.

The files look like this:

array(
'username' => array(
   'username_available' => 'This username is already...',
   'not_empty' => 'Username must not be empty.',
   'invalid' => 'Password or username is incorrect.',
),
'password' => array(
   'matches' => 'The password and password confirmation ..',
   'range' => ':field must be within the range of :param1 to :param2',
),
);

Each field is a sub-array and each rule has it’s own field. Note that you can use :field to specify the field name.

To retrieve the messages, use $validate->errors($file) where $file is the filename (no extension).

The return value looks something like:

array(
   'password' => 'password must be less than 42 characters long',
   'password_confirm' => 'The password and password confirmation are different.',
   'username' => 'This username is already registered, please choose another one.',
   'email' => 'This email address is already in use.');

The simplest way to show these is the just print them in as a list. However, I will show one way to show these messages in context on the actual form.

Displaying validation errors in context on the form

Here is how we want to show the forms:

To accomplish this, I have created a new Appform helper which uses the Form class, but wraps its input with application-specific markup for errors.

The basic idea is that you can pass default values, form values and error messages to Appform prior to outputting the fields. It will then create the contextual markup for each of the fields. I have maintained compatibility with the Form API, with additional properties for added functionality.

Note: for Kohana 3.1, you need to move the error messages from _external to the base for Appform to work.

$errors = $e->errors('register');
$errors = array_merge($errors, (isset($errors['_external']) ? $errors['_external'] : array()));
$view->set('errors', $errors);

As a side note, I think this is exactly how the division of labor between framework and application developer should be: the framework should not impose a particular markup on the developer, but to keep application-specific code shorter, the developer should be able to create an extended version of the Form helper for the basic form layout. There are too many different preferred styles of markup, and the framework should not try to guess how you like your forms but rather provide the backend. Kohana gets this right.

Here is how a sample invocation would look like (note that the Appform class is not static, since each form has its own contextual data):

$form = new Appform();
if(isset($defaults)) {
  $form->defaults = $defaults;
}
if(isset($errors)) {
  $form->errors = $errors;
}
if(isset($values)) {
  $form->values = $values;
}
echo $form->open('user/login');
echo '<ul>';
echo '<li>'.$form->label('username', __('Username')).'</li>';
echo $form->input('username', NULL, array('info' => __('You can also log in using your email address instead of your username.')));
echo '<li>'.$form->label('password', __('Password')).'</li>';
echo $form->input('password');
echo '</ul>';
echo $form->submit(NULL, __('Login'));
echo $form->close();

You can download my form validation helper from Bitbucket. It’s only a couple of hundred lines so it is easy to improve – a useful start for building more complex functionality for your app and also to reduce the lines of code needed for each form.

ORM and form validation (still need to update to KO 3.1)

The Kohana ORM provides support for field validation. However, it is very much oriented towards each model having one set of validations. With ORM, the function calls are:

if ( !empty($_POST) && is_numeric($id) ) {
   $model = ORM::factory('user', $id); // create
   $model->values($_POST); // load values to model
   // check() initializes $model->_validate with a Validation object containing the
   // rules, filters and callbacks from Model_User (e.g. $_rules, $_callbacks..)
   if ($model->check()) {
      $model->save(); // save the model
   } else {
      // Load errors. The first param is the path to the
      // message file (e.g. /messages/register.php)
      $content->errors = $user->validate()->errors('register');
   }
}

You may want to do something different, either using a different set of rules or a different set of features! The classic example is user profile editing, where you do not want to force the user to re-enter their password – so you need to exclude the password rules from the Validation check.

There are many different suggestions regarding how you should handle this case. For example, in the unofficial docs they define two functions which each initialize a different validation object. I did this in my KO3 Auth sample implementation, and biakaveron (author/maintainer of KO3 Auth) suggested it is a “WTF” – since ORM already has the properties and methods for initializing a validation object from the given rules.

I would suggest using the following pattern: rely on the ORM $_rules property when possible, and when you need different types of validation (e.g. create, update, change subset of fields), define a checkCreate/checkUpdate/checkXYZ function on your model which loads the alternative set of $_rules and then calls the ORM check() to actually perform the validation.

Here is an example (model function, see also sample code):

public function check_edit() {
   $values = $this->as_array();
   // since removing validation rules is tricky (this is needed to ignore
   // the password),we will just create our own alternate _validate
   // object and store it in the model.
   $this->_validate = Validate::factory($values)
      ->label('username', $this->_labels['username'])
      ->label('email', $this->_labels['email'])
      ->rules('username', $this->_rules['username'])
      ->rules('email', $this->_rules['email']);
   // if the password is set, then validate it
   // Note: the password field is always set if the model was loaded
   // from DB (since there is a DB value for it)
   // So we will check for the password_confirm field instead.
   if(
      isset($values['password_confirm']) &&
      (trim($values['password_confirm']) != '')
   ) {
   $this->_validate
      ->label('password', $this->_labels['password'])
      ->label('password_confirm', $this->_labels['password_confirm'])
      ->rules('password', $this->_rules['password'])
      ->rules('password_confirm', $this->_rules['password_confirm']);
   }
   // Since new versions of Kohana automatically exclude the current user from
   // the uniqueness checks, we no longer need to define our own callbacks.
   foreach ($this->_callbacks as $field => $callbacks) {
      foreach ($callbacks as $callback) {
         if (is_string($callback) AND method_exists($this, $callback)) {
            // Callback method exists in current ORM model
            $this->_validate->callback($field, array($this, $callback));
         } else {
            // Try global function
            $this->_validate->callback($field, $callback);
         }
      }
   }
   return $this->_validate->check();
}

This way you can still keep using the same, or almost identical code e.g. $model->values(), $model->validation->errors() and $model->checkXYZ(), while benefiting from the builtin code.

For example (in the Controller, see also sample code):

if ( !empty($_POST) && is_numeric($id) ) {
   $model = ORM::factory('user', $id); // create
   $model->values($_POST); // load values to model
   // check() initializes $model->_validate with a Validation object containing the
   // rules, filters and callbacks from Model_User (e.g. $_rules, $_callbacks..)
   if ($model->check_edit()) {
      $model->save(); // save the model
   } else {
      // Load errors. The first param is the path to the
      // message file (e.g. /messages/register.php)
      $content->errors = $user->validate()->errors('register');
   }
}

A note about ORM: $_ignored_columns

This field is used to specify fields which will not be saved to the database, but which may be written and accessed in while the model class exists. If you use validation through ORM, then you should specify any additional fields which are part of the model validation here (e.g. fields which will be combined during the actual save, but are transmitted separately).


6 comments


  1. There are no filters in Kohana 3.1

    • Yep, but you can get the same functionality with own callback as rule e.g.:
      function trim_(&$value){
      $value = trim($value);
      }
      $val->rule(‘name’, ‘trim_’, array(‘:value’));

      my problem with it in kohana 3.2 that I cannot get back the validated array from the Validation object on which I used several callbacks.

      I would like something like:
      if( $validation->check() ){
      $validation->getValidatedData();
      }

      any idea how to do this without changing this core class?

Leave a comment

You must be logged in to post a comment.