help  namespaces  categories  help  rss feeds

Validation

<< Back to Dashboard

Contents

ColdBox Object & Form Validation Engine

Covers up to version 3.7.0

Intro

ColdBox sports its own server side validation engine so it can provide you with a unifed approach to object and form validation. We have based it on several intefaces, so you can also use any validation framework in your ColdBox applications as long as they implement the required interfaces (See http://apidocs.coldbox.org/). Hyrule by Dan Vega is the easiest to port as it was the framework that inspired us. ColdBox Validation is not supported on CF 8. You'll need to have CF 9 and up or Railo.

ColdBox validation is based on a way to declaratively specify validation rules for properties or fields in an object or form. The constraints can exist inside of the target object or you can define object and form constraints in your ColdBox configuration file so you can reuse validation constraints or as we call them: shared constraints.

Configuration

In your ColdBox.cfc Configuration File you can declare the following validation structure to configure the validation engine:

validation = {
	manager = "class path",
  	sharedConstraints = {
    	sharedName = { constraints }
  	}
}
Key Type Required Default Description
manager instantiation path or WireBox ID false coldbox.system.validation.ValidationManager You can override the default ColdBox validation manager with your own implementation. Just use an instantiation path or a valid WireBox object id.
sharedConstraints struct false {} This structure will hold all of your shared constraints for forms or/and objects.

ColdBox will also create a mapping to your validation manager with the id: WireBoxValidationManager, so you can easily retrieve it.

Declaring Constraints

You can define constraints in several locations:

  1. Configuration file
  2. Inside domain object
  3. A-la-carte via event handlers

Configuration File

You can optionally register shared constraints in your configuration file. This means you register them with a unique name of your choice and its value is a collection of constraints for properties in your objects or form. Later on you will reference the key name in your handlers or wherever in order to validate the object or form. Here is an example:

validation = {
	sharedConstraints = {
		sharedUser = {
			fName = {required=true},
			lname = {required=true},
			age   = {required=true, max=18 }
			metadata = {required=false, type="json"}
		},
		loginForm = {
			username = {required=true}, password = {required=true}
		},
		changePasswordForm = {
			password = {required=true,min=6}, password2 = {required=true, sameAs="password", min=6}
		}
	}
}

As you can see, our constraints definition describes the set of rules for a property on ANY target object or form.

Domain Object

Within any domain object you can define a public variable called constraints that is a assigned an implicit structure of validation rules for any fields or properties in your object:

component persistent="true"{
	
	// Object properties
	property name="id" fieldtype="id" generator="native" setter="false";
	property name="fname";
	property name="lname";
	property name="email";
	property name="username";
	property name="password";
	property name="age";

	// Validation
	this.constraints = {
		// Constraints go here
	}
}

We can then create the validation rules for the properties it will apply to it:

component persistent="true"{
	 
	...

	// Validation
	this.constraints = {
		fname = { required = true },
		lname = { required = true},
		username = {required=true, size=6..10},
		password = {required=true, size=6..8},
		email = {required=true, type="email"},
		age = {required=true, type="numeric", min=18}
	};
}

That easy! You can just declare these validation rules and ColdBox will validate your properties according to the rules. In this case you can see that a password must be between 6 and 10 characters long, and it cannot be blank.

By default all properties are of type string and not required

Valid Constraints

Below are all the currently supported constraints:

propertyName = {
	// required field or not, includes null values
	required : boolean [false],
	
	// specific type constraint, one in the list.
	type  : (ssn,email,url,alpha,boolean,date,usdate,eurodate,numeric,GUID,UUID,integer,string,telephone,zipcode,ipaddress,creditcard,binary,component,query,struct,json,xml),

	// size or length of the value which can be a (struct,string,array,query)
	size  : numeric or range, eg: 10 or 6..8
	
	// range is a range of values the property value should exist in
	range : eg: 1..10 or 5..-5
	
	// regex validation
	regex : valid no case regex
	
	// same as another property
	sameAs : propertyName
	
	// same as but with no case
	sameAsNoCase : propertyName
	
	// value in list
	inList : list

	// value is unique in the database via the ORM object ONLY
	unique : true
	
	// discrete math modifiers
	discrete : (gt,gte,lt,lte,eq,neq):value
	
	// UDF to use for validation, must return boolean accept the incoming value and target object, validate(value,target):boolean
	udf = variables.UDF or this.UDF or a closure.
	
	// Validation method to use in the target object must return boolean accept the incoming value and target object 
	method : methodName
	
	// Custom validator, must implement coldbox.system.validation.validators.IValidator
	validator : path or wirebox id, example: 'mypath.MyValidator' or 'id:MyValidator'
	
	// min value
	min : value
	
	// max value
	max : value
}

Here is a little reference table for you:

Constraint Type Default Description
required boolean false Whether the property must have a non-null value
type string string Validates that the value is of a certain format type. Our included types are: ssn,email,url,alpha,boolean,date,usdate,eurodate,numeric,GUID,UUID,integer,string,telephone,zipcode,ipaddress,creditcard,binary,component,query,struct,json,xml
size numeric or range --- The size or length of the value which can be a struct, string, array, or query. The value can be a single numeric value or our cool ranges. Ex: size=4, size=6..8, size=-5..0
range range --- Range is a range of values the property value should exist in. Ex: range=1..10, range=6..8
regex regular expression --- The regular expression to try and match the value with for validation. This is a no case regex check.
sameAs propertyName --- Makes sure the value of the constraint is the same as the value of another property in the object. This is a case sensitive check.
sameAsNoCase propertyName --- Makes sure the value of the constraint is the same as the value of another property in the object with no case sensitivity.
inList string list --- A list of values that the property value must exist in
discrete string --- Do discrete math in the property value. The valid values are: eq,neq,lt,lte,gt,gte. Example: discrete="eq:4" or discrete="lte:10"
udf UDF or closure --- I can do my own custom validation by doing an inline closure (CF 10 or Railo only) or a pointer to a custom defined function. The function must return boolean and accepts two parameters: value and target.
method method name --- The name of a method to call in the target object for validation. The function must return boolean and accepts two parameters: value and target.
min numeric --- The value must be greater than or equal to this minimum value
max numeric --- The value must be less than or equal to this maximum value
unique boolean --- The value must be unique in the ORM object table. Please make sure the name of the property is enclosed in double quotes as the property case has to be the same. e.g. "username"
validator instantiation path or wirebox DSL --- You can also build your own validators instead of our internal ones. This value will be the instantiation path to the validator or a wirebox id string. Example: validator="mymodel.validators.MyValidator", validator="id:MyValidator"

Unique Constraints

Important : If you will be using the unique constraint, then the name of the property has to be EXACTLY the same case as the constraint name. To do this, use single or double quotes to declare the constraint name. Please see example below.

this.constraints = {
  "username" = { required=true, unique=true },
  "email" = { required=true, unique=true }
};

Constraint Custom Messages

By default if a constraint fails an error message will be set in the result objects for you in English. If you would like to have your own custom messages for specific constraints you can do so by following the constraint message convention:

{constraintName}Message = "My Custom Message";

Just add the name of the constraint you like and append to it the work Message and you are ready to roll:

username = { required="true", requiredMessage="Please enter the username", size="6-8", sizeMessage="The username must be between 6 to 8 characters" }

Custom Message Replacements

We also setup lots of global {Key} replacements for your messages and also several that the core constraint validators offer as well. This is great for adding these customizations on your custom messages and also your i18n messages (Keep Reading):

Global {} replacements

  • rejectedValue - The rejected value
  • field or property - The property or field that was validated
  • validationType - The name of the constraint validator
  • validationData - The value of the constraint definition, e.g size=5..10, then this value is 5..10
  • targetName - The name of the user, shared constraint or form

Validator {} replacements

  • DiscreteValidator = operation, operationValue
  • InListValidator = inList
  • MaxValidator = max
  • MinValidator = min
  • RangeValidator = range, min, max
  • RegexValidator = regex
  • SameAsValidator, SameAsNoCaseValidator = sameas
  • SizeValidator = size, min, max
  • TypeValidator = type
username = { required="true", requiredMessage="Please enter the {field}", size="6-8", sizeMessage="The username must be between {min} and {max} characters" }

Validating Constraints

Most likely you will be validating your objects at the controller layer in your ColdBox event handlers. All event handlers, plugins and interceptors have some new methods thanks to our framework super type (See API Docs).

getValidationManager()
validateModel( target, [fields="*"], [constraints=""], [locale=""], [excludeFields=""] )
Constraint Type Default Description
target any --- The object to validate or a structure of name-value pairs to validate, this can be the request collection or the private collection or any structure you would like to validate.
fields string * By default it will validate all the fields in the constraints structure or you can validate only certain fields.
excludeFields string --- The fields to exclude in the validation
constraints string or structure empty Optional constraints to validate the form or object with. By default it look in the object for the constraints. You can pass a shared name (from your configuration file) or a structure of constraints.
locale string empty The optional i18n locale to validate with. You can retrieve the current user's locale via getFWLocale(). All the validation messages will be taken from your resource bundles, if defined!

You pass in your target object or structure, an optional list of fields or properties to validate only (by default it does all of them), an an optional constraints argument which can be the shared name or an actual constraints structure a-la-carte. If no constraints are passed, then we will look for the constraints in the target object as a public property called 'constraints'. The validateModel() method returns coldbox.system.validation.result.IValidationResult type object, which you can then use for validation.

function saveUser(event,rc,prc){
	// create and populate a user object from an incoming form
	var user = populateModel( entityNew("User") );
	// validate model
	prc.validationResults = validateModel( user );

	if( prc.validationResults.hasErrors() ){
		getPlugin("MessageBox").error( messageArray=prc.validationResults.getAllErrors() );
	}
	else{
		// save
	}
	
}

The return of populate model is our results interface which has cool methods like:

import coldbox.system.validation.result.*;
interface{

	/**
	* Add errors into the result object
	* @error.hint The validation error to add into the results object
	*/
	IValidationResult function addError(required IValidationError error);
	
	/**
	* Set the validation target object name
	*/
	IValidationResult function setTargetName(required string name);
	
	/**
	* Get the name of the target object that got validated
	*/
	string function getTargetName();
	
	/**
	* Get the locale
	*/
	string function getLocale();
	
	/**
	* has locale information
	*/
	boolean function hasLocale();
	
	/**
	* Set the validation locale
	*/
	IValidationResult function setLocale(required string locale);
	
	
	/**
	* Determine if the results had error or not
	* @field.hint The field to count on (optional)
	*/
	boolean function hasErrors(string field);
	
	/**
	* Clear All errors
	*/
	IValidationResult function clearErrors();
	
	
	/**
	* Get how many errors you have
	* @field.hint The field to count on (optional)
	*/
	numeric function getErrorCount(string field);
	
	/**
	* Get the Errors Array, which is an array of error messages (strings)
	* @field.hint The field to use to filter the error messages on (optional)
	*/
	array function getAllErrors(string field);
	
	/**
	* Get an error object for a specific field that failed. Throws exception if the field does not exist
	* @field.hint The field to return error objects on
	*/
	IValidationError[] function getFieldErrors(required string field);
	
	/**
	* Get a collection of metadata about the validation results
	*/
	struct function getResultMetadata();
	
	/**
	* Set a collection of metadata into the results object
	*/
	IValidationResult function setResultMetadata(required struct data);
	
}

Some of these methods return error objects which adhere to our Error Interface: coldbox.system.validation.result.IValidationError, which can quickly tell you what field had the exception, what was the rejected value and the validation message:


/**
* Set error metadata that can be used in i18n message replacements or in views
* @data.hint The name-value pairs of data to store in this error.
*/
IValidationError function setErrorMetadata(required any data);

/**
* Get the error metadata
*/
struct function getErrorMetadata();
/**
* Set the error message
* @message.hint The error message
*/
IValidationError function setMessage(required string message);
 
/**
* Set the field
* @message.hint The error message
*/
IValidationError function setField(required string field);

/**
* Set the rejected value
* @value.hint The rejected value
*/
IValidationError function setRejectedValue(required any value);

/**
* Set the validator type name that rejected
* @validationType.hint The name of the rejected validator
*/
IValidationError function setValidationType(required any validationType);

/**
* Get the error validation type
*/
string function getValidationType();

/**
* Set the validator data
* @data.hint The data of the validator
*/
IValidationError function setValidationData(required any data);

/**
* Get the error validation data
*/
string function getValidationData();

/**
* Get the error message
*/
string function getMessage();

/**
* Get the error field
*/
string function getField();

/**
* Get the rejected value
*/
any function getRejectedValue();

Validating with shared constraints

We also have the ability to validate a target object or form with shared constraints from our configuration file:


	// validate user object
	prc.results = validateModel(target=user,constraints="sharedUser");

	// validate incoming form elements in the RC or request collection
	prc.results = validateModel(target=rc,constraints="sharedUser");

This will validate the object and rc using the sharedUser constraints.

Validating with a-la-carte constraints

We also have the ability to validate a target object with custom a-la-carte constraints by passing the constraints inline:

	myConstraints = {
		login = { required=true, size=6..10}, password = { required=true, size=6..10}
	};
	prc.results = validateModel(target=user,constraints=myConstraints);

This will validate the object using the inline constraints that you built.

Validating Custom Fields

You can also tell the validation manager to ONLY validate on certain fields and not all the fields declared in the validation constraints.

prc.results = validateModel(target=user,fields="login,password");

This will only validate the login and password fields.

Displaying Errors

After validation you can use the same results object and use it to display the validation errors in your client side:

Handlers:

// store the validation results in the request collection
prc.validationResults = validateModel( obj );

Views:

<-- DDisplay all errors as a message box --->
#getPlugin("MessageBox").renderMessage(type="error",messageArray=prc.validationResults.getAllErrors())#

If you want more control you can use the hasErrors() and iterate over the errors to display:

<cfif prc.validationResults.hasErrors()>
	<ul>
	<cfloop array="#prc.validationResults.getErrors()#" index="thisError">
		<li>#thisError.getMessage()#</li>
	</cfloop>
	</ul>
</cfif>

You can even use the results object in your views to get specific field errors, messagesbox, etc.

Common Methods

The following are some common methods from the validation result object for dealing with errors:

  • getResultMetadata()
  • getFieldErrors( [field] )
  • getAllErrors( [field] )
  • getAllErrorsAsJSON( [field] )
  • getAllErrorsAsStruct( [field] )
  • getErrorCount( [field] )
  • hasErrors( [field] )
  • getErrors()

As always, please visit the API Docs for the latest methods and arguments.

i18n Integration

If you are using i18n (Internationalization and Localization) in your ColdBox applications you can also localize your validation error messages from the ColdBox validators. You will do this by our lovely conventions for you resource bundle keys:

Objects:

{ObjectName}.{Field}.{ConstraintType}}=Message

Forms with Shared Constraints Name

{SharedConstraintName}.{Field}.{ConstraintType}=Message

Forms with No Shared Constraints

GenericForm.{Field}.{ConstraintType}=Message

We also setup lots of global {Key} replacements for your messages and also several that the core constraint validators offer as well:

Global {} replacements

  • rejectedValue - The rejected value
  • field or property - The property or field that was validated
  • validationType - The name of the constraint validator
  • validationData - The value of the constraint definition, e.g size=5..10, then this value is 5..10
  • targetName - The name of the user, shared constraint or form

Validator {} replacements

  • DiscreteValidator = operation, operationValue
  • InListValidator = inList
  • MaxValidator = max
  • MinValidator = min
  • RangeValidator = range, min, max
  • RegexValidator = regex
  • SameAsValidator, SameAsNoCaseValidator = sameas
  • SizeValidator = size, min, max
  • TypeValidator = type
blank=The field {property} must contain a value.
email=The field {property} is not a valid email address.
unique=The field {property} is not a unique value.
size=The field {property} was not in the size range of {size}.
inlist=The field {property} was not in the list of possible values.
validator=There was a problem with {property}.
min=The minimum value {min} was not met for the field {property}.
max=The maximum value {max} was exceeded for the field {property}.
range=The range was not met for the field {property}.
matches=The field {property} does not match {regex}.
numeric=The field {property} is not a valid number.

WireBox DSL & Integration

ColdBox will create two binder mappings for you on application startup:

  • WireBoxValidationManager : A reference to the application's validation manager
  • WireBoxValidationManagerPath : A reference to the path or id the application was configured with

We also expanded the WireBox injection DSL with coldbox:validationManager to get a reference to the application's validation manager in any dependency injection enabled object.

// get reference
property name="validationManager" inject="coldbox:validationManager";
property name="validationManager" inject="id:WireBoxValidationManager";
// or map it
map("ValidationManager").toDSL("coldbox:validationManager");

You can also retrieve the path or id of the validation manager and retrieve it like so, or you could even map it yourself.

// Get validation manager
var validationManager = wirebox.getInstance( "WireBoxValidationManager" );

Custom Validators

You can build also your own validators by implementing our interface coldbox.system.validation.validators.IValidator:

/**
* Will check if an incoming value validates
* @validationResult.hint The result object of the validation
* @target.hint The target object to validate on
* @field.hint The field on the target object to validate on
* @targetValue.hint The target value to validate
*/
boolean function validate(required coldbox.system.validation.result.IValidationResult validationResult, required any target, required string field, any targetValue, string validationData);

/**
* Get the name of the validator
*/
string function getName();

The arguments received are:

  • validationResults : The validation result object
  • target : The target object in the validation
  • field : The field or property in the object that is in validation
  • targetValue : The value to test

Here is a sample validator:

/**
********************************************************************************
Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
www.coldbox.org | www.luismajano.com | www.ortussolutions.com
********************************************************************************
The ColdBox validator interface, all inspired by awesome Hyrule Validation Framework by Dan Vega
*/
component accessors="true" implements="coldbox.system.validation.validators.IValidator" singleton{

	property name="name";
	
	MaxValidator function init(){
		name = "Max";	
		return this;
	}

	/**
	* Will check if an incoming value validates
	* @validationResult.hint The result object of the validation
	* @target.hint The target object to validate on
	* @field.hint The field on the target object to validate on
	* @targetValue.hint The target value to validate
	* @validationData.hint The validation data the validator was created with
	*/
	boolean function validate(required coldbox.system.validation.result.IValidationResult validationResult, required any target, required string field, any targetValue, string validationData){
		
		// Simple Tests
		if( !isNull(arguments.targetValue) AND arguments.targetValue <= arguments.validationData ){
			return true;
		}
		
		var args = {message="The '#arguments.field#' value is not less than #arguments.validationData#",field=arguments.field,validationType=getName(),validationData=arguments.validationData};
		var error = validationResult.newError(argumentCollection=args).setErrorMetadata({max=arguments.validationData});
		validationResult.addError( error );
		return false;
	}
	
	/**
	* Get the name of the validator
	*/
	string function getName(){
		return name;
	}
	
}

Custom Validation Managers

If you would like to adapt your own validation engines to work with ANY ColdBox application you can do this by implementing the following interfaces:

  • Validation Manager : Implement the coldbox.system.validation.IValidationManager. Then use the class path in your configuration file so it uses your validation manager instead of ours.
  • Validation Results : Implement the coldbox.system.validation.result.IValidationResult, which makes it possible for any ColdBox application to use your validation results.
  • Validation Error : Implement the coldbox.system.validation.result.IValidationError, which makes it possible for any ColdBox application to use your validation error representations.

That's it! Then use your own validation engine with ColdBox seamlessly.

 
Download in other Formats:
markup Markup | html HTML | word Word

comments Comments (0)


ColdBox Books