Developing a Form Validation System with the Observer Pattern in PHP

Want to gain a good grounding in how to apply the Observer pattern in PHP? Then you’re in the right place! Welcome to the second part of the series “The Observer Pattern in PHP.” Composed of three tutorials, this series teaches you the key concepts of the popular Observer design pattern, and shows you how to apply it in the context of real-world PHP applications.

Introduction

Okay, after introducing the topics treated over the course of this series, let’s quickly recapitulate the concepts that I deployed in the first tutorial so you can understand this second article more easily.

As you’ll hopefully remember, in the previous part I went through the implementation of the Observer pattern in PHP 5 by coding a basic example. The example demonstrated in an easygoing fashion how the different objects that make up a specific program can be decoupled from the rest of the application, in this way clearly delineating their scope.

Regarding this particular example, the implementation of the Observer pattern allowed me to develop a message handling system capable of reflecting certain changes generated by independent objects at the application’s core level. Indeed, the application of this pattern is one of the best approaches available today for constructing truly independent objects, as well as building systems that expose a centralized mechanism for making decisions according to the changes introduced by disparate components.

Well, after refreshing some of the most important points regarding the creation of observer objects, let’s focus our attention on the subject of this second tutorial. Over the next few lines, I’ll be demonstrating how to apply the Observer pattern during the development of a real-world application: a form validation system.

Based on this approach, hopefully you’ll learn quickly how to validate input data by using the Observer pattern. Are you interested in learning how this can be achieved with PHP? Fine, start right now reading the next section!

{mospagebreak title=Validating input data: constructing some useful data validation classes}

Before I proceed to demonstrate how the Observer pattern can be used inside a data validation application, I’m going to take a logical path and start defining some independent classes, all of them aimed at validating different types of user-supplied input.

Once these classes have been created, I’ll show you how to implement an observer object that will be able to perform an efficient validation process on the data entered by users. Considering this schema, below I listed the first set of classes that I plan to use inside the data checking application:

  // 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);
        }
    }
  }

Okay, that’s all for the moment about the data checking classes. As you can see above, I defined a highly generic “DataValidator” class, which takes as an input parameter an object of the type “FormObserver” and then assigns it as a class property.

Please don’t worry about the meaning of this object right now, since I’ll explain later how it will fit into the whole context of the application. Pay strong attention to the additional “notifyObserver()” method, however, which will be responsible for sending the corresponding error messages to the “FormObserver” object whenever a user-supplied input is considered invalid by a particular data checking class.

As you’ll realize, after defining this base “DataValidator” class, creating a fine-tuned class to validate specific data types is indeed a straightforward process, which is reflected precisely by the definition of these classes.

With reference to the validation of specific data, you can see that I created a set of classes that check for valid strings, integers, floating numbers, and ranges respectively. In addition, when a given input fails the checking process (no matter what class is used), then the respective “notifyObserver()” method is called, in order to send a notification about the error that happened.

At this stage, even though I still didn’t show you a single clue about how an observer object can be coded inside the application, quite probably you’ve guessed its driving logic. Yes, you’re correct, when a particular user input is considered not valid by the corresponding class, a notification message will be sent to the observer, and it will decide what action to take. Now, hopefully you’re beginning to see how all these validation objects are decoupled from the application, right?

All right, now that you have taken a look at the definition of the previous classes, let’s move forward and create some additional ones to tackle the validation of alphabetic and alphanumeric data, as well as the verification of email addresses.

To see how this will be done, please read the next section.

{mospagebreak title=Extending the scope of data validation: defining some additional data checking classes}

As I mentioned before, I was planning to extend the validation capabilities of the application. So my next task is to code some new classes, aimed at checking alphabetic and alphanumeric values, along with email addresses. Please take a look at the classes listed below:

  // 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;
    }
  }

As shown above, I defined an additional set of data checking classes, in order to extend the capabilities of the application. It’s easy to see how these new classes have been created, since they’re also sub classes of the base “DataValidator” class.

Of course, since I wish to expand the functionality of the whole application, I covered the validation of alphanumeric and alphabetic values, together with the verification of email addresses, in two flavors: Unix-based and Windows systems.

At this stage, I provided this data-checking application with a decent number of checking classes. These classes are not only independent, but also capable of notifying a core object of any errors that occurred during the checking process. Definitely, you’ll agree with me that I’m on the right track for implementing the Observer pattern with PHP 5. Don’t you feel a little happier?

But, let’s get serious now and jump into the next section, so you can see the full source code of all the data checking classes that I defined before. Just go ahead and keep on reading.

{mospagebreak title=Getting the whole picture: listing the full source code for the data checking classes}

Culled from my own experience, I know that it’s much better to have all the source code listed in one place. For this simple reason, below I’ve listed all the data validation classes that I’ve defined in the previous section, so you can have a clear idea of how all the pieces fit together. Here they are:

  // 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;
    }
  }

Here you have it. Now that you have the entire set of data validation classes listed in one place, feel free to improve them, in accordance with your specific programming requirements. It’s truly educational, believe me.

To wrap up

In this second article, you learned how to build a data validation system which uses independent objects for checking the validity of a broad range of user-supplied input, and also is capable of sending information about errors to a core object when user data fails to pass the verification process. However, the application is still incomplete, since the “core object” that I mentioned before remains undefined.

But, there’s no reason to panic. In the last article, I’ll show you how to define an observer object at the application’s core level, in this way providing the program with a centralized mechanism for making programmatic decisions based upon the errors that occurred when validating user-provided data. I’ll meet you in the last tutorial!

Google+ Comments

Google+ Comments