Observer pattern for form validation
Posted: Sun Aug 06, 2006 6:51 am
In a recent series of articles on DevShed Alejandro shows how to use the Observer pattern for form validation. A topic which comes up regular on this forum. For example, we have talked about using the Strategy pattern (see also here) for form validation. In the same thread, Arborint shows how he uses a Rules bases Validator class approach to Forms validation in his Skeleton. An approach which looks very solid. However, as I'm learning i'm also looking at other approaches.
I'll show the code from the devshed articles:
example:
FormObserver.class.php
DataValidator.class.php
This approach seems to work ok. However, I can't see yet what advantages or disadvantages this pattern has in this situation compared to other solutions, like the previously mentioned Strategy Pattern or Rule Based approach from Arborint.
- I do like how the validation classes are nicely seperated. Each form of validation has it's own class. So that's ok.
- I don't like the fact that the form is hardcoded in the Observer class in the function displayForm. Suppose you have a whole bunch of forms, how would I refactor the code to seperate those?
As I'm not experienced enough to see other pitfalls I'm asking for your input. Just recently there was a thread about when to use the observer pattern, and form validation was not mentioned. Was that for a reason?
(p.s. if the mods want me to shorten the code I'll do that.)
I'll show the code from the devshed articles:
example:
Code: Select all
include 'DataValidator.class.php';
include 'FormObserver.class.php';
try{
// instantiate 'FormObserver' object
$formObs=new FormObserver('send');
// instantiate validators and validate fields
$alphaVal=new AlphaValidator($formObs);
$alphaVal->validate('firstname','Enter a valid First Name (only alphabetic characters).');
$alphaVal->validate('lastname','Enter a valid Last Name (only alphabetic characters).');
$intVal=new IntegerValidator($formObs);
$intVal->validate('age','Enter a valid age (0-99).');
$emailVal=new EmailValidatorWin($formObs);
$emailVal->validate('email','Enter a valid email address.');
// check for errors
$formObs->checkNotifications();
}
catch(Exception $e){
echo $e->getMessage();
exit();
}Code: Select all
<?php
/**
* 5-8-06
* Centralizing the Validation of Data with the Observer Pattern in PHP
* http://www.devshed.com/c/a/PHP/Centrali ... rn-in-PHP/
* Alejandro
*/
// class FormObserver
class FormObserver{
private $notifications;
private $formVar;
public function __construct($formVar){
//$this->formVar=$_POST[$formVar];
$this->formVar=!isset($_POST[$formVar])?'':$_POST[$formVar];
$this->notifications=array();
}
public function addNotification($errorMessage){
$this->notifications[]=$errorMessage;
}
public function checkNotifications(){
if(!$this->formVar){
$this->displayForm();
}
else
{
if(count($this->notifications)>0){
// form is not OK
$this->displayErrors();
$this->displayForm();
}
else
{
// form is OK
echo '<p>The form has been submitted successfully!</p>';
}
}
}
// display errors
private function displayErrors(){
$errstr='<p>Please correct the following fields and resubmit the form:</p>';
foreach($this->notifications as $notification){
$errstr.='<p>'.$notification.'</p>';
}
echo $errstr;
}
// display form
public function displayForm(){
$formstr='<form action="'.$_SERVER['PHP_SELF'].'" method="post">';
$formstr.='First Name <input type="text" name="firstname" value="'.$_POST['firstname'].'"/><br />';
$formstr.='Last Name <input type="text" name="lastname" value="'.$_POST['lastname'].'" /><br />';
$formstr.='Email <input type="text" name="email" value="'.$_POST['email'].'" /><br />';
$formstr.='Age <input type="text" size="1" name="age" value="'.$_POST['age'].'" /><br />';
$formstr.='<input type="submit" value="Send" name="send" /></form>';
echo $formstr;
}
}
?>Code: Select all
<?php
/**
* 5-8-06
* Centralizing the Validation of Data with the Observer Pattern in PHP
* http://www.devshed.com/c/a/PHP/Centrali ... rn-in-PHP/
* Alejandro
*/
// define DataValidator class
class DataValidator{
protected $method;
protected $formObserver;
public function __construct(FormObserver $formObserver){
$this->formObserver=$formObserver;
$this->method=$_POST;
}
protected function notifyObserver($errorMessage){
$this->formObserver->addNotification($errorMessage);
}
}
// define StringValidator class
class StringValidator extends DataValidator{
public function __construct($formObserver){
parent::__construct($formObserver);
}
// validate strings
public function validate($field,$errorMessage,$min=4,$max=32)
{
if(!isset($this->method[$field])
|| trim($this->method[$field])==''
|| strlen($this->method[$field])<$min
|| strlen($this->method[$field])>$max)
{
$this->notifyObserver($errorMessage);
}
}
}
// define IntegerValidator class
class IntegerValidator extends DataValidator{
public function __construct($formObserver){
parent::__construct($formObserver);
}
// validate integers
public function validate($field,$errorMessage){
if(!isset($this->method[$field])
|| !is_numeric($this->method[$field])
||intval($this->method[$field])!=$this->method[$field]){
$this->notifyObserver($errorMessage);
}
}
}
// define NumberValidator class
class NumberValidator extends DataValidator{
public function __construct($formObserver){
parent::__construct($formObserver);
}
// validate numbers
public function validate($field,$errorMessage){
if(!isset($this->method[$field])
|| !is_numeric($this->method[$field])){
$this->notifyObserver($errorMessage);
}
}
}
// define RangeValidator class
class RangeValidator extends DataValidator{
public function __construct($formObserver){
parent::__construct($formObserver);
}
// validate ranges
public function validate($field,$errorMessage,$min=1,$max=99){
if(!isset($this->method[$field])
|| $this->method[$field]<$min || $this->method[$field]>$max){
$this->notifyObserver($errorMessage);
}
}
}
// define AlphaValidator class
class AlphaValidator extends DataValidator{
public function __construct($formObserver){
parent::__construct($formObserver);
}
// validate alphabetic field
public function validate($field,$errorMessage){
if(!isset($this->method[$field])
|| !preg_match("/^[a-zA-Z]+$/",$this->method[$field])){
$this->notifyObserver($errorMessage);
}
}
}
// define AlphanumValidator class
class AlphanumValidator extends DataValidator{
public function __construct($formObserver){
parent::__construct($formObserver);
}
// validate alphanumeric data
public function validate($field,$errorMessage){
if(!isset($this->method[$field])
|| !preg_match("/^[a-zA-Z0-9]+$/",$this->method[$field])){
$this->notifyObserver($errorMessage);
}
}
}
// define EmailValidator class
class EmailValidator extends DataValidator{
public function __construct($formObserver){
parent::__construct($formObserver);
}
// validate email
public function validate($field,$errorMessage){
if(!isset($this->method[$field])
|| !preg_match("/.+@.+..+/",$this->method[$field])
||!checkdnsrr(array_pop(explode("@",$this->method[$field])),"MX")){
$this->notifyObserver($errorMessage);
}
}
}
// define EmailValidatorWin class (Windows systems)
class EmailValidatorWin extends DataValidator{
public function __construct($formObserver){
parent::__construct($formObserver);
}
public function validate($field,$errorMessage){
if(!isset($this->method[$field])
|| !preg_match("/.+@.+..+/",$this->method[$field])
|| !$this->windnsrr(array_pop(explode("@",$this->method[$field])),"MX")){
$this->notifyObserver($errorMessage);
}
}
// private method 'windnsrr()' for Windows systems
private function windnsrr($hostName,$recType=''){
if(!empty($hostName)){
if($recType=='')$recType="MX";
exec("nslookup -type=$recType $hostName",$result);
foreach($result as $line){
if(preg_match("/^$hostName/",$line)){
return true;
}
}
return false;
}
return false;
}
}
?>- I do like how the validation classes are nicely seperated. Each form of validation has it's own class. So that's ok.
- I don't like the fact that the form is hardcoded in the Observer class in the function displayForm. Suppose you have a whole bunch of forms, how would I refactor the code to seperate those?
As I'm not experienced enough to see other pitfalls I'm asking for your input. Just recently there was a thread about when to use the observer pattern, and form validation was not mentioned. Was that for a reason?
(p.s. if the mods want me to shorten the code I'll do that.)