A free and open-source book on ZF3 for beginners

Translation into this language is not yet finished. You can help this project by translating the chapters and contributing your changes.

9.7. Escribir nuestro propio validador

Una alternativa al uso del validador Callback es escribir nuestra propia clase validadora, implementando la interfaz ValidatorInterface. Y luego, este validador se puede usar en los formulario de nuestra aplicación web.

Para demostrar como crear nuestro propio validador, escribiremos la clase PhoneValidator, encapsulando el algoritmo de validación que usamos en el ejemplo del validador Callback.

Como podemos recordar, la clase concreta base para todos los validadores estándares es la clase AbstractValidator. Por analogía, también derivaremos nuestro validador personalizado PhoneValidator de esta clase base.

Planeamos tener los siguientes métodos en nuestra clase validadora PhoneValidator (ver tabla 9.15):

Tabla 9.15. Métodos públicos del validador Callback
Nombre del método Descripción
__construct($options) Constructor. Acepta un argumento opcional, $options, que es necesario para colocar las opciones del validador.
setFormat($format) Coloca la opción: formato de número de teléfono.
getFormat() Regresa la opción del formato de número de telefónico.
isValid($value) Regresa true cuando el valor es un número de teléfono valido, de lo contrario regresa false.
getMessages() Si la validación falla, el método regresa un arreglo de mensajes de error.

Para la clase PhoneValidator, tendremos tres posibles mensajes de error:

Para comenzar, creamos el archivo PhoneValidator.php en la carpeta Validator que está dentro de la carpeta fuente del módulo 38. Colocamos el siguiente código dentro del archivo:

38) La clase PhoneValidator se puede considerar como un modelo de servicio, porque su objetivo es procesar datos y no guardarlos. Por convención, guardamos el validador personalizado dentro de la carpeta Validator.

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

En la línea 2, podemos ver que la clase validadora pertenece al espacio de nombres Application\Validator.

En la línea 8, definimos la clase PhoneValidator. Derivamos nuestra clase validadora de la clase base AbstractValidator para reusar la funcionalidad que provee. La línea 4 contiene el alias para la clase AbstractValidator.

Entre las líneas 11-12, por conveniencia, definimos las constantes para el formato de número de teléfono (PHONE_FORMAT_INTL para el formato internacional y PHONE_FORMAT_LOCAL para el formato local). Estas son equivalentes respectivamente a las cadenas de caracteres «intl» y «local».

En las líneas 15-17, definimos la variable privada $options que es un arreglo que tiene una sola llave llamada «format». Esta llave contendrá la opción del formato de número de teléfono para nuestro validador.

En las líneas 20-22, definimos los identificadores de mensajes de error. Tenemos tres identificadores (NOT_SCALAR, INVALID_FORMAT_INTL e INVALID_FORMAT_LOCAL), porque nuestro validador puede generar tres diferentes mensajes de error. Estos identificadores se usan para que la maquina, y no los humanos, distingan los diferentes mensajes de error.

Entre las líneas 25-29, tenemos la variable de tipo arreglo $messageTemplates que contiene la asociación entre los identificadores de mensajes de error anteriores y su representación textual. Los mensajes textuales se mostrarán a los humanos.

En las líneas 32-43, tenemos al método constructor que toma a la variable $options como único argumento. Cuando construimos el validador manualmente, podemos omitir este parámetro. Pero, cuando el validador es construido por la clase fábrica, la fábrica pasará las opciones al constructor del validador a través de este argumento.

En las líneas 46-55, tenemos el método setFormat() que permite colocar el formato de teléfono actual.

En las líneas 58-98, tenemos al método isValid(). Este método encapsula el algoritmo que revisa el número de teléfono. Toma a la variable $value como parámetro, ejecuta la comparación con la expresión regular y regresa true en caso de éxito.

En caso de falla, el método isValid() regresa el booleano false y la lista de errores, esta se pueden recuperar con el método getMessages().

Notemos que no se define el método getMessages() en nuestra clase PhoneValidator. Esto es porque heredamos este método de la clase base AbstractValidator. Dentro de nuestro método isValid(), para generar mensajes de error, usamos el método protegido error() que provee la clase base (líneas 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. Usar la clase PhoneValidator

Cuando la clase validadora PhoneValidator está lista, podemos fácilmente comenzar a usarla en el formulario de contacto (o en otro formulario) de la siguiente manera. Se asume que llamamos el siguiente código dentro del método ContactForm::addInputFilter():

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

Podemos ver al validador PhoneValidator funcionando en la aplicación de ejemplo Form Demo que está disponible junto a este libro. Abrimos la página «http://localhost/contactus» en nuestro navegador web. Si ingresamos algún número de teléfono en un formato incorrecto, el validador mostrara un error (ver figura 9.3).

Figura 9.3. Error de validación de número de teléfono Figura 9.3. Error de validación de número de teléfono

Si lo deseamos, podemos usar la clase PhoneValidator fuera de los formularios como se muestra en el código de ejemplo más abajo:

<?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