A free and open-source book on ZF3 for beginners


9.7. Написание своего валидатора

Альтернативой использованию валидатора Callback является написание своего собственного класса валидатора, реализующего интерфейс ValidatorInterface. Этот валидатор затем может быть использован в формах вашего веб-приложения.

Чтобы продемонстрировать создание своего валидатора, мы напишем класс PhoneValidator, инкапсулирующий алгоритм валидации номера, который мы использовали в предыдущем примере.

Как вы возможно помните, базовым классом для всех стандартных валидаторов является класс AbstractValidator. По аналогии мы также будем наследовать наш валидатор PhoneValidator от этого базового класса.

Мы планируем иметь следующие методы в классе валидатора PhoneValidator (см. таблицу 9.15):

Таблица 9.15. Public-методы валидатора Callback
Имя метода Описание
__construct($options) Конструктор - принимает опциональный аргумент $options, который нужен для того, чтобы сразу задать опции валидатора.
setFormat($format) Задает опцию формата номера.
getFormat() Возвращает опцию формата номера.
isValid($value) Возвращает true, если значение - действительный телефонный номер; иначе возвращает false.
getMessages() При неудачной валидации этот метод вернет массив сообщений об ошибках.

Для PhoneValidator у нас будет три возможных сообщения об ошибках:

Сперва создайте файл PhoneValidator.php в каталоге Validator под корневым каталогом модуля 38. Поместите в этот файл следующий код:

38) Класс PhoneValidator можно считать моделью сервиса, так как его задачей является обработка данных, а не их хранение. Все пользовательские валидаторы принято хранить под каталогом Validator.

<?php
namespace Application\Validator;

use Zend\Validator\AbstractValidator;

// Этот класс валидатора предназначен для проверки телефонного номера на
// соответствие локальному или международному формату.
class PhoneValidator extends AbstractValidator 
{
  // Константы формата номера.
  const PHONE_FORMAT_LOCAL = 'local'; // Локальный формат номера.
  const PHONE_FORMAT_INTL  = 'intl';  // Международный формат номера.
    
  // Доступные опции валидатора.
  protected $options = [
    'format' => self::PHONE_FORMAT_INTL
  ];
    
  // ID сообщений об ошибках валидации.
  const NOT_SCALAR  = 'notScalar';
  const INVALID_FORMAT_INTL  = 'invalidFormatIntl';
  const INVALID_FORMAT_LOCAL = 'invalidFormatLocal';
    
  // Сообщения об ошибках валидации.
  protected $messageTemplates = [
    self::NOT_SCALAR  => "Номер телефона должен быть скалярным значением",
    self::INVALID_FORMAT_INTL => "Номер телефона должен быть в международном формате",
    self::INVALID_FORMAT_LOCAL => "Номер телефона должен быть в локальном формате",
  ];
    
  // Конструктор.
  public function __construct($options = null) 
  {
    // Задаем опции валидатора (если они предоставлены).
    if(is_array($options)) {
            
      if(isset($options['format']))
        $this->setFormat($options['format']);
      }
        
      // Вызываем конструктор родительского класса.
      parent::__construct($options);
  }
    
  // Задаем формат номера.
  public function setFormat($format) 
  {
    // Проверяем входной аргумент.
    if($format!=self::PHONE_FORMAT_LOCAL && 
       $format!=self::PHONE_FORMAT_INTL) {            
      throw new \Exception('Invalid format argument passed.');
    }
        
    $this->options['format'] = $format;
  }
    
  // Валидируем номер телефона.
  public function isValid($value) 
  {
    if(!is_scalar($value)) {
      $this->error(self::NOT_SCALAR);
      return false; // Номер должен быть скалярной величиной.
    }
            
    // Преобразуем значение в строку.
    $value = (string)$value;
        
    $format = $this->options['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}$/';
    }
        
    // Сперва проверяем длину телефонного номера
    $isValid = false;
    if(strlen($value)==$correctLength) {            
      // Проверяем, соответствует ли значение шаблону
      if(preg_match($pattern, $value))                    
        $isValid = true;
    }
       
    // Если была ошибка, задаем сообщение об ошибке
    if(!$isValid) {            
      if($format==self::PHONE_FORMAT_INTL)
        $this->error(self::INVALID_FORMAT_INTL);
      else
        $this->error(self::INVALID_FORMAT_LOCAL);
    }
        
    // Возвращаем результат валидации.
    return $isValid;
  }
}

Как видите из строки 2, класс валидатора содержится в пространстве имен Application\Validator.

В строке 8 мы определяем класс PhoneValidator. Мы наследуем наш валидатор от базового класса AbstractValidator для повторного использования предоставляемой им функциональности. Строка 4 содержит псевдоним для класса AbstractValidator.

В строках 11-12 мы для удобства определяем константы форматов номера (PHONE_FORMAT_INTL для международного формата и PHONE_FORMAT_LOCAL для локального). Эти константы - эквиваленты строк "intl" и "local" соответственно.

В строках 15-17 мы определяем private-переменную $options, которая является массивом, имеющим один единственный ключ под названием "format". Этот ключ будет содержать опцию формата номера для нашего валидатора.

В строках 20-22 мы определяем идентификаторы сообщений об ошибках. В нашем случае идентификаторов три (NOT_SCALAR, INVALID_FORMAT_INTL и INVALID_FORMAT_LOCAL), так как наш валидатор может генерировать три разных сообщения об ошибках. Эти идентификаторы предназначены для распознавания различных сообщений машиной, а не человеком.

В строках 25-29 находится переменная массива, которая содержит соответствия между идентификаторами сообщений об ошибках и их текстовыми представлениями. Текстовые сообщения предназначены для показа человеку.

В строках 32-43 находится метод конструктора, который принимает один аргумент $options. При создании валидатора вручную, этот параметр можно пропустить. Однако, когда валидатор создается классом фабрики, фабрика будет передавать опции валидатора его конструктору через этот аргумент.

В строках 46-55 находится метод setFormat(), позволяющий задать текущий формат номера.

Строки 58-98 содержат метод isValid(). Этот метод инкапсулирует алгоритм проверки телефонного номера. Он принимает параметр $value, осуществляет сопоставление с регулярным выражением и возвращает true при удачном исходе.

В случае неудачной валидации метод isValid() возвращает булевое false, а список ошибок можно извлечь методом getMessages().

Вы могли заметить, что мы не определили метод getMessages() в нашем классе PhoneValidator. Это потому, что мы наследовали этот метод от базового класса AbstractValidator. Внутри метода isValid(), для генерации сообщений об ошибках, мы также использовали предоставляемый базовым классом protected-метод error() (строки 61, 91, 93).

PhoneValidator приведен только для демонстрации того, как писать собственные валидаторы в ZF3. Реализация валидатора, который бы правильно обрабатывал все возможные номера телефонов в мире находится за пределами обсуждения данной книги. Если вы захотите использовать данный валидатор в своем приложении, вам определенно придется улучшить его. Например, взгляните на PHP библиотеку libphonenumber от Google.

9.7.1. Использование класса PhoneValidator

Как только класс валидатора PhoneValidator будет готов, вы легко можете начать его использовать в форме обратной связи (или других формам) как показано ниже. Предполагается, что вы вызываете следующий код внутри метода ContactForm::addInputFilter():

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

Вы можете посмотреть, как работает валидатор PhoneValidator в примере Form Demo - приложении, которое идет вместе с этой книгой. Откройте страницу "http://localhost/contactus" в своем браузере. Если вы введете какой-либо телефонный номер в некорректном формате, валидатор отобразит ошибку (см. рисунок 9.3).

Рисунок 9.3. Ошибка валидации телефонного номера Рисунок 9.3. Ошибка валидации телефонного номера

Если хотите, можете использовать PhoneValidator вне форм, как показано во фрагменте кода ниже:

<?php 
use Application\Validator\PhoneValidator;

// Создаем валидатор PhoneValidator
$validator = new PhoneValidator();

// Настраиваем валидатор.
$validator->setFormat(PhoneValidator::PHONE_FORMAT_INTL);

// Валидируем номер телефона
$isValid = $validator->isValid('1 (234) 567-8901'); // Возвращает true.
$isValid2 = $validator->isValid('12345678901'); // Возвращает false.

if(!$isValid2) {
  // Получаем ошибки валидации.
  $errors = $validator->getMessages();
}

Top