Alternate Advanced Validation

This tutorial is also based on a couple of posts I made in my blog. The code below has been updated to work with CakePHP 1.x

Introduction

This method of validation allows you to assign multiple validators to each field in the model and to display the error messages in one chunk. This has made my life a lot easier, as you don’t have to think about copy-and-pasting the validator messages to each page that manipulates the model. It also adds a JavaScript validator function that takes the regular expressions and generates javascript validation functions, reducing server roundtrips for simple validation.

The modification

Create a new app_model.php file in the /apps directory (if you haven’t already) and drop in the following function:

function invalidFields ($data = array()) 
{
	if (empty($data)) {
		$data = $this->data;
	}
 
	if (!$this->beforeValidate()) {
		return false;
	}
 
	if (!isset($this->validate)) {
		return true;
	}
 
	
	if (!empty($data)) {
		$data = $data;
	} elseif (isset($this->data)) {
		$data = $this->data;
	}
 
	if (isset($data[$this->name])) {
		$data = $data[$this->name];
	}
	
	$errors = array();
	foreach($this->validate as $field_name => $validators) {
		foreach($validators as $validator) {
			if (isset($data[$field_name]) && !preg_match($validator['expression'], $data[$field_name])) {
				$errors[$field_name] = $validator['message'];
			}
		}
	}
	$this->validationErrors = $errors;
	return $errors;
}

Now this does change the syntax of the validators in the model slighty. In this example, I want to validate an Australia postcode (four numbers) for validility and existance and make sure that the field username is entered:

var $validate = array(
	'post_code' => array(
		array('expression' => VALID_NOT_EMPTY, 'message' => 'You have not entered a post code'),
		array('expression' => '/\d{4}/', 'message' => 'A postcode must be four digits')
	),
	'username' => array(
		array('expression' => VALID_NOT_EMPTY, 'message' => 'You must enter a username')
	)
    );

Basically, it is an associative array of validators, where the key is the name of the field. Each element in that array is another array of validators. The validators themselves are associative arrays with two keys:

expression: Which is the regular expression that is being tested; and message: Which is the message to display is the test fails.

That is enough to set the error variables. Note: only one error message is saved at a time, in decending order.

Adding block error messages

The next code snippet is a helper which allow you to display a block of errors at the top of the page on failure in one line of code!

Create a file called validators.php in app/views/helpers and drop in the following code:

<?php
	/**
  	 * Error string short short error message.
  	 */
	define ('SHORT_ERROR_MESSAGE', '<div class="error_message">%s</div>');
	
	class ValidatorsHelper extends Helper {
				function tagErrorMessages() {
			$messages = "";
			if(isset($this->validationErrors)) {
				foreach($this->validationErrors as $tag) {
					
						foreach($tag as $element => $message) {
							$messages .= sprintf(SHORT_ERROR_MESSAGE, empty($message) ? 'Error in field: ' . $element : $message);
						}
					
				}
			}
			return $messages;
		}
		
		function javascriptErrors($modelNames) {
			if(!is_array($modelNames)) {
				$modelNames = array($modelNames);
			}
			$scriptTags  	 = "function validate(form) {\n";
			$scriptTags		.= "	errors = new Array();\n";
			$scriptTags		.= "	tags = form.getElementsByTagName('input');\n";
			$scriptTags		.= "	for(i = 0; i < tags.length; i++) {\n";
			
			foreach($modelNames as $modelName) {
				$model = new $modelName();
			
				foreach ($model->validate as $field_name => $validators)
				{
					foreach($validators as $validator) {
						$scriptTags	.= "		if(tags[i].name != null && tags[i].name == 'data[" . $model->name . "][" . $field_name . "]') {\n";
						$scriptTags .= "			if(!tags[i].value.match(" . $validator['expression'] . ")) {\n";
						$scriptTags .= "				errors.push('" . $validator['message'] . "');\n";
						$scriptTags .= "			}\n";
						$scriptTags .= "		}\n";
	
					}
				}
			}
			$scriptTags 	.= "	}\n";			
			$scriptTags 	.= "	return errors;\n";
			$scriptTags 	.= "}\n";
			
			return $scriptTags;
		}
	}
?>

Make sure you include the Validator helper in your helper array, then you can call the following function in views under that controller:

<?php echo $validators->tagErrorMessages() ?>

Which will generate a list of errors in a div called error_message.

Adding Javascript validation

You will notice another function in that helper called javascriptErrors(). This function will spit out javascript function called validate which takes a form as a parameter. This function will run all of the model tests client side, avoiding a roundtrip to the server! The javascriptErrors function takes a parameter which can be either a string or array of string which represent the models you wish to validate for.

Note that it doesn’t directly display the errors, it returns an array of error strings. You can then do what you like with it. I personally hate JavaScript alert boxes, so I use DHTML to display the errors - that way it looks exactly the same as if there was a roundtrip.

With in JavaScript tags call the validator function:

echo $validators->javascriptErrors('modelName');

then add the following code:

function validateForm(form) {
	var errors = validate(form);
	
	if(errors.length == 0) {
		return true;
	} else {
		var str = "";
		for(i = 0; i < errors.length; i++) {
			str += "<div class=\"error_message\">" + errors[i] + "</div>\n";
		}
		
		document.getElementById('errors').innerHTML = str;
		return false;
	}
}

And finally, add a onSubmit listener to the form:

<form action="post" method="<?php echo $html->url() ?>" onSubmit="return validateForm(this)">
	<!-- Form gets generated here -->
</form>
<html>
 

Hope someone finds this useful!

Myles Eftos 2006/03/15 22:18

 
tutorials/alternate_advanced_validation.txt · Last modified: 2006/07/22 13:42 by kropotkin999