<?xml version="1.0" encoding="UTF-8"?>
<!-- Reviewed: no -->
<sect1 id="zend.validate.writing_validators">

    <title>Написание валидаторов</title>

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

    <para>
        <classname>Zend_Validate_Interface</classname> определяет три метода:
        <code>isValid()</code>, <code>getMessages()</code> и
        <code>getErrors()</code>, они должны
        реализовываться в ваших классах валидации.
        Объект, реализующий данный интерфейс, может быть добавлен в цепочку
        валидаторов через метод <code>Zend_Validate::addValidator()</code>,
        также такие объекты могут использоваться с классом
        <link linkend="zend.filter.input"><classname>Zend_Filter_Input</classname></link>.
    </para>

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

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

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

    <example id="zend.validate.writing_validators.example.simple">

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

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

            <programlisting language="php"><![CDATA[
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;
    }
}
]]></programlisting>

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

    </example>

    <example id="zend.validate.writing_validators.example.conditions.dependent">

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

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

            <itemizedlist>
                <listitem>
                    <para>Значение не является числом.</para>
                </listitem>
                <listitem>
                    <para>Значение меньше минимально допустимого.</para>
                </listitem>
                <listitem>
                    <para>Значение больше максимально допустимого.</para>
                </listitem>
            </itemizedlist>
        </para>

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

            <programlisting language="php"><![CDATA[
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;
    }
}
]]></programlisting>

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

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

    </example>

    <example id="zend.validate.writing_validators.example.conditions.independent">

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

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

            <itemizedlist>
                <listitem>
                    <para>был длиной как минимум 8 символов,</para>
                </listitem>
                <listitem>
                    <para>содержал как минимум одну букву в верхнем регистре,</para>
                </listitem>
                <listitem>
                    <para>содержал как минимум одну букву в нижнем регистре,</para>
                </listitem>
                <listitem>
                    <para>содержал как минимум один цифровой символ.</para>
                </listitem>
            </itemizedlist>
        </para>

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

            <programlisting language="php"><![CDATA[
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;
    }
}
]]></programlisting>

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

    </example>

</sect1>
<!--
vim:se ts=4 sw=4 et:
-->
