Creating a custom form validator using Zend_Validate

Zend_Validate_Interface allows to create any type of validator, though a Zend_Validate_Regex exists, I created my own URL validator using a regular expression just for the sake of this example.

You can see the custom validator in action in this post.

This is the code:

class My_Validate_Url extends Zend_Validate_Abstract
{
    /**
     * Validation failure message key for when the value is an empty string
     */
    const STRING_EMPTY = 'stringEmpty';
    const NOT_MATCH    = 'urlNotMatch';
    
    /**
     * @var array
     */
    protected $_messageTemplates = array(
        self::NOT_MATCH    => "'%value%' does not match an url",
        self::STRING_EMPTY => "'%value% is an empty string"
    );
    
    public function isValid($value)
    {
        $valueString = (string) $value;
        
        $this->_setValue($valueString);
        
        if ('' === $valueString) {
            $this->_error(self::STRING_EMPTY);
            return false;
        } elseif (!preg_match('#https?://([-\w\.])+(:\d+)?(/([\w/_\.]*(\?\S+)?)?)?#', $valueString)) {
            $this->_error(self::NOT_MATCH);
            return false;
        }
        
        return true;
    }
}

Validator chains using Zend_Filter_Input

Zend_Validator paired with Zend_Filter become a powerfull tool for validating data and forms.
Keep in mind that thanks to the Use At Will architecture of the Zend Framework, you can use this sample code in any PHP5 based project, does not matter if your using MVC or not.

Like all my examples, this is pretty straightforward:

The indexAction() only shows the view that contains the form to be validated, if you’re not using MVC you can leave it behind.
validateAction() is where all the magic happens, first a class for a custom validator (explained in other post) is declared. Then the validator chain is created, each one specifying the validator class being used with its respective parameters and the custom messages to show. The custom messages is a real nice feature, specially when developing multi-language applications.
Then the filters array is created, not all input is filtered. In this case, just the age, address and phone are “cleaned” to avoid forbidden characters.
Finally Zend_Filter_Input comes into the game, receiving all the form’s data and checking everything at once with its isValid() method.

Additionally, I’m using FlashMessenger to store the messages and show them in the following page reload. Keep in mind that FlashMessenger’s message can not be displayed in the same action where they are instantiated.

Any questions feel free to write me to [email protected]’sEmailService.com

class ValidatorController extends Zend_Controller_Action
{
    /**
     * Renders the form
     *
     */
    public function indexAction()
    {
        show_source(PATH_CONTROLLERS . 'ExampleController.php');
    }

    /**
     * Validates the input
     *
     */
    public function validateAction()
    {
        /**
         * The included class is an example of custom validators
         */
        require_once PATH_CLASSES . 'My_Validate_Url.php';
        $validurl = new My_Validate_Url();
        $validurl->setMessage('Invalid URL', My_Validate_URL::NOT_MATCH);
        $validurl->setMessage('Empty', My_Validate_URL::STRING_EMPTY);
        $validators = array(
             'address' => array('Alnum', new Zend_Validate_Alnum(true),
                                'messages' => array(
                                'Address not valid', // Default error message
                                // Specific error messages
                                array(Zend_Validate_Alnum::NOT_ALNUM =>
                                      'Address contains invalid characters',
                                      Zend_Validate_Alnum::STRING_EMPTY =>
                                      'Address field is mandatory'))),
             'phone'   => array('Digits', new Zend_Validate_Digits(),
                                'messages' => array(
                                'Wrong phone number format',
                                array(Zend_Validate_Digits::NOT_DIGITS =>
                                      'Phone number can only have digits',
                                      Zend_Validate_Digits::STRING_EMPTY =>
                                      'Phone number can not be empty'))),
             'age'     => array('Digits', new Zend_Validate_Between(16, 59),
                                'messages' => array(
                                'You do not qualify because of your age',
                                array(Zend_Validate_Between::NOT_BETWEEN =>
                                      'Age is not between the allowed range'))),
             'id'      => array('StringLength', new Zend_Validate_StringLength(8, 10),
                                'messages' => array(
                                'ID not valid',
                                array(Zend_Validate_StringLength::TOO_LONG =>
                                      'ID is too long',
                                      Zend_Validate_StringLength::TOO_SHORT =>
                                      'ID is too short'))),
             'date'    => array('Date', new Zend_Validate_Date(),
                                'messages' => array(
                                'Wrong date',
                                array(Zend_Validate_Date::INVALID =>
                                      'Date not recognized as valid',
                                      Zend_Validate_Date::FALSEFORMAT =>
                                      'Date format is incorrect',
                                      Zend_Validate_Date::NOT_YYYY_MM_DD =>
                                     'Date format must be YYYY-MM-DD'))),
              'url'      => $validurl
           );

        /**
         * Age, address and phone are also filtered
         * The true param in Zend_Filter_Alnum tells it to allows whitespaces
         */
        $filters = array('age'      => 'Int',
                         'address'  => array('Alnum', new Zend_Filter_Alnum(true)),
                         'phone'    => 'Digits');

        // Now we chain the validators, the filters and pass the post params on
        $input = new Zend_Filter_Input($filters, $validators);
        $input->setData($this->getRequest()->getParams());

        // Check whether errors occur
        if ($input->isValid())
        {
            $this->_helper->FlashMessenger->addMessage('Success!');
            $this->_helper->redirector('index');
        } else {
            $messages = $input->getMessages();
            foreach ($messages as $key => $value)
            {
                // Store all messages in FlashMessenger
                foreach ($value as $msg)
                {
                    $this->_helper->FlashMessenger->addMessage($msg);
                }
            }
        }

        // There are errors, the index action will show them...
        $this->_helper->redirector('index');
    }
}

Cómo crear un validador propio para Zend_Validator

Zend_Validate_Interface permite crear cualquier tipo de validador, aunque existe una clase Zend_Validate_Regex, he creado un validador de URLs que utiliza una expresión regular solo para que sirva como ejemplo.

En este post, está el ejemplo de implementación de la clase.

Este es el código:

class My_Validate_Url extends Zend_Validate_Abstract
{
    /**
     * Validation failure message key for when the value is an empty string
     */
    const STRING_EMPTY = 'stringEmpty';
    const NOT_MATCH    = 'urlNotMatch';
    
    /**
     * @var array
     */
    protected $_messageTemplates = array(
        self::NOT_MATCH    => "'%value%' does not match an url",
        self::STRING_EMPTY => "'%value% is an empty string"
    );
    
    public function isValid($value)
    {
        $valueString = (string) $value;
        
        $this->_setValue($valueString);
        
        if ('' === $valueString) {
            $this->_error(self::STRING_EMPTY);
            return false;
        } elseif (!preg_match('#https?://([-\w\.])+(:\d+)?(/([\w/_\.]*(\?\S+)?)?)?#', $valueString)) {
            $this->_error(self::NOT_MATCH);
            return false;
        }
        
        return true;
    }
}

Windows Live Admin Center PHP Client

Recently, I needed to implement a Soap Client for this web service from Microsoft. I read the SDK and browsed some examples, I also found another PHP implementation which required NuSOAP but I didn’t like because it was based on PHP 4.

I wanted to use the latest, so I used this implementation as an inspiration and build my own one using PHP 5 with SOAP and cURL.

This is something very basic, not all SOAP Calls are implemented. I think this is very self explanatory, so if you have something to ask please drop me a line: macuenca at GMail’s.

/**
*
* Windows Live Admin Center Client Class
* Author: Mauricio Cuenca
*
*/
class WinAdminCenter {
    private $username;
    private $password;
    private $wsdlfile = 'https://domains.live.com/service/managedomain.asmx?WSDL';
    private $soapver = SOAP_1_2;

    /**
     * Class constructor
     * @param string $username
     * @param string $password
     * @return resource
     */
    public function __construct($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
        $this->client = new SoapClient($this->wsdlfile, array('soap_version' => $this->soapver));
    }
    
    /**
     * Gets the URL where the member should obtain authentication
     * @param string $membername
     * @return string
    */
    private function getLoginURL($membername)
    {
        $obj = $this->client->GetLoginURL(array('memberName' => $membername));
        return $obj->GetLoginUrlResult;
    }
    
    /**
     * Obtains the template format that should be sent to the Web Service
     * @param string $username
     * @param string $password
     * @return string
    */
    private function getLoginDataTemplate($username, $password)
    {
        $template = $this->client->GetLoginDataTemplate();
        $template = str_replace(array('%NAME%', '%PASSWORD%'),
        array($this->username, $this->password),
        $template->GetLoginDataTemplateResult);
        return $template;
    }
    
    /**
     * Gets the authentication data which later will be used on all requests
     * @return string
     */
    private function getAuthData()
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->getLoginURL($this->username));
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getLoginDataTemplate($this->username, $this->password));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_AUTOREFERER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        
        return trim(curl_exec($ch));
    }
    
    /**
     * Checks wether the users exists or not
     * @param string $username
     * @return string
     */
    public function getUserState($username)
    {
        try {
            $result = $this->client->GetUserState(array('name' => $username,
            'authData' => $this->getAuthData()));
            return (string)$result->GetUserStateResult;
        } catch (SoapFault $sf) {
            trigger_error("SOAP Fault: (code: {$sf->faultcode},
            message: {$sf->faultstring})", E_USER_ERROR);
        }
    }
    
    /**
     * Creates a new user
     * @param string $email
     * @param string $password
     * @return object
    */
    public function addUser($email, $password)
    {
        $userdata = array('name' => $email,
        'password' => $password,
        'resetPassword' => false,
        'authData' => $this->getAuthData());
        
        try {
            return $this->client->AddUser($userdata);
        } catch (SoapFault $sf) {
            trigger_error("SOAP Fault: (code: {$sf->faultcode},
            message: {$sf->faultstring})", E_USER_ERROR);
        }
    }
}