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:
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.
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:
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.
class Validator
{
private $_errors = array();
private $_action;
public function __construct($action)
{
$this->_action = $action;
}
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;
}
public function hasError()
{
return (count($this->_errors) > 0);
}
public function get_action() {
return $this->_action;
}
public function get_errors() {
return $this->_errors;
}
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:
class NamePattern extends BasePattern
{
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('/[~!@#$%^&\*\(\)\+={}\[\]|\\:;"<>\?\/]/', $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.
class ValidationError
{
private $_field;
private $_value;
private $_message;
public function __construct($field, $value, $message='')
{
$this->_field = $field;
$this->_value = $value;
$this->_message = $message;
}
public function get_message() {
return $this->_message;
}
public function get_value() {
return $this->_value;
}
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:
$employee = new Employee ( );
$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