The Strategy Pattern is another mechanism to save endless reproduction of if / else statements. It is used in cases where there is a common “problem” which can be solved by one of many algorithms, for example validating the fields of sent by a form. As with most things object oriented, it allows for reuse and extensibility.

1) Taking the example of form validation, we have a prime candidate for a strategy pattern. The fields submitted from a form usually need to checked in some way before acting further on them, such as inserting them into a database. How often do we find ourselves writing procedural code like;

if ( isset ($_POST['submit']) ) {
    if ( $user < '6' ) {
        echo ('Username is too short');
    } else if ( $pass != $conf ) {
        echo ('Passwords do not match');
    } else if ( $fullmoon == true ) {
        // Etc. etc. etc.
    }
}

The idea behind a strategy pattern is to build a set of classes accessed via a single interface: i.e. a generic superclass (parent) and specific subclasses (children). The subclasses encapsulate the specific algorithms we need and by defining a solid interface, we can simply add subclasses as we need them.

Then whenever the particular problem arises in our code, we delegate the solution of the problem to the strategy pattern.

As with all things in patterns, it’s easier to see it in practice rather than try to define it in detail. So here’s a strategy pattern for form validation;

<?php
/**
 *  Validator superclass for form validation
 */
class Validator {
    /**
    * Private
    * $errorMsg stores error messages if not valid
    */
    var $errorMsg;
 
    //! A constructor.
    /**
    * Constucts a new Validator object
    */
    function Validator () {
        $this->errorMsg=array();
        $this->validate();
    }
 
    //! A manipulator
    /**
    * @return void
    */
    function validate() {
        // Superclass method does nothing
    }
 
    //! A manipulator
    /**
    * Adds an error message to the array
    * @return void
    */
    function setError ($msg) {
        $this->errorMsg[]=$msg;
    }
 
    //! An accessor
    /**
    * Returns true is string valid, false if not
    * @return boolean
    */
    function isValid () {
        if ( isset ($this->errorMsg) ) {
            return false;
        } else {
            return true;
        }
    }
 
    //! An accessor
    /**
    * Pops the last error message off the array
    * @return string
    */
    function getError () {
        return array_pop($this->errorMsg);
    }
}
 
/**
 *  ValidatorUser subclass of Validator
 *  Validates a username
 */
class ValidateUser extends Validator {
    /**
    * Private
    * $user the username to validate
    */
    var $user;
 
    //! A constructor.
    /**
    * Constucts a new ValidateUser object
    * @param $user the string to validate
    */
    function ValidateUser ($user) {
        $this->user=$user;
        Validator::Validator();
    }
 
    //! A manipulator
    /**
    * Validates a username
    * @return void
    */
    function validate() {
        if (!preg_match('/^[a-zA-Z0-9_]+$/',$this->user )) {
            $this->setError('Username contains invalid characters');
        }
        if (strlen($this->user) < 6 ) {
            $this->setError('Username is too short');
        }
        if (strlen($this->user) > 20 ) {
            $this->setError('Username is too long');
        }
    }
}
 
/**
 *  ValidatorPassword subclass of Validator
 *  Validates a password
 */
class ValidatePassword extends Validator {
    /**
    * Private
    * $pass the password to validate
    */
    var $pass;
    /**
    * Private
    * $conf to confirm the passwords match
    */
    var $conf;
 
    //! A constructor.
    /**
    * Constucts a new ValidatePassword object subclass or Validator
    * @param $pass the string to validate
    * @param $conf to compare with $pass for confirmation
    */
    function ValidatePassword ($pass,$conf) {
        $this->pass=$pass;
        $this->conf=$conf;
        Validator::Validator();
    }
 
    //! A manipulator
    /**
    * Validates a password
    * @return void
    */
    function validate() {
        if ($this->pass!=$this->conf) {
            $this->setError('Passwords do not match');
        }
        if (!preg_match('/^[a-zA-Z0-9_]+$/',$this->pass )) {
            $this->setError('Password contains invalid characters');
        }
        if (strlen($this->pass) < 6 ) {
            $this->setError('Password is too short');
        }
        if (strlen($this->pass) > 20 ) {
            $this->setError('Password is too long');
        }
    }
}
 
/**
 *  ValidatorEmail subclass of Validator
 *  Validates an email address
 */
class ValidateEmail extends Validator {
    /**
    * Private
    * $email the email address to validate
    */
    var $email;
 
    //! A constructor.
    /**
    * Constucts a new ValidateEmail object subclass or Validator
    * @param $email the string to validate
    */
    function ValidateEmail ($email){
        $this->email=$email;
        Validator::Validator();
    }
 
    //! A manipulator
    /**
    * Validates an email address
    * @return void
    */
    function validate() {
        $pattern=
    "/^([a-zA-Z0-9])+([.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-]+)+/";
        if(!preg_match($pattern,$this->email)){
            $this->setError('Invalid email address');
        }
        if (strlen($this->email)>100){
            $this->setError('Address is too long');
        }
    }
}
?>

Looking at the parent Validator class, we see it defines an interface by which any client code will access the strategy pattern.

Now, using a procedural example (for simplicity) of some client code accessing the strategy pattern, we’ll demonstrate how it works. This could be a user registration form;

<?php
if ( $_POST['register'] ) {
    require_once('lib/Validator.php');
 
    // Register the subclasses to use
    $v['u']=new ValidateUser($_POST['user']);
    $v['p']=new ValidatePassword($_POST['pass'],$_POST['conf']);
    $v['e']=new ValidateEmail($_POST['email']);
 
    // Perform each validation
    foreach($v as $validator) {
        if (!$validator->isValid()) {
            while ($error=$validator->getError()) {
                $errorMsg.="<li>".$error."</li>n";
            }
        }
    }
    if (isset($errorMsg)) {
        print ("<p>There were errors:<ul>n".$errorMsg."</ul>");
    } else {
        print ('<h2>Form Valid!</h2>');
    }
} else {
?>
<h2>Create New Account</h2>
<form action="<?php echo ($_SERVER['PHP_SELF']); ?>" method="post">
<p>Username: <input type="text" name="user"></p>
<p>Password: <input type="password" name="pass"></p>
<p>Confirm: <input type="password" name="conf"></p>
<p>Email: <input type="text" name="email"></p>
<p><input type="submit" name="register" value=" Register "></p>
</form>
<?php
}
?>

If we needed to add some more fields to the form, we simply need to update this section to validate them;

    // Register the subclasses to use
    $v['u']=new ValidateUser($_POST['user']);
    $v['p']=new ValidatePassword($_POST['pass'],$_POST['conf']);
    $v['e']=new ValidateEmail($_POST['email']);
 
    $v['a']=new ValidateAddress($_POST['address'],$_POST['zip']);

We can now simply add another subclass to the Validator pattern to handle addresses. Pretty soon, we’ll have a subclass for every common validation problem and never need to suffer the torment of endless if/else statements again.

Note: once you’ve read this, make sure you read Unit Testing in PHP


design/strategy_pattern.txt · Last modified: 2005/10/15 21:47