Как стать автором
Поиск
Написать публикацию
Обновить

Создание мастера на PHP

Предисловие



Наверное каждый php-разработчик раз в жизни сталкивался с задачей создания какого-либо мастера — скрипта, проводящего пользователя через последовательность шагов, с целью получения от него некоторой информации. Самым популярным примером мастера пожалуй можно назвать процесс оформления заказа в интернет-магазине. В данной статье я хочу рассказать о своих наработках в данной области. Статья ни в коем случае не претендует на man по созданию мастеров, а лишь излагает мои наработки по данной теме.




Постановка задачи



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



  • Возможность добавления произвольных шагов в мастер;
  • Контроль последовательности выполнения шагов;
  • Возможность прямого перехода к любому доступному шагу.


Требования к шагу мастера вытекают непосредственно из требований к самому мастеру. Шаг должен иметь возможность:



  • Подготовить данные для своего отображения;
  • Ответить, достаточно ли данных в мастере, чтобы считаться пройденным;
  • Произвести обработку полученных от пользователя данных.


Реализация



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



/**<br> * Шаг Мастера<br> */<br>abstract class CMasterStep<br>{<br>    /**<br>     * Данные из запроса клиента<br>     * @var array<br>     */<br>    protected $_aData;<br>    <br>    /**<br>     * Настройки<br>     * @var array<br>     */<br>    protected $_aParam;<br>    <br>    /**<br>     * Результаты работы<br>     * @var array<br>     */<br>    protected $_aResult;<br>    <br><br>    /**<br>     * Конструктор шага<br>     *<br>     * @param array $aData      Данные из запроса клиента<br>     * @param array $aParam     Настройки<br>     * @param array $aResult    Результаты работы<br>     */<br>    function __construct( &$aData, $aParam, &$aResult )<br>    {<br>        // Инициализация переменных<br>        $this->_aData    = &$aData;<br>        $this->_aParam   = $aParam;<br>        $this->_aResult  = &$aResult;<br>        <br>        // Вызов метода инициализации<br>        $this->_Init();<br>    }<br>    <br>    /**<br>     * Инициализация шага<br>     *<br>     * Метод предназначен для инициализации шага, выборки априорных<br>     * данных и так далее. Вызов метода происходит в конце конструктора<br>     * класса. Для того, что бы изменить поведение метода для какого-либо<br>     * класса, необходимо перегрузить этот метод в соответствующем классе<br>     */<br>    protected function _Init()<br>    {<br>    }<br>    <br>    /**<br>     * Проверка завершённости шага<br>     * <br>     * Метод проверяет, пройден ли данный шаг на текущий момент<br>     * <br>     * @return bool<br>     */<br>    abstract public function IsComplete();<br>    <br>    /**<br>     * Подготовка данных для отображения шага<br>     * <br>     * Метод добавляет в массив результатов работы данные для шаблона<br>     * текущего шага<br>     */<br>    abstract public function PrepareData();<br>    <br>    /**<br>     * Проверка и сохранение данных шага<br>     *<br>     * Метод проверяет полученные от пользователя данные. В случае,<br>     * если данные корректны, метод сохраняет их и возвращает true.<br>     * В случае неудачи метод записывает в массив результатов работы<br>     * массив с ошибками ($_aResult['Error']) и возвращает false<br>     * <br>     * @return bool<br>     */<br>    abstract public function Submit();<br>    <br>}    // Конец класса "Шаг Мастера"<br><br>* This source code was highlighted with Source Code Highlighter.

Для реализации мастера создадим класс, с помощью которого мы сможем добавлять шаги, проверять доступность некоторого шага, отображать и обрабатывать указанный шаг. Так же класс мастера будет отвечать за хранение данных, полученных от пользователя (в качестве хранилища используется сессия, точнее «пространство имён» в сессии):



/**<br> * Мастер<br> * <br> * Суть работы мастера сводится к обработке данных, полученных от<br> * пользователя и формированию массива результатов работы на основе<br> * обработки данных шагами мастера.<br> */<br>class CMaster<br>{<br>  /**<br>   * Шаги мастера<br>   * @var array<br>   */<br>  private $_aSteps;<br>  <br>  /**<br>   * Результат работы<br>   * @var array<br>   */<br>  private $_aResult;<br>  <br>  /**<br>   * Пространство имён, для хранения данных в сессии<br>   */<br>  const NameSpace = 'Master';<br>  <br><br>  /**<br>   * Конструктор<br>   * <br>   * @param array $aResult Результат работы<br>   */<br>  function __construct( &$aResult )<br>  {<br>    $this->_aSteps  = array();<br>    $this->_aResult = &$aResult;<br>  }<br>  <br>  /**<br>   * Добавление шага в мастер<br>   *<br>   * @param integer $iStepNumber  Номер шага<br>   * @param CSlMasterStep $oStep  Объект шага<br>   */<br>  public function AddStep( $iStepNumber, CMasterStep $oStep )<br>  {<br>    $this->_aSteps[ $iStepNumber ] = $oStep;<br>  }<br>  <br>  /**<br>   * Получение количества шагов в мастере<br>   * <br>   * @return int<br>   */<br>  public function GetStepsCount()<br>  {<br>    return sizeof( $this->_aSteps );<br>  }<br>  <br>  /**<br>   * Сохранение данных в мастере<br>   *<br>   * @param string $sDataKey    Ключ<br>   * @param mixed  $mDataValue  Значение<br>   */<br>  public static function SetData( $sDataKey, $mDataValue )<br>  {<br>    if( !isset( $_SESSION[ self::NameSpace ] ) )<br>      $_SESSION[ self::NameSpace ] = array();<br>      <br>    $_SESSION[ self::NameSpace ][ $sDataKey ] = $mDataValue;<br>  }<br>  <br>  /**<br>   * Получение данных из мастера<br>   *<br>   * @param string $sDataKey  Ключ<br>   * @return mixed<br>   */<br>  public static function GetData( $sDataKey )<br>  {<br>    $mResult = null;<br>    <br>    if( isset( $_SESSION[ self::NameSpace ] )  && <br>      isset( $_SESSION[ self::NameSpace ][ $sDataKey ] )<br>    )<br>      $mResult = $_SESSION[ self::NameSpace ][ $sDataKey ];<br>      <br>    return $mResult;<br>  }<br>  <br>  /**<br>   * Очистка мастера<br>   */<br>  public static function Clear()<br>  {<br>    if( isset( $_SESSION[ self::NameSpace ] ) )<br>      unset( $_SESSION[ self::NameSpace ] );<br>  }<br>  <br>  /**<br>   * Проверка доступности шага<br>   * <br>   * Метод проверяет, доступен ли шаг с указанным номером.<br>   * По сути процесс сводится к перебору всех шагов начиная с нулевого<br>   * и проверки завершённости каждого из них. В случае когда заданный<br>   * шаг оказывается недоступным, возвращается номер первого незавершенного<br>   * шага по порядку<br>   *<br>   * @param integer $iTargetStep Номер шага<br>   * @return integer<br>   */<br>  private function GetAvaibleStep( $iTargetStep )<br>  {<br>    // Целевой шаг не должен превышать максимально доступный<br>    $iMaxStep = max( array_keys( $this->_aSteps ) );<br>    if( $iTargetStep > $iMaxStep )<br>      $iTargetStep = $iMaxStep;<br><br>    // Проверка завершённости предыдущих шагов<br>    for( $iStep = 0; $iStep < $iTargetStep; $iStep++ )<br>    {<br>      if( !$this->_aSteps[ $iStep ]->IsComplete() )<br>        break;<br>    }<br><br>    return $iStep;<br>  }<br>  <br>  /**<br>   * Отображение шага<br>   * <br>   * Метод подготавливает необходимые данные для шаблона шага,<br>   * вызвав метод PrepareData объекта соответствующего шага.<br>   * После этого в массиве результатов работы выставляется<br>   * переменная _aResult['Step'], указывающая, какой шаг должен<br>   * быть отображён<br>   *<br>   * @param integer $iStep Номер шага<br>   */<br>  public function Show( $iStep )<br>  {<br>    $iAvaibleStep = $this->GetAvaibleStep( $iStep );<br><br>    $this->_aSteps[ $iAvaibleStep ]->PrepareData();<br>    $this->_aResult['Step'] = $iAvaibleStep;<br>  }<br>  <br>  /**<br>   * Подтверждение шага<br>   *<br>   * Метод проверяет данные, переданные клиентом, для указанного<br>   * шага с помощью вызова метода Submit объекта соответствующего<br>   * шага. В качестве возвращаемого значения<br>   * выступает флаг успеха проверки параметров заданного шага.<br>   * <br>   * @param integer $iStep Номер шага<br>   * @return bool Флаг успеха<br>   */<br>  public function Submit( $iStep )<br>  {<br>    $bValid = false;<br>    $iAvaibleStep = $this->GetAvaibleStep( $iStep );<br><br>    if( $iAvaibleStep == $iStep )<br>    {<br>      if( $this->_aSteps[ $iStep ]->Submit() )<br>        $bValid = true;  <br>    }<br>    <br>    return $bValid;<br>  }<br>  <br>  /**<br>   * Обработчик запроса пользователя<br>   *<br>   * @param integer $iStep Номер шага<br>   * @param bool $bSubmit Флаг подтверждения<br>   * @return bool Флаг завершения работы мастера<br>   */<br>  public function Process( $iStep, $bSubmit )<br>  {<br>    $bIsComplete = false;<br>    <br>    // Подтверждение шага<br>    if( $bSubmit )<br>    {<br>      // Подтверждение шага прошло удачно<br>      if( $this->Submit( $iStep ) )<br>      {<br>        // Шаг не последний в мастере<br>        if( $iStep < max( array_keys( $this->_aSteps ) ) )<br>        {<br>          $iStep += 1;<br>        }<br>        <br>        // Шаг последний в мастере<br>        else <br>        {<br>          $bIsComplete = true;<br>        }<br>      }<br>    }<br><br>    // Отображение шага<br>    $this->Show( $iStep );<br>    <br>    return $bIsComplete;<br>  }<br>  <br>}  // Конец класса "Мастер заказа"<br><br>* This source code was highlighted with Source Code Highlighter.

Основа мастера готова. Конечно это не окончательный вариант, но для изложения общей концепции он вполне сгодится.



Результат



Продемонстрируем работу созданного мастера на… на первом, что пришло в голову — пошаговом получении имени и фамилии :) В реальной жизни такой мастер конечно же не имеет смысла, но для демонстрации его будет достаточно.


Первым делом создадим два шага, отвечающих за получение имени и фамилии соответственно (я опишу только шаг получения имени, так как шаг получения фамилии принципиально ничем отличаться не будет):



/**<br> * Шаг: "Получение имени"<br> */<br>class CMasterStepName extends CMasterStep<br>{<br>  public function IsComplete()<br>  {<br>    return !is_null( CMaster::GetData( 'Name' ) );<br>  }<br>  <br>  public function PrepareData()<br>  {<br>    $this->_aResult['Text'] = 'Введите имя';<br>    $this->_aResult['Name'] = !empty( $this->_aData['Name'] ) ?<br>      $this->_aData['Name'] : '';<br>  }<br>  <br>  public function Submit()<br>  {<br>    $this->_aResult['Error'] = array();<br>    <br>    if( !empty( $this->_aData['Name'] )<br>      CMaster::SetData( 'Name', $this->_aData['Name'] );<br>    else<br>      $this->_aResult['Error']['Name'] = 'Empty';<br>  <br>    return ( sizeof( $this->_aResult['Error'] ) == 0 ) ? true : false;<br>  }<br>  <br>}  // Конец класса CMasterStepName<br><br>* This source code was highlighted with Source Code Highlighter.

Теперь для работы мастера нам остаётся создать скрипт, где мы соберём все вышеописанное воедино:



/* Подключение классов */<br><br>// Инициализация переменных<br>$aParam   = array();<br>$aResult  = array();<br><br>// Обработка параметров<br>$iStep    = ( isset( $_GET['Step'] ) ) ? $_GET['Step'] : false;<br>$bConfirm = ( !empty( $_GET['Confirm'] ) ) ? ($_GET['Confirm'] === 'true') : false;<br><br>// Создание мастера<br>$oMaster = new CSlMaster( $aResult );<br>$oMaster->AddStep( 0, new CMasterStepName( $_GET, $aParams, $aResult ) );<br>$oMaster->AddStep( 1, new CMasterStepSurName( $_GET, $aParams, $aResult ) );<br><br>// Обработка запроса пользователя<br>$bIsComplete = $oMaster->Process( $iStep, $bConfirm );<br><br>// Просмотр результатов<br>var_dump( $aResult );<br><br>* This source code was highlighted with Source Code Highlighter.

Заключение



Получившийся мастер конечно же не является идеальным. Если у Вас есть предложения по улучшению его структуры, я с большим интересом выслушаю Ваше мнение.
_________
Текст подготовлен в
ХабраРедакторе
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.