PHP Form Wizard processing

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
nektarios
Forum Newbie
Posts: 2
Joined: Sat Mar 15, 2008 4:15 pm

PHP Form Wizard processing

Post by nektarios »

Hello,

I have a wizard form with 5 steps implemented already but I want to change the whole code to a better OO approach.

So far I got this classes:

Code: Select all

 
class Validator {
    
    protected $fields;
    
    /**
     * 
     */
    function __construct() {
        $this->fields = array();
    }
    
    /**
     * Adds a ValidatorField
     *
     * @param ValidatorField $ValidatorField
     */
    public function addField(ValidatorField $ValidatorField) {
        $this->fields[] = $ValidatorField;
    }
    
    /**
     * Validates the whole objects' data based on the corresponding criteria
     *
     */
    public function validate() {
        /* @var $Errors Errors */       
        $Errors = Errors::getInstance();
        
        foreach ($this->fields as $ValidatorField) {
            /* @var $ValidatorField ValidatorField */
            /* @var $ValidatorCriteria ValidatorCriteria */
            $ValidatorCriteria = new ValidatorCriteria();
            
            $value = $ValidatorField->getData();
            
            if(strlen($value) == '') {
                // it's empty so it's unvalidated
                $ValidatorField->makeValid(false);
                $Errors->addError($ValidatorField->getHTMLFieldID(), $ValidatorField->getErrorMsg());
            } else {
                switch ($ValidatorField->getCriteria()) {
                    case $ValidatorCriteria->POSITIVEINTEGER:
                        if(// PSEUDO CODE DOVALIDATION) {
                                $ValidatorField->makeValid(true);
                        } else {
                            $ValidatorField->makeValid(false);
                            $Errors->addError($ValidatorField->getHTMLFieldID(), $ValidatorField->getErrorMsg());
                        }
                    break;
 
                    // MORE CASES FOR ALL THE VALIDATION CRITERIA               
                 }
            }
        }
    }
    
    /**
     * Checks the whole data if they are checked and validated
     *
     * @return boolean
     */
    public function isAllValid() {
        $Debug = Debug::getInstance();
        $validated = true;
        foreach ($this->fields as $ValidatorField) {
            /* @var $ValidatorField ValidatorField */
            
            if($ValidatorField->isChecked()) {
                if(!$ValidatorField->isValidated()) {
                    // If even on is not validated then unvalidate all
                    $validated = false;
                    $Debug->addMessage('unvalidated field ' . $ValidatorField->getHTMLFieldID());
                }
            } else {
                // If even one is not checked then unvalidate all
                $validated = false;
            }
        }
        
        return $validated;
    }
}

Code: Select all

 
<?php
 
class ValidatorField {
    
    protected $data;
    protected $criteria;
    protected $validated;
    protected $checked;
    protected $errorMsg;
    protected $HTMLFieldID;
    
    /**
     * 
     */
    function __construct($HTMLFieldID, $data, $criteria, $errorMsg) {
        $this->checked = false;
        $this->validated = false;
        $this->HTMLFieldID = $HTMLFieldID;
        $this->data = $data;
        $this->criteria = $criteria;
        $this->errorMsg = $errorMsg;
    }
    /**
     * @return unknown
     */
    public function isChecked() {
        return $this->checked;
    }
    
    /**
     * Is validated?
     * 
     * @return unknown
     */
    public function isValidated() {
        return $this->validated;
    }
 
    /**
     * Validates the field
     */
    public function makeValid($status) {
        $this->checked = true;
        if($status) {
            $this->validated = true;
        } else {
            $this->validated = false;
        }
    }
}
 
?>
 

Code: Select all

 
class ValidatorCriteria {
    
    public $POSITIVEINTEGER = 1;
    public $NAME = 2;
    public $EMAIL = 3;
    public $STRING = 4;
    public $CC3DIGITS = 5;
    public $CC4DIGITS = 6;
    public $CCEXPIREDATE = 7;
    public $FULLNAME = 8;
    public $ZIP = 9;
    public $TELEPHONE = 10;
    public $DATE = 11;
    public $CCTYPE = 12;
    public $CCVISANUMBER = 13;
    public $CCMASTERNUMBER = 14;
    public $CCAMEXNUMBER = 15;
    
    /**
     * 
     */
    function __construct() {
    
    }
}
 

And here's the code that uses the classes

Code: Select all

 
// Create validators
                $Validator = new Validator();
                $ValidatorCriteria = new ValidatorCriteria();
                
                // Add validation fields
                $Validator->addField(new ValidatorField('firstname', $this->getValue('firstname'), $ValidatorCriteria->NAME, $Lexicon->getLexical('FIRSTNAME_IS_WRONG')));
                $Validator->addField(new ValidatorField('lastname', $this->getValue('lastname'), $ValidatorCriteria->NAME, $Lexicon->getLexical('LASTNAME_IS_WRONG')));
                $Validator->addField(new ValidatorField('customeremail', $this->getValue('customeremail'), $ValidatorCriteria->EMAIL, $Lexicon->getLexical('EMAIL_ADDRESS_IS_WRONG')));
                
                // DOZENS AND DOZENS MORE OF VALIDATORFIELDS HERE
                
                // Validate everything
                $Validator->validate();
                
                if(!$Validator->isAllValid()) {
                    $error = true;
                }
 
My problems with this are:

1) In the Validator class the Validate() method is a very big one Switch statement, that is bothering me and I don't like it.

2) The above validations aren't flexible enough, because some validations need to be done in combining 2 or more fields for example a card number validation is different if the card type is visa or american express and this still needs more business logic behind it to work in this case. So you don't really gaining anything, but maybe this isn't a problem in the end, because this is the place where the higher business logic should go anyway.

3) I would use the Strategy Pattern here for different validators but I would end up with dozens of new subclasses which I reject because I use __autoloading and I want my class directory to be nice and tidy with few classes

Can you point me to a better approach? Am I missing something here?

Also I would want to use a similar approach for handling my wizard step processing, so far I haven't found a nice example of an OO wizard implementation.
marcth
Forum Contributor
Posts: 142
Joined: Mon Aug 25, 2008 8:16 am

Re: PHP Form Wizard processing

Post by marcth »

1) I'd Validate your forms using regular expressions:

Code: Select all

$form = new FormValidator();
$form->addRule('email', RegExp::MANDATORY, 'The email field is mandatory.');
$form->addRule('email', RegExp::EMAIL, 'The email field must be a validly formatted email address.');
 
$actionResponse = $form->validate();
2) Add methods to your FormValidator for complex validation rules:

Code: Select all

class FormValidator {
  private $rules;
 
  public function __construct() {
    $this->rules = array();
  }
 
  public function addRule($fieldName, $regexp, $failureMessage) {
    if(is_null($this->rules[$fieldName])) {
      $this->rules[$fieldName] = array();
    }
 
     $this->rules[$fieldName][] = array(
       'regExp'           => $regexp,
       'failureMessage' => $failureMessage,
     );
  }
 
  public function addCreditCardRule($creditCardDetails, $failureMessages) {
    //....
  }
 
  public function validate() {
    $validationResponse = new ActionResponse();
 
     // ....
 
    return $validationResponse ;
  }
 
}
3) You shouldn't use __autoload() function; use spl_autoload_register() function instead. In my view, the number of files in directory is irrelevant.

4) Checkout the approach I used in the PHP Form Control project I created (http://www.phpFormControl.com). It may give you some ideas.

5) Maintaining wizards is a pain in the butt. I think your FormWizard class could store arrays $_REQUEST arrays from each page. As they navigate backwards and forwards you could update the $_REQUEST scope with the corresponding stored request array to bring back the value they entered on that page. You could serialize this request array and store it in a hidden form field on each page.
Post Reply