Валидация веб-форм на PHP — тема довольно избитая. Об этом написана куча интересных и не очень статей. В этой статье я хочу ещё раз поднять эту тему, рассказав об идеи валидации форм на основе шаблона проектирования «Спецификация».
Допустим, у нас есть множество объектов, каждый из которых мы хотим проверить на соответствие некоторому критерию. Согласно шаблону «Спецификация», для решения этой проблемы необходимо оформить заданный критерий (спецификацию) в виде объекта, снабжённого методом IsSatisfiedBy( $oSomeObject ), c помощью которого будет проводится проверка соответствия этому критерию (спецификации) объекта $oSomeObject. Другими словами, каждая спецификация должна реализовывать следующий интерфейс:
Шаблон «Спецификация»
Допустим, у нас есть множество объектов, каждый из которых мы хотим проверить на соответствие некоторому критерию. Согласно шаблону «Спецификация», для решения этой проблемы необходимо оформить заданный критерий (спецификацию) в виде объекта, снабжённого методом IsSatisfiedBy( $oSomeObject ), c помощью которого будет проводится проверка соответствия этому критерию (спецификации) объекта $oSomeObject. Другими словами, каждая спецификация должна реализовывать следующий интерфейс:
/**<br> * Интерфейс спецификации<br> */<br>interface ISpecification<br>{<br> /**<br> * Проверка соответствия спецификации<br> *<br> * @param mixed $mObject<br> * @return bool Флаг удовлетворённости<br> */<br> public function IsSatisfiedBy( $mObject );<br>}<br><br>* This source code was highlighted with Source Code Highlighter.
Валидация форм
Применение данного шаблона к валидации веб-форм требует некоторых правок. Точнее сказать, после предложенных ниже правок от шаблона в принципе мало что останется… но ведь на то, он и шаблон, что бы приспосабливать его под свои нужды :)
Базовые спецификации
Первым делом создадим набор базовых спецификаций, которые в дальнейшем будут использоваться для построения более сложных спецификаций. В нашем случае базовыми спецификациями будут:
- Существование поля (CFieldExistsSpecification)
- Заполненность поля (CFieldNotEmptySpecification)
- Поле больше либо равно (CGreaterOrEqualSpecification)
- Поле меньше либо равно (CLessOrEqualSpecification)
- Соответствие поля регулярному выражению (CRegexpMatchSpecification)
- Соответствие поля «белому листу» (CWhiteListMatchSpecification)
Код такой спецификации будет примерно следующим:
/**<br> * Проверка соответствия поля регулярному выражению<br> */<br>class CRegexpMatchSpecification implements ISpecification<br>{<br> private $_sFieldValue;<br><br> function __construct( $sFieldValue )<br> {<br> $this->_sFieldValue = $sFieldValue;<br> }<br> <br> public function IsSatisfiedBy( $mData )<br> {<br> if( !preg_match( $mData, $this->_sFieldValue ) )<br> throw new Exception('INVALID');<br> }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.
Как видно, эти спецификации не соответствуют шаблону в полной мере, так как их метод IsSatisfiedBy не возвращает флаг успеха, а бросает исключение в случае неудачи. Однако, я не случайно назвал эти спецификации «базовыми». На основе этих спецификаций мы сможем создать специализированные спецификации с удобным API, предусматривающим:
- Возможность настройки параметров спецификации
- Возможность получения кода ошибки
Специализированные спецификации
Опищем базовый класс для специализированных спецификаций:
/**<br> * Абстрактная спецификация<br> */<br>abstract class CAbstractSpecification implements ISpecification<br>{<br> /**<br> * Название поля<br> * @var string<br> */<br> protected $_sFieldName;<br> <br> /**<br> * Значение поля<br> * @var string<br> */<br> protected $_sFieldValue;<br> <br> /**<br> * Код ошибки<br> * @var string<br> */<br> protected $_sErrorCode;<br> <br> /**<br> * Параметры спецификации<br> * @var array<br> */<br> protected $_aParam;<br><br> /**<br> * Конструктор<br> *<br> * @param string $sFieldName Имя поля<br> * @param array $aParam Параметры спецификации<br> */<br> function __construct( $sFieldName, array $aParam = array() )<br> {<br> $this->_sFieldName = $sFieldName;<br> $this->_aParam = $aParam;<br> }<br> <br> /**<br> * Проверка соответствия спецификации<br> *<br> * @param array $aData<br> * @return bool<br> */<br> public function IsSatisfiedBy( $aData )<br> {<br> $bIsSatisfied = true;<br> <br> // Начало попытки пройти соответствие спецификации<br> try<br> {<br> // Флаг необходимости поля<br> $bRequired = ( isset( $this->_aParam['required'] ) ) ? $this->_aParam['required'] : true;<br> <br> // Поле должно быть заполнено<br> if( $bRequired )<br> {<br> $oSpecNotEmpty = new CFieldNotEmptySpecification( $this->_sFieldName );<br> $oSpecNotEmpty->IsSatisfiedBy( $aData );<br> }<br> <br> // Поле может быть пустым<br> else<br> {<br> $oSpecExists = new CFieldExistsSpecification( $this->_sFieldName );<br> $oSpecExists->IsSatisfiedBy( $aData ); <br> }<br> <br> // Сохраняем значение поля<br> $this->_sFieldValue = $aData[ $this->_sFieldName ];<br> <br> // В случае, если поле заполнено, проходим спецификацию<br> if( !empty( $this->_sFieldValue ) )<br> $this->_Specify( $aData );<br> }<br> <br> // Обработка ошибок<br> catch( Exception $ex )<br> {<br> $this->_sErrorCode = $ex->getMessage();<br> $bIsSatisfied = false;<br> }<br> <br> return $bIsSatisfied;<br> }<br> <br> /**<br> * Логика спецификации<br> *<br> * @param array $aData Данные для спецификации<br> */<br> abstract protected function _Specify( $aData );<br> <br> /**<br> * Получение ошибки спецификации<br> * <br> * @return array<br> */<br> public function GetError()<br> {<br> return array(<br> 'FieldName' => $this->_sFieldName,<br> 'ErrorCode' => $this->_sErrorCode<br> );<br> }<br> <br>} // Конец класса CAbstractSpecification<br><br>* This source code was highlighted with Source Code Highlighter.
Теперь на основе бызовых спецификаций и созданного абстрактного класса мы без труда можем создавать спецификации для любого поля нашей формы. Рассмотрим для примера спецификацию имени пользователя:
/**<br> * Проверка имени<br> */<br>class CNameSpecification extends CAbstractSpecification <br>{<br> protected function _Specify( $aData )<br> {<br> $oSpecGreaterOrEqual = new CGreaterOrEqualSpecification( mb_strlen( $this->_sFieldValue ) );<br> $oSpecGreaterOrEqual->IsSatisfiedBy( FORM_NAME_SIZE_MIN );<br> <br> $oSpecLessOrEqual = new CLessOrEqualSpecification( mb_strlen( $this->_sFieldValue ) );<br> $oSpecLessOrEqual->IsSatisfiedBy( FORM_NAME_SIZE_MAX );<br> <br> $oSpecRegexpMatch = new CRegexpMatchSpecification( $this->_sFieldValue );<br> $oSpecRegexpMatch->IsSatisfiedBy( '/^[\p{L}]+$/i' );<br> }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.
Пример использования
И так, наши спецификации готовы. Отныне для проверки данных с веб-формы нам достаточно создать необходимый набор спецификаций и проверить их на наличие ошибок. Выглядеть это будет следующим образом:
// Создание спецификаций<br>$aSpec = array();<br><br>// Общие поля<br>$aSpec[] = new CNameSpecification( 'Name' );<br>$aSpec[] = new CNameSpecification( 'SurName' );<br>$aSpec[] = new CEmailSpecification( 'Email' );<br>$aSpec[] = new CPhoneSpecification( 'Phone' );<br>$aSpec[] = new CPhoneSpecification( 'PhoneAdd', array(<br> 'required' => false<br>) );<br>$aSpec[] = new CRegionSpecification( 'Region', array(<br> 'whiteList' => $aRegion<br>) );<br>$aSpec[] = new CZipCodeSpecification( 'ZipCode' );<br>$aSpec[] = new CAddressSpecification( 'Address' );<br><br>// Проверка спецификаций<br>$aFormError = array();<br>foreach( $aSpec as $oSpecification )<br>{<br> if( !$oSpecification->IsSatisfiedBy( $_POST ) )<br> {<br> $aError = $oSpecification->GetError();<br> $aFormError[ $aError['FieldName'] ] = $aError;<br> }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.
Конечно, тот же Zend_Form из ZF был бы гораздо удобней, но в случае работы без фреймворка (да, такое в наши дни бывает), вышеописанный подход значительно упрощает разработку веб-форм.
_________
Текст подготовлен в ХабраРедакторе