Search
Write a publication
Pull to refresh

PHP: Валидация веб-форм с помощью спецификаций

Валидация веб-форм на PHP — тема довольно избитая. Об этом написана куча интересных и не очень статей. В этой статье я хочу ещё раз поднять эту тему, рассказав об идеи валидации форм на основе шаблона проектирования «Спецификация».


Шаблон «Спецификация»



Допустим, у нас есть множество объектов, каждый из которых мы хотим проверить на соответствие некоторому критерию. Согласно шаблону «Спецификация», для решения этой проблемы необходимо оформить заданный критерий (спецификацию) в виде объекта, снабжённого методом 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 был бы гораздо удобней, но в случае работы без фреймворка (да, такое в наши дни бывает), вышеописанный подход значительно упрощает разработку веб-форм.

_________
Текст подготовлен в
ХабраРедакторе
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.