Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PHP

PHP Business Entity Validation

4.50/5 (3 votes)
29 Sep 2010CPOL3 min read 16.2K  
PHP business entity validation

PHP Validation Functionality

I will not reveal any secret if I say all applications are using validation functionality. Some of them have it very simple, just if-else condition to be sure incoming data is correct but some of them require advanced functionality, especially business applications which process incoming user data. If you develop some web based business applications and you are sure the application user will set correct data, you still need some validation because HTTP internet traffic can be easy hacked (of course if it is not HTTPS) and somebody can send incorrect data to ... even just for joke.

Here I will explain a very good validation functionality we use now in our different PHP and .NET projects. Why do I think it is good? Because it is easy extendible and moveable way. Our last .NET projects include Validation as external project to solution, unfortunately PHP does not have namespaces like .NET and we keep all Validation in the separate folder inside project and just copy this folder to a new project.

Let's start...

For example, you develop some business application with some different business instances. Let's assume you have Employee business instance and corresponding DEmployee data instance. They have different properties and functions and implement some IBusiness and IDataEnity interfaces. Before saving Employee instance to database, I must validate it. I just add Validate function to our interfaces, to IBusiness to validate instance based on business rules like employee name should start from Department number and to IDataEnity to validate instance based on database rules like employee name length should be more than 5 symbols. Why we can not make employee validation once? Starting employee name with Department number is business rules, we require employee name should start from property of another business property, we should validate it at the business level. Employee length should be between 5 and 25 symbols is data rule because field length is database restriction, business level does not think about name symbols, it uses name property as object. May be Employee name is a complex instance and it does not have symbols at all, it is reference, but string Employee presentation should start from Department number.

As we declared Validation function in the IBusiness interface, we need implement it in the Employee class, something like this:

PHP
public function Validate(Action $action) {
	$v = new Validator ( $action );
	$v
		->Validate ( new BusinessNamePattern ( ), 
		array ('Employee Name' => $this->getActualName () ) );
	if ($v->hasError ())
		return $v;
	return parent::Validate ( $action );
}

Looks simple, we create Validator instance and run function with pattern instance and data array to validate. If data is wrong, then we return Validator instance.

The same simple Validation implementation is in the DEmployee class.

PHP
public function Validate(BaseBusinessAction $action)
{
	$v = new Validator($action);
	$v->Validate(new DOBPatternYYYYMMDD(), array('dob' => $this->get_dob()));
	$v->Validate(new NamePattern(), array('Last Name'=>$this->get_last_name()));
	$v->Validate(new NamePattern(), array('First Name'=>$this->get_first_name()));
	if ($v->hasError())
		return $v;
	return parent::Validate($action);		
}

Here we validate date of birthday and first, last names. Exactly the same but it looks too complex, let's rewrite more elegant as:

PHP
public function Validate(BaseBusinessAction $action)
{
	$v = new Validator($action);
	$v
		->Validate(new DOBPatternYYYYMMDD(), array('dob' => $this->get_dob()))
		->Validate(new NamePattern(), array(
			'First Name'=>$this->get_first_name()
			, 'Last Name'=>$this->get_last_name()
			));
	if ($v->hasError())
		return $v;
	return parent::Validate($action);		
}

It is the same.

Validation is ready and if you have Validator and patterns, then it is all. In our projects, it is all. Simple? Yes.
Fast? Yes.
Is it cheap develop and support? Yes.

You do not have validator, let's go ahead. Nothing difficult. All patterns extend IPattern interface and implement isApproved function that returns TRUE if data is good or ValidationError instance.

PHP
class Validator
{
	private $_errors = array();
	/**
	 * 
	 * @var BaseBusinessAction
	 */
	private $_action;
	
	public function __construct($action)
	{
		$this->_action = $action;	
	}
	
	/**
	 * Validate $data
	 * @param BasePattern $pattern
	 * @param $data it is key=>value array to validate
	 * @return Validator
	 * @uses 
	 * $v = new Validator();
	 * $v->Validate(new NotEmptyPattern(BasePattern::ACTION_INSERT), 
	 * array('name'=>$name, 'city'=>$city, 'address'=>$address))
	 * $v->Validate(new NamePattern(BasePattern::ACTION_INSERT), 
	 * array('name'=>$name))
	 * if ($v->hasError)
	 * 	throw new ValidationException($v);
	 */
	public function Validate(BasePattern $pattern, $data)
	{
		if (is_array($data))
		{
			$pattern->set_action($this->_action);
			foreach ($data as $key => $value)
				if (!(($error = $pattern->isApproved($key, $value)) 
					=== TRUE))
					$this->_errors[] = $error;
		}
		else
			throw new ApplicationException
			('Wrong argument parameters:'.print_r($data, true));
		return $this;
	}
	
	/**
	 * 
	 * @return true if there is some error
	 */
	public function hasError()
	{
		return (count($this->_errors) > 0);
	}
	/**
	 * @return the $_action
	 */
	public function get_action() {
		return $this->_action;
	}

	/**
	 * @return the $_errors
	 */
	public function get_errors() {
		return $this->_errors;
	}
	
	/**
	 * Return validation errors as string
	 * @return string
	 */
	public function toString()
	{
		$result = array();
		foreach ($this->_errors as $error)
			$result[] = $error->toString();
		return implode("\n", $result);		
	}
	
	public function __toString()
	{
		return $this->toString();
	}
}

You can have any pattern as you need, some tricky or difficult or easy, everything that you need. Below I show NamePattern class:

PHP
class NamePattern extends BasePattern
{
	/**
	 * Satisfy data with coded requirements
	 * Name can not have any numbers or any special symbols
	 * @param $field
	 * @param $value
	 * @return ValidationError or TRUE
	 */
	public function isApproved($field, $value)
	{
		$not_empty = new NotEmptyPattern($this->get_action());
		if (($res = $not_empty->isApproved($field, $value)) !== TRUE)
			return $res;
		//if (preg_match('/[\d~!@#$%^&\*\(\)\+={}\[\]|\\:;"<>\?\/]/', $value))
		if (preg_match('/[~!@#$%^&\*\(\)\+={}\[\]|\\:;"<>\?\/]/', $value))
			return new ValidationError($field, $value, 
				"$field has unavailable symbols: $value");
		$clear_value = preg_replace('/\s/', "", $value);
		$len = strlen($clear_value); 
		if ($len<5)
			return new ValidationError($field, $value, 
					"$field is too short.");
		if ($len>25)
			return new ValidationError($field, $value, 
					"$field is too long.");
		return TRUE;
	}
}

It uses another pattern NotEmptyPattern to be sure name is not empty, looks for unapproved symbols and checks name length. This pattern is not action dependent. For example, instance Id validation pattern can be action dependent because usually INSERT action can have id = 0 but update cannot.

PHP
class ValidationError
{
	private $_field;
	private $_value;
	private $_message;
	
	/**
	 * Create instance
	 * @param $field
	 * @param $value
	 * @param $message it is format of error string for sprintf.
	 */
	public function __construct($field, $value, $message='')
	{
		$this->_field = $field;
		$this->_value = $value;
		$this->_message = $message;
	}
	/**
	 * @return the $_message
	 */
	public function get_message() {
		return $this->_message;
	}

	/**
	 * @return the $_value
	 */
	public function get_value() {
		return $this->_value;
	}

	/**
	 * @return the $_field
	 */
	public function get_field() {
		return $this->_field;
	}

	public function __toString()
	{
		return $this->toString();
	}
	
	public function toString()
	{
		if (empty($this->_message))
			return "Field ".$this->_field." 
			has incorrect value '".$this->_value."'.";
		else
			return sprintf($this->_message, $this->_value);
	}
}

I think you see how simple it is to use it. Get populated instance, validate it, check result and rollback if data is not correct or save if data is correct. It looks like this one:

PHP
//......................................
	$employee = new Employee ( );
//...... populate employee instance......
	$v = $employee->Validate(BasePattern::ACTION_UPDATE);
	if ($v->hasError())
	{
		$transaction->rollback();
		HTTPFactory::sendJSONResponse ( array 
			('code'=>1, 'processStatus' => "Form is not saved 
			(Employee data validation error:\n".$v.')' ) );
	}
	$employee->save ();
//......................................

HTTPFactory::sendJSONResponse sends json response to user browser and exists.

That's it.

History

  • 29th September, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)