A free and open-source book on ZF3 for beginners


9.7. Writing Own Validator

An alternative of using the Callback validator is writing your own validator class implementing the ValidatorInterface interface. Then, this validator may be used in forms of your web application.

To demonstrate how to create your own validator, we will write the PhoneValidator class encapsulating the phone validation algorithm we used with the Callback validator example.

As you might remember, the base concrete class for all standard validators is the AbstractValidator class. By analogy, we will also derive our custom PhoneValidator validator from that base class.

We plan to have the following methods in our PhoneValidator validator class (see table 9.15):

Table 9.15. Public methods of the Callback validator
Method name Description
__construct($options) Constructor. Accepts an optional argument $options which is needed to set validator options at once.
setFormat($format) Sets the phone format option.
getFormat() Returns the phone format option.
isValid($value) Returns true when the value is a valid phone number; otherwise returns false.
getMessages() If validation failed, this method will return an array of error messages.

For the PhoneValidator, we will have three possible error messages:

To start, create the PhoneValidator.php file in the Validator directory under the module's source directory 38. Put the following code into that file:

38) The PhoneValidator class may be considered as a service model, because its goal is to process data, not to store it. By convention, we store custom validators under the Validator directory.

<?php
namespace Application\Validator;

use Zend\Validator\AbstractValidator;

// This validator class is designed for checking a phone number for 
// conformance to the local or to the international format.
class PhoneValidator extends AbstractValidator 
{
  // Phone format constants.
  const PHONE_FORMAT_LOCAL = 'local'; // Local phone format.
  const PHONE_FORMAT_INTL  = 'intl';  // International phone format.
    
  // Available validator options.
  protected $options = [
    'format' => self::PHONE_FORMAT_INTL
  ];
    
  // Validation failure message IDs.
  const NOT_SCALAR  = 'notScalar';
  const INVALID_FORMAT_INTL  = 'invalidFormatIntl';
  const INVALID_FORMAT_LOCAL = 'invalidFormatLocal';
    
  // Validation failure messages.
  protected $messageTemplates = [
    self::NOT_SCALAR  => "The phone number must be a scalar value",
    self::INVALID_FORMAT_INTL => "The phone number must be in international format",
    self::INVALID_FORMAT_LOCAL => "The phone number must be in local format",
  ];
    
  // Constructor.
  public function __construct($options = null) 
  {
    // Set filter options (if provided).
    if(is_array($options)) {
            
      if(isset($options['format']))
        $this->setFormat($options['format']);
      }
        
      // Call the parent class constructor.
      parent::__construct($options);
  }
    
  // Sets phone format.
  public function setFormat($format) 
  {
    // Check input argument.
    if($format!=self::PHONE_FORMAT_LOCAL && 
       $format!=self::PHONE_FORMAT_INTL) {            
      throw new \Exception('Invalid format argument passed.');
    }
        
    $this->options['format'] = $format;
  }
    
  // Validates a phone number.
  public function isValid($value) 
  {
    if(!is_scalar($value)) {
      $this->error(self::NOT_SCALAR);
      return false; // Phone number must be a scalar.
    }
            
    // Convert the value to string.
    $value = (string)$value;
        
    $format = $this->options['format'];
    
    // Determine the correct length and pattern of the phone number,
    // depending on the format.            
    if($format == self::PHONE_FORMAT_INTL) {
      $correctLength = 16;
      $pattern = '/^\d \(\d{3}\) \d{3}-\d{4}$/';
    } else { // self::PHONE_FORMAT_LOCAL
      $correctLength = 8;
      $pattern = '/^\d{3}-\d{4}$/';
    }
        
    // First check phone number length
    $isValid = false;
    if(strlen($value)==$correctLength) {            
      // Check if the value matches the pattern.
      if(preg_match($pattern, $value))                    
        $isValid = true;
    }
       
    // If there was an error, set error message.
    if(!$isValid) {            
      if($format==self::PHONE_FORMAT_INTL)
        $this->error(self::INVALID_FORMAT_INTL);
      else
        $this->error(self::INVALID_FORMAT_LOCAL);
    }
        
    // Return validation result.
    return $isValid;
  }
}

From line 2, you can see that the validator class lives in the Application\Validator namespace.

In line 8, we define the PhoneValidator class. We derive our validator class from the AbstractValidator base class to reuse the functionality it provides. Line 4 contains the short alias for the AbstractValidator class.

In lines 11-12, for convenience, we define the phone format constants (PHONE_FORMAT_INTL for international format and PHONE_FORMAT_LOCAL for local format). These are the equivalents of the "intl" and "local" strings, respectively.

In lines 15-17, we define the $options private array variable which is an array having the single key named "format". This key will contain the phone format option for our validator.

In lines 20-22, we define the error message identifiers. We have three identifiers (NOT_SCALAR, INVALID_FORMAT_INTL and INVALID_FORMAT_LOCAL), because our validator may generate three different error messages. These identifiers are intended for distinguishing different error messages by machine, not by human.

In lines 25-29, we have the $messageTemplates array variable that contains mapping before error message identifiers and their textual representations. The textual messages are intended for displaying to a human.

In lines 32-43, we have the constructor method which takes the single argument $options. When constructing the validator manually, you may omit this parameter. But, when the validator is constructed by the factory class, the factory will pass validation options to validator's constructor through this argument.

In lines 46-55, we have the setFormat() method that allow to set the current phone format, respectively.

In lines 58-98, we have the isValid() method. This method encapsulates the phone number checking algorithm. It takes the $value parameter, performs the regular expression match, and returns true on success.

On failure, the isValid() method it returns the boolean false, and the list of errors can be retrieved by the getMessages() method.

You might notice that we didn't define the getMessages() method in our PhoneValidator class. This is because we inherited this method from the AbstractValidator base class. Inside of our isValid() method, for generating error messages, we also used the error() protected method provided by the base class (lines 61, 91, 93).

The PhoneValidator is only for demonstration of how to write custom validators in ZF3. Implementing a validator that will work correctly against all possible phone numbers in the world is beyond the scope of this book. If you'd like to use this validator in a real-life app, you will definitely need to improve it. For example, take a look at the libphonenumber PHP library from Google.

9.7.1. Using the PhoneValidator Class

When the PhoneValidator validator class is ready, you can easily start using it in the feedback form (or in another form) as follows. It is assumed that you call the following code inside of the ContactForm::addInputFilter() method:

$inputFilter->add([
      'name'     => 'phone',
      'required' => true,                
      'validators' => [
        [
          [
            'name' => PhoneValidator::class,
            'options' => [
              'format' => PhoneValidator::PHONE_FORMAT_INTL
            ]                        
          ],
        ],
        // ...
      ],                
      // ...
    ]);

You can see how the PhoneValidator validator works in the Form Demo sample application bundled with this book. Open the "http://localhost/contactus" page in your web browser. If you enter some phone number in an incorrect format, the validator will display an error (see figure 9.3).

Figure 9.3. Phone number validation error Figure 9.3. Phone number validation error

If you wish, you can use the PhoneValidator outside of forms, as shown in code example below:

<?php 
use Application\Validator\PhoneValidator;

// Create PhoneValidator validator
$validator = new PhoneValidator();

// Configure the validator.
$validator->setFormat(PhoneValidator::PHONE_FORMAT_INTL);

// Validate a phone number
$isValid = $validator->isValid('1 (234) 567-8901'); // Returns true.
$isValid2 = $validator->isValid('12345678901'); // Returns false.

if(!$isValid2) {
  // Get validation errors.
  $errors = $validator->getMessages();
}

Top