# Написание валидаторов

Zend_Validate предоставляет набор наиболее часто используемых валидаторов, но разработчики часто сталкиваются с такими ситуациями, когда требуется написание своих валидаторов. В этом разделе описан процесс написания своих валидаторов.

Zend_Validate_Interface определяет три метода: `isValid()`, `getMessages()` и `getErrors()`, они должны реализовываться в ваших классах валидации. Объект, реализующий данный интерфейс, может быть добавлен в цепочку валидаторов через метод `Zend_Validate::addValidator()`, также такие объекты могут использоваться с классом Zend_Filter_Input.

Как вы наверное уже поняли из приведенного выше описания интерфейса Zend_Validate_Interface, классы валидации, предоставляемые Zend Framework, возвращают булево значение, означающее успех/неуспех валидации. Они также предоставляют информацию о том почему переданное значение не прошло валидацию. Возможность узнать причину того, почему значение не проходит валидацию, может быть полезна для различных целей, как, например, создание статистики для анализа удобства использования приложения.

Базовый функционал для сообщений об ошибке валидации реализован в классе Zend_Validate_Abstract. Для того, чтобы использовать этот функционал при создании класса валидации, просто наследуйте свой класс от Zend_Validate_Abstract. В классе-наследнике вы можете реализовать логику метода `isValid()`, а также определить шаблоны и переменные сообщений, которые соответствуют различным типам ошибок валидации. Если значение не проходит проверку, то `isValid()` должен возвращать `FALSE`. Если значение проходит проверку, то `isValid()` должен возвращать `TRUE`.

Обычно метод `isValid()` не должен бросать никаких исключений, за исключением тех ситуаций, когда невозможно определить, является ли валидным входное значение или нет. Например, бросок исключения уместен в случае, когда невозможно открыть файл (недоступен сервер LDAP, нет соединения с базой данных), и процесс валидации не может быть продолжен.

Создание простого класса валидации

Следующий пример демонстрирует создание простейшего валидатора. В данном случае правила валидации просты - входное значение должно иметь тип `float`.

    class MyValid_Float extends Zend_Validate_Abstract
    {
        const FLOAT = 'float';

        protected $_messageTemplates = array(
            self::FLOAT => "'%value%' не является числом с плавающей точкой"
        );

        public function isValid($value)
        {
            $this->_setValue($value);

            if (!is_float($value)) {
                $this->_error();
                return false;
            }

            return true;
        }
    }

В данном классе определен шаблон для единственного сообщения об ошибке валидации, который включает в себя встроенный "магический" параметр `%value%`. Вызов `_setValue()` подготавливает объект к подстановке проверяемого значения в сообщение об ошибке. Вызов `_error()` устанавливает причину ошибки валидации. Поскольку в этом классе только одно сообщение об ошибке, то нет необходимости передавать методу `_error()` имя шаблона сообщения об ошибке.

Написание классов валидации с подчиненными условиями

Следующий пример демонстрирует более сложный набор правил валидации, где требуется, чтобы входное значение было числом, находящимся в диапазоне между минимальным и максимальным значениями. Значение может не пройти валидацию по одной из следующих причин:

- Значение не является числом.

- Значение меньше минимально допустимого.

- Значение больше максимально допустимого.

Эти возможные причины переводятся в определения в классе:

    class MyValid_NumericBetween extends Zend_Validate_Abstract
    {
        const MSG_NUMERIC = 'msgNumeric';
        const MSG_MINIMUM = 'msgMinimum';
        const MSG_MAXIMUM = 'msgMaximum';

        public $minimum = 0;
        public $maximum = 100;

        protected $_messageVariables = array(
            'min' => 'minimum',
            'max' => 'maximum'
        );

        protected $_messageTemplates = array(
            self::MSG_NUMERIC => "'%value%' не является числом",
            self::MSG_MINIMUM => "'%value%' должен быть не меньше '%min%'",
            self::MSG_MAXIMUM => "'%value%' должен быть не больше '%max%'"
        );

        public function isValid($value)
        {
            $this->_setValue($value);

            if (!is_numeric($value)) {
                $this->_error(self::MSG_NUMERIC);
                return false;
            }

            if ($value < $this->minimum) {
                $this->_error(self::MSG_MINIMUM);
                return false;
            }

            if ($value > $this->maximum) {
                $this->_error(self::MSG_MAXIMUM);
                return false;
            }

            return true;
        }
    }

Открытые свойства `$minimum` и `$maximum` были созданы для установки нижней и верхней границ диапазона соответственно. В классе также определены две переменные сообщений, которые соответствуют этим открытым свойствам и позволяют использовать `min` и `max` в качестве магических параметров в шаблонах сообщений, так же, как и `value`.

Обратите вмимание, что если какая-либо из проверок в методе `isValid()` не была пройдена, то подготавливается соответствующее сообщение и метод сразу же возвращает `FALSE`. Таким образом, эти правила валидации являются последовательно-зависимыми. Это значит, что если одна из проверок не была пройден, то нет необходимости производить последующие проверки. Но это не обязательный случай, в следующем примере показан класс с независимыми правилами валидации, где объект валидации может возвращать несколько причин, по которым валидация не была пройдена.

Валидация с независимыми условиями и возвратом нескольких причин непрохождения валидации

Рассмотрим написание класса валидации для обеспечения стойкости пароля, где от пользователя требуется выбрать пароль, который соответствует определенным критериям в целях защиты пользовательского аккаунта. Предположим, что критерии безопасности требуют, чтобы пароль:

- был длиной как минимум 8 символов,

- содержал как минимум одну букву в верхнем регистре,

- содержал как минимум одну букву в нижнем регистре,

- содержал как минимум один цифровой символ.

Следующий класс реализует эти критерии валидации:

    class MyValid_PasswordStrength extends Zend_Validate_Abstract
    {
        const LENGTH = 'length';
        const UPPER  = 'upper';
        const LOWER  = 'lower';
        const DIGIT  = 'digit';

        protected $_messageTemplates = array(
            self::LENGTH => "'%value%' должен быть длиной как минимум 8 символов",
            self::UPPER  => "'%value%' должен содержать как минимум одну букву в верхнем регистре",
            self::LOWER  => "'%value%' должен содержать как минимум одну букву в нижнем регистре",
            self::DIGIT  => "'%value%' должен содержать как минимум один цифровой символ"
        );

        public function isValid($value)
        {
            $this->_setValue($value);

            $isValid = true;

            if (strlen($value) < 8) {
                $this->_error(self::LENGTH);
                $isValid = false;
            }

            if (!preg_match('/[A-Z]/', $value)) {
                $this->_error(self::UPPER);
                $isValid = false;
            }

            if (!preg_match('/[a-z]/', $value)) {
                $this->_error(self::LOWER);
                $isValid = false;
            }

            if (!preg_match('/\d/', $value)) {
                $this->_error(self::DIGIT);
                $isValid = false;
            }

            return $isValid;
        }
    }

Обратите внимание, что эти четыре проверки в методе `isValid()` не сразу же возвращают `FALSE`. Это позволяет классу валидации предоставлять все причины того, почему введенный пароль не отвечает требованиям валидации. Если, например, пользователь введет строку "`#$%`" в качестве пароля, то после `isValid()` вызов `getMessages()` вернет все четыре сообщения ошибки валидации.