ITDumka
Примеры шаблонов проектирования или как написать свой PHP framework. Часть 3: Контроллер

Написать свой PHP framework становиться всё проще и проще, ведь я публикую очередную статейку об этом. Сейчас мы поговорим об архитектуре web-приложений, а точнее об одном из основных аспектов организации обработок команд пользователя. Конечно же, я буду обговаривать некоторые шаблоны проектирования. Здесь мы затронем такие паттерны как модель-представление-контроллер (Model View Controller), контроллер страниц (Page Controller), контроллер запросов (Front Controller), команда (Command), сценарий транзакции (Transaction Script) и некоторые другие.

Итак, с чего начнем? А начнем мы с того, что все только и твердят об MVC (Model View Controller) хотя многие понятия не имеют, что это такое и как это работает. Не смотря на то, знаете ли вы или нет, что скрывается за этими словами, я все же попытаюсь кратко объяснить суть, так как на основе последней будет строиться дальнейшее изложение. Конечно же, в нашем контексте я говорю, что MVC представляет собой шаблон проектирования. Но так как шаблоны проектирования как таковые могут абстрагировать довольно различные по охвату мысли и масштабу реализации какого-то функционала, то сказать, что MVC просто шаблон проектирования равносильно тому, что говорить о компьютере, как о простом наборе микросхем. Паттерн MVC не просто типовое решение - он скрывает в себе один из фундаментальных принципов проектирования программного обеспечения - разделение основных частей приложения, благодаря которому приложение легче адаптируется, масштабируется, тестируется, сопровождается и конечно же реализуется. Разделение происходит именно в тех местах, где это наиболее выгодно для всех только что перечисленных действий. Конкретно происходит отделение интерфейсной части приложения от логической. В нашем же случае - случае web-приложения, происходит разделение последнего на три составляющие: модель, представление и контроллер. Модель представляет собой бизнес-логику приложения. Представление характеризуется внешним видом, которое непосредственно наблюдается пользователем. Ну а контроллер управляет всем этим делом. Сегодня речь пойдет о контроллере и вариантах его реализации, на одном из которых мы и остановимся.

На данный момент в 90 процентах случаев взаимодействие пользователя с web-приложением проходит посредством переходов по ссылкам. Посмотрите сейчас на адресную строку браузера - по этой ссылке вы получили этот текст. По другим ссылкам, например, находящимся справа на этой странице, вы получите другое содержимое. Таким образом, значение ссылки - комбинация символов в ней - определяет конкретную команду web-приложению. Надеюсь, вы уже успели заметить, что у разных сайтов могут быть совершенные разные форматы построения адресной строки. Где-то можно выделить явные названия файлов, где-то нельзя, а где-то - вообще ничего вроде и не меняется. Каждый формат может отображать архитектуру web-приложения. Хотя это и не всегда так, но в большинстве случаев это явный факт. К чему я веду? Я просто хочу показать на примере адресной строки различие двух подходов в реализации (сразу скажем это слово) контроллера. Допустим, имеется два варианта адресной строки, по которым показывается какой-то текст:

  1. http://www.domain1.com/article.php?id=3
  2. http://www.domain2.com/index.php?article=3

Соответственно имеются также два варианта для показа профиля пользователя:

  1. http://www.domain1.com/user.php?id=4
  2. http://www.domain2.com/index.php?user=4

Как видно из примеров, логика обращения к web-приложению является разной. Для сайта domain1.com каждый сценарий отвечает за выполнение определённой команды, а для сайта domain2.com все обращения происходят в одном сценарии index.php. То есть для первого случая каждая страница сервера выполняет конкретное действие на сайте, а для второго - все действия реализуются как бы одним скриптом. За реальными примерам далеко ходить не надо. Подход с множеством точек взаимодействия вы можете наблюдать на любом форуме с движком phpBB (например, www.php.ru/forum): просмотр форума происходит через сценарий viewforum.php, просмотр топика через viewtopic.php и т. д. Второй же подход, с доступом через один физический файл сценария, можно наблюдать на этом сайте: все обращения проходят через index.php. Чтобы просмотреть статью нужен адрес http://itdumka.com.ua/index.php?cmd=shownode&node=1, а чтобы, допустим, зарегистрироваться нужно пройти по ссылке http://itdumka.com.ua/index.php?cmd=showregisterform. Как видите, это совершенно два различных подхода, которые, как это ни странно, находят отражение в контексте наших с вами шаблонов проектирования. Первый подход характерен для шаблона контроллер страниц (Page Controller), а второй подход реализуется паттерном контроллер запросов (Front Controller).

Контроллер страниц хорошо применять для сайтов с достаточно простой логикой. Форум phpBB имеет очень простую логику, поэтому контроллер страниц хорошо подошел для его реализации. В свою очередь, контроллер запросов объединяет все действия по обработке запросов в одном месте, что даёт ему дополнительные возможности, благодаря которым можно реализовать более трудные задачи, чем обычно решаются контроллером страниц. Я не буду вдаваться в подробности реализации контроллера страниц, а скажу лишь, что в рамках нашей задачи в роли контроллера будет разработан именно контроллер запросов. Мы же делаем фреймоврк, который должен решать большинство задач, поэтому контроллер страниц, к сожалению, тут не уместен. Но, я думаю, мы еще затронем его.

Вернемся немного к архитектуре MVC. Что же будет делать наш контроллер запросов? В первую очередь, он будет просматривать параметры запроса пользователя, и исходя из их значений, обращаться к соответствующим моделям приложения. Существуют разные способы реализации контроллера запросов, но все они основаны на схеме, в которой взаимодействуют две сущности: обработчик и действие. Обработчик извлекает из URL необходимую информацию, после чего решает, какое действие необходимо инициировать. После выбора конкретного действия он передает ему управление. То есть, в большинстве случаев обработчик является довольно простой программой, функции которой заключаются в выборе нужного действия. Действие представляет собой чистой воды шаблон проектирования, реализуемый в виде сценария транзакции (Transaction Script) или команды (Command).

В чем отличие между сценарием транзакции (Transaction Script) и командой (Command)? Эти два типовых решения практически одинаковы и решают одну и ту же задачу - обеспечить "логическую изоляцию" логики работы приложения по процедурам. Но, в связи с различной структурой реализации, эти типовые решения начинают приобретать различные свойства, обеспечивающие им довольно сильные отличия друг от друга, которые способствуют различным вариантам применения. Фактически, происходит реализация логически завершенных процедур, но конструктивно разными способами. Сценарий транзакций подразумевает расположения каждого логического действия в отдельной процедуре, которые могут существовать как разрознено, так и в пределах одного класса. Это уже решает сам разработчик исходя из своих собственных убеждений. Можно объединить в одном классе сценарии транзакции, реализующие более родственные операции. Но это, еще раз повторюсь, решает конкретный разработчик. В рамках контроллера запросов web-приложения я обычно не решаю такие вопросы, а использую шаблон проектирования команда. В отличие от сценария транзакции каждая логическая процедура реализуется отдельным классом, в котором присутствует полиморфный метод запуска процедуры. Этот метод объявлен в абстрактном классе и реализуется, в наследниках:


Listing №1 (PHP)
  1. /**
  2.  * Абстрактное действие
  3.  */
  4. abstract class Action {
  5.   /**
  6.    * Запускает действие
  7.    * с определенными параметрами
  8.    * @param ArrayList $Params
  9.    */
  10.   abstract function Run(ArrayList $Params);
  11. }


В соответствие с шаблоном проектирования команда наследник реализует конкретное действие. Таким образом, наследник реализует одну процедуру бизнес-логики приложения - фактически это будет реализация модели. Например, модель регистрации пользователя может выглядеть так:


Listing №2 (PHP)
  1. /**
  2.  * Объявляем класс действия
  3.  * наследуемого от Action
  4.  */
  5. class regAction extends Action {
  6.   /**
  7.    * Реализуем логику модели в методе Run
  8.    * @param ArrayList $Params
  9.    * @return mixed
  10.    */
  11.   public function Run(ArrayList $Params)
  12.   {
  13.     // Создаем контейнер ошибок
  14.     $Errors = new ArrayList();
  15.     // Проверяем новые логин и пароль
  16.     $Errors->LoginError = User::TestLogin(Request::Post('login'));
  17.     if(!count($Errors) && User::Exists('Login', Request::Post('login'))) {
  18.       $Errors->LoginError = 'Такой логин уже используется';
  19.     }
  20.     $Errors->PasswordError = User::TestPswd(Request::Post('password'));
  21.     // Если нет ошибок создаём нового пользователя
  22.     if(!count($Errors)) {
  23.       $User = new User();
  24.       $User->Login = Request::Post('login');
  25.       $User->Password = Request::Post('password');
  26.       // Пытаемся сохранить его
  27.       if ($User->Save()) {
  28.         // Если все прошло удачно,
  29.         // то возвращаем сообщение об
  30.         // успешной регистрации
  31.         $View = new View();
  32.         $View->SetTemplete('message');
  33.         $View->Message = $User->Login . ' зарегистрирован';
  34.         return $View;
  35.       }
  36.       else {
  37.         // Возвращаем ошибку если
  38.         // не можем сохранить пользователя
  39.         $Errors->UnknownError = 'Ошибка регистрации';
  40.         return $Errors;
  41.       }
  42.     }
  43.     else {
  44.       // Возвращаем ошибки входных данных
  45.       return $Errors;
  46.     }
  47.   }
  48. }


Как видите все очень просто. Класс конкретного действия, один метод - и обработчик готов. Данный паттерн называют по-разному - команда (Сommand), действие (Action) и иногда путают со сценарием транзакции (Transaction Script). Чаще всего я встречал название "команда" хотя, как видите, употребляю также слово "действие". Это происходит потому, что реализация самого шаблона проектирования чаще связана со слово Action. Думаю, ничего страшного не будет, если я буду использовать оба названия.

Что же такого хорошего в команде? Применяя это типовое решение, мы получаем следующие результаты:

  • Команда разрывает связь между объектом, инициирующим операцию, и объектом, имеющим информацию о том, как ее выполнить. То есть наш контроллер запросов будет как бы выполнять команды и ничего не знать о том, что конкретно в них происходит. Это позволяет добиваться высокой гибкости и динамически подменять команды
  • Команды - это самые настоящие объекты. Мы можем манипулировать ими и расширять точно так же, как в случае с любыми другими объектами
  • Из простых команд можно собирать составные
  • Добавлять новые команды очень легко, поскольку никакие существующие классы изменять не нужно. Необходимо только создать новый класс конкретной команды, реализовав в методе запуска соответствующую логику

Для завершения реализации контроллера запросов осталось дописать обработчик. Напомню, что он всего лишь должен выбрать нужную команду и запустить её. Тут стоит уточнить, что под словом "выбрать" подразумевается создание объекта команды, более модными словами - инстанцирование. Обработчик не должен знать, какой именно экземпляр команды он создаёт, но он точно знает название метода запуска команды. Ведь оно диктуется абстрактным классом, в нашем случае классом Action.

Итак, обработчик, назовем его диспетчер (Dispatcher), будет принимать на вход имя команды, которое будет совпадать с частью имени класса команды в нижнем регистре. К этой части имени мы прибавим строку 'Action' для констатации класса как класса команды (действия). Пример вы видели в листинге 2. Далее осуществляется полет фантазий и каждый летает, как хочет. Конечно же, более умные люди, скорее всего, опираются на задачу, но мы полетим довольно простым способом. Мы можем разделить команды по модулям, привязав название модулей к соответствующим директориям. В принципе, это довольно распространенный подход. Он осуществляется в основном с целью расположить классы команд более удобным способом и таким образом формировать непосредственно модули самого приложения. Таким образом, путь к классу конкретной команды будет выглядеть следующим образом:

Формат пути к классу команды:
/корневой путь/модуль родственных команд/конкретнаяКоманда.php

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


Listing №3 (PHP)
  1. class Dispatcher {
  2.   /**
  3.    * Корневой путь
  4.    * к модулям
  5.    * @static
  6.    * @var string
  7.    */
  8.   private static $_Path = '';
  9.   /**
  10.    * Задает путь к модулям приложения
  11.    * @static
  12.    * @param string $Path
  13.    */
  14.   public static function Init($Path)
  15.   {
  16.     self::$_Path = $Path;
  17.   }


Теперь реализуем два основных метода. Один будет создавать конкретный экземпляр команды, а второй запускать команду на исполнение:


Listing №4 (PHP)
  1.   /**
  2.   * Возвращает инстанцированный
  3.   * объект команды
  4.   * @param string $ActionName
  5.   * @param string $ModuleName
  6.   * @return Action
  7.   */
  8.   private function _CreateAction($ActionName, $ModuleName)
  9.   {
  10.     //Формируем имя и путь подключаемого файла с классом команды
  11.     $FileName = self::$_Path . $ModuleName . DIRECTORY_SEPARATOR .
  12.                 $ActionName . 'Action' . PHP_EXT;
  13.     if(is_file($FileName)) {
  14.       require_once $FileName;
  15.       $ActionClassName = $ActionName . 'Action';
  16.       return new $ActionClassName();
  17.     }
  18.     else {
  19.       //Генерируем исключение если файл не найден
  20.       throw new Exception('Action file "' . $FileName . '" does not exists');
  21.     }
  22.   }
  23.   /**
  24.   * Запускает команду и
  25.   * возвращает результат ее действия
  26.   * @param Action $Action
  27.   * @param ArrayList $Params
  28.   * @return mixed
  29.   */
  30.   private function _RunAction(Action $Action, ArrayList $Params)
  31.   {
  32.     return $Action->Run($Params);
  33.   }


Как видим, последний метод не пропустит ничего кроме класса с типом Action, что декларирует обязательную реализацию интерфейса объектов команд через их абстрактного предка.

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


Listing №5 (PHP)
  1. class Dispatcher {
  2.   /**
  3.    * Зарегистрированные
  4.    * команды
  5.    * @var array
  6.    */
  7.   private $_Actions;
  8.  
  9.   public function __construct()
  10.   {
  11.     $this->_Actions = array();
  12.   }
  13.   /**
  14.    * Регистрирует команду модуля
  15.    * @param string $ActionName
  16.    * @param string $ModuleName
  17.    */
  18.   public function RegisterAction($ActionName, $ModuleName)
  19.   {
  20.     //Приводим переменные к нижнему регистру
  21.     $ActionName = Str::Strtolower($ActionName);
  22.     $ModuleName = Str::Strtolower($ModuleName);
  23.     $this->_Actions[$ModuleName . ' ' . $ActionName] = TRUE;
  24.   }
  25.   /**
  26.    * Возвращает TRUE если команда зарегистрирована
  27.    * @param string $ActionName
  28.    * @param string $ModuleName
  29.    * @param bool $InDefault FALSE
  30.    * @return bool
  31.    */
  32.   public function IsActionRegistered($ActionName, $ModuleName)
  33.   {
  34.     //Приводим переменные к нижнему регистру
  35.     $ActionName = Str::Strtolower($ActionName);
  36.     $ModuleName = Str::Strtolower($ModuleName);
  37.     return (isset($this->_Actions[$ModuleName . ' ' . $ActionName]));
  38.   }
  39.   /**
  40.    * Возвращает результат выполнения команды
  41.    * или NULL если команда не зарегистрирована
  42.    * @param string $ActionName
  43.    * @param string $ModuleName
  44.    * @param ArrayList|NULL $Params
  45.    * @return mixed
  46.    */
  47.   public function GetResponse($ActionName, $ModuleName, $Params = NULL)
  48.   {
  49.     //Создаем пустые параметры команды если они не заданы
  50.     if(!$Params) {
  51.       $Params = new ArrayList();
  52.     }
  53.     //Приводим переменные к нижнему регистру
  54.     $ActionName = Str::Strtolower($ActionName);
  55.     $ModuleName = Str::Strtolower($ModuleName);
  56.     if($this->IsActionRegistered($ActionName, $ModuleName)) {
  57.       //Создаем неизвестный экземпляр команды
  58.       $Action = $this->_CreateAction($ActionName, $ModuleName);
  59.       //Запускаем команду и возвращаем результат
  60.       return $this->_RunAction($Action, $Params);
  61.     }
  62.     else {
  63.       return NULL;
  64.     }
  65.   }


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


Listing №6 (PHP)
  1. class Dispatcher {
  2.   /**
  3.    * Корневой путь
  4.    * к модулям
  5.    * @static
  6.    * @var string
  7.    */
  8.   private static $_Path = '';
  9.   /**
  10.    * Зарегистрированные
  11.    * команды
  12.    * @var array
  13.    */
  14.   private $_Actions;
  15.   /**
  16.    * Команды по умолчанию
  17.    * @var array
  18.    */
  19.   private $_Default;
  20.   /**
  21.    * Модуль по умолчанию
  22.    * @var string
  23.    */
  24.   private $_DefModule;
  25.   /**
  26.    * Адрес редиректа
  27.    * @var string
  28.    */
  29.   private $_RedirectUrl;
  30.   /**
  31.    * Задает путь к модулям приложения
  32.    * @static
  33.    * @param string $Path
  34.    */
  35.   public static function Init($Path)
  36.   {
  37.     self::$_Path = $Path;
  38.   }
  39.   /**
  40.    * @param string $DefModule
  41.    * @param string $RedirectUrl
  42.    */
  43.   public function __construct($DefModule, $RedirectUrl = '/')
  44.   {
  45.     $this->_Actions = array();
  46.     $this->_Default = array();
  47.     $this->_RedirectUrl = Str::Strtolower($RedirectUrl);
  48.     $this->_DefModule = Str::Strtolower($DefModule);
  49.   }
  50.   /**
  51.    * Регистрирует команду модуля
  52.    * @param string $ActionName
  53.    * @param string $ModuleName
  54.    */
  55.   public function RegisterAction($ActionName, $ModuleName)
  56.   {
  57.     //Приводим переменные к нижнему регистру
  58.     $ActionName = Str::Strtolower($ActionName);
  59.     $ModuleName = Str::Strtolower($ModuleName);
  60.     $this->_Actions[$ModuleName . ' ' . $ActionName] = TRUE;
  61.   }
  62.   /**
  63.    * Регистрирует команду по умолчанию для модуля
  64.    * @param string $ActionName
  65.    * @param string $ModuleName
  66.    */
  67.   public function RegisterDefault($ActionName, $ModuleName)
  68.   {
  69.     //Приводим переменные к нижнему регистру
  70.     $ActionName = Str::Strtolower($ActionName);
  71.     $ModuleName = Str::Strtolower($ModuleName);
  72.     $this->_Default[$ModuleName] = $ActionName;
  73.   }
  74.   /**
  75.    * Возвращает TRUE если команда зарегистрирована
  76.    * @param string $ActionName
  77.    * @param string $ModuleName
  78.    * @param bool $InDefault FALSE
  79.    * @return bool
  80.    */
  81.   public function IsActionRegistered($ActionName, $ModuleName)
  82.   {
  83.     //Приводим переменные к нижнему регистру
  84.     $ActionName = Str::Strtolower($ActionName);
  85.     $ModuleName = Str::Strtolower($ModuleName);
  86.     return (isset($this->_Actions[$ModuleName . ' ' . $ActionName]));
  87.   }
  88.   /**
  89.    * Возвращает команду по умолчанию для модуля
  90.    * или NULL если её нет
  91.    * @param string $ModuleName
  92.    * @return mixed
  93.    */
  94.   public function GetDefaultAction($ModuleName)
  95.   {
  96.     //Приводим переменную к нижнему регистру
  97.     $ModuleName = Str::Strtolower($ModuleName);
  98.     return (isset($this->_Default[$ModuleName])) ? $this->_Default[$ModuleName] : NULL;
  99.   }
  100.   /**
  101.    * Возвращает результат выполнения команды
  102.    * @param string $ActionName
  103.    * @param string $ModuleName
  104.    * @param ArrayList|NULL $Params
  105.    * @return mixed
  106.    */
  107.   public function GetResponse($ActionName, $ModuleName, $Params = NULL)
  108.   {
  109.     //Создаем пустые параметры команды если они не заданы
  110.     if(!$Params) {
  111.       $Params = new ArrayList();
  112.     }
  113.     //Если не указан модуль устанавливаем тот что по умолчанию
  114.     if(!$ModuleName) {
  115.       $ModuleName = $this->_DefModule;
  116.     }
  117.     //Ищем команду в зарегистрированных
  118.     if($this->IsActionRegistered($ActionName, $ModuleName)) {
  119.       //Создаем неизвестный экземпляр команды
  120.       $Action = $this->_CreateAction($ActionName, $ModuleName);
  121.       //Запускаем команду и возвращаем результат
  122.       return $this->_RunAction($Action, $Params);
  123.     }
  124.     //Ищем команду в зарегистрированных по умолчанию
  125.     elseif (NULL !== $ActionName = $this->GetDefaultAction($ModuleName)) {
  126.       //Создаем неизвестный экземпляр команды
  127.       $Action = $this->_CreateAction($ActionName, $ModuleName);
  128.       //Запускаем команду по умолчанию и возвращаем результат
  129.       return $this->_RunAction($Action, $Params);
  130.     }
  131.     else {
  132.       //Перенаправляем пользователя
  133.       header('Location: ' . $this->_RedirectUrl);
  134.       exit(0);
  135.     }
  136.   }
  137.   /**
  138.    * Возвращает инстанцированный
  139.    * объект команды
  140.    * @param string $ActionName
  141.    * @param string $ModuleName
  142.    * @return Action
  143.    */
  144.   private function _CreateAction($ActionName, $ModuleName)
  145.   {
  146.     //Приводим переменные к нижнему регистру
  147.     $ActionName = Str::Strtolower($ActionName);
  148.     $ModuleName = Str::Strtolower($ModuleName);
  149.     //Формируем имя и путь подключаемого файла с классом команды
  150.     $FileName = self::$_Path . $ModuleName . DIRECTORY_SEPARATOR .
  151.                 $ActionName . 'Action' . PHP_EXT;
  152.     if(is_file($FileName)) {
  153.       //Подключаем файл класса
  154.       require_once $FileName;
  155.       //Формируем имя класса команды (действия)
  156.       $ActionClassName = $ActionName . 'Action';
  157.       //Инстанцируем экземпляр команды и возвращаем его
  158.       return new $ActionClassName();
  159.     }
  160.     else {
  161.       //Генерируем исключение если файл не найден
  162.       throw new Exception('Action file "' . $FileName . '" does not exists');
  163.     }
  164.   }
  165.   /**
  166.    * Запускает команду и
  167.    * возвращает результат ее действия
  168.    * @param Action $Action
  169.    * @param ArrayList $Params
  170.    * @return mixed
  171.    */
  172.   private function _RunAction(Action $Action, ArrayList $Params)
  173.   {
  174.     return $Action->Run($Params);
  175.   }
  176. }


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


Listing №7 (PHP)
  1. // Константа расширения для файлов команд
  2. // да и для всех интерпретируемых файлов
  3. define('PHP_EXT', '.php');
  4. require_once './framework/Action' . PHP_EXT;
  5. require_once './framework/ArrayList' . PHP_EXT;
  6. require_once './framework/Dispatcher' . PHP_EXT;
  7. require_once './framework/Request' . PHP_EXT;
  8. require_once './framework/Str' . PHP_EXT;
  9. //Инициализируем классы
  10. Str::Init(Str::MODE_MULTIBYTE, 'UTF-8');
  11. Request::Init();
  12. Dispatcher::Init('./actions/');
  13. //Посылаем заголовок
  14. header('Content-type: text/html; charset="' . Str::GetCharset() . '"');
  15. //Создаем диспетчер
  16. $Dispatcher = new Dispatcher('standart');
  17. //Регистрируем команду "login" в модуле "standart"
  18. $Dispatcher->RegisterAction('login', 'standart');
  19. //Регистрируем команду "reg" в модуле "standart"
  20. $Dispatcher->RegisterAction('reg', 'standart');
  21. //Регистрируем команду "user" в модуле "standart"
  22. $Dispatcher->RegisterAction('user', 'standart');
  23. //Также регистрируем ее как команду по умолчанию
  24. $Dispatcher->RegisterDefault('user', 'standart');
  25. //Запускаем диспетчер
  26. $Response = $Dispatcher->GetResponse(Request::Get('cmd'), Request::Get('module'));
  27. //Выводим результат
  28. echo $Response;


Вот так с помощью шаблона проектирования контроллер запросов (Front Controller) мы объединяем все действия по обработке запросов в одном месте, распределяя их выполнение посредством диспетчера. Благодаря применению шаблона проектирования команда (Command) в составе контроллера запросов, мы с легкостью можем добавлять новые действия без особых усилий. Стоит лишь создать класс конкретной команды и подкинуть ее в директорию модуля. Давайте это и сделаем, добавив действия описанные в листинге7:


Listing №8 (PHP)
  1. <?php //авторизация: файл ./actions/standart/loginAction.php
  2. class loginAction extends Action {
  3.  
  4.   public function Run(ArrayList $Params)
  5.   {
  6.     return 'Запущена команда авторизации';
  7.   }
  8. }
  9. ?>
  10. <?php //регистрация: файл ./actions/standart/regAction.php
  11. class regAction extends Action {
  12.  
  13.   public function Run(ArrayList $Params)
  14.   {
  15.     return 'Запущена команда регистрации';
  16.   }
  17. }
  18. ?>
  19. <?php //пользователь: файл ./actions/standart/userAction.php
  20. class userAction extends Action {
  21.  
  22.   public function Run(ArrayList $Params)
  23.   {
  24.     return 'Запущена команда пользователя';
  25.   }
  26. }
  27. ?>


Надеюсь, с путями вы разберетесь. Конечно, данный диспетчер не претендует на идеальность, но я старался сделать как можно проще, чтобы показать суть решения. Естественно, вы можете допилить его до нужной кондиции - рефакторинг никогда не помешает!

Ах да, забыл я о классе ArrayList. Не хочется вас разочаровывать, но мы обсудим его в следующей статье вместе с некоторым шаблоном проектирования. Вы же можете создать фиктивный класс, благо в коде мы его почти нигде не использовали:


Listing №9 (PHP)
  1. class ArrayList {}


Можете считать, что перед вами зародыш фиктивной службы (Service Stub).


Было бы неплохо увидеть ваши комментарии! Продолжение следует...


Comments
By   Злой Архитектор
Published   13.01.2010

три с плюсом

Published   13.01.2010

Злой Архитектор
три с плюсом

Ваши аргументы?

By   Дерзкий алень
Published   13.01.2010

И этот человек прочитал Фаулера? Зачем свое городить? Используешь какие-то непонятные стандарты кодирования. Возьми какой-то готовый фреймворк и не парься.

Published   13.01.2010

Я возьму готовое для той задачи, которая это требует. И запомни, Дерзкий алень - понятие "стандарты кодирования" очень расплывчатые для того, чтобы их называли стандартами.

By   уже ахуенно злой!
Published   13.01.2010

Предмет цитирования
Возьми какой-то готовый фреймворк и не парься.

Ухо откушу!


Предмет цитирования
Ваши аргументы?

я знаю как сделать в два раза удобнее и практичнее,

поэтому моя оценка 3+

Published   13.01.2010

тот, кто злой
я знаю как сделать в два раза удобнее и практичнее

Ну так напиши, регистрация открыта, а оценки тут ставить не надо ибо они субъективны...

Published   29.01.2010

Аргументы приводим. Хотя бы те, которые говорят, чем лучше роутер на основе файловой структуре, как в ZF, от роутера на основе контролируемого пользовательского запроса, или типа того. А фукать, бякать и т.п - это никому не нужно. И место занимать это не будет.

By   123
Published   31.01.2010

есть задача. из неё и нужно исходить.

у фреймвока задача - максимальная простота/функциональность/производительность


дальше сам смотри что ты делаешь )


но сначало думай, 10 раз. а потом делай.

у тебя сейчас наоборот, тоесть ты взял "умную" книжку, начитался там про всякие паттерны фичи и пр. и начал делать что-то. а зачем и почему, у тебя пока с этим ещё не очень хорошо.

как это... рука не набита =)

Published   31.01.2010

Ну, это ты не мне должен говорить. Я примеры привожу, а не идеальные решения на все случаи жизни. Кому нужна интеграция как в ZF, пожалуйста пуст пользуется ZF. Кому надо что то другое, есть другое. У меня есть еще что-то. Или ты думаешь другие фреймоврки решают абсолютно все задачи? Например, у меня теоретически будет больше вызовов isset чем is_file. Какие проблемы? Или я тут прям вдалбливаю в голову людям своё виденье? Они же тоже должны думать. И это уже не мои проблемы.

By   Man
Published   07.07.2010

Хорошо бы привести пример рабочего этого всего фреймворка. Чтобы вместе с шаблонизатором всё это было. А то по отдельности всё лаконично и красиво. А к реальному делу если применить, то начнутся вопросы.


Listing №1 (PHP)
  1. #
  2. class userAction extends Action {
  3.  
  4. #
  5.  
  6.  
  7. #
  8.  public function Run(ArrayList $Params)
  9.  
  10. #
  11.  {
  12.  
  13. #
  14.    return 'Запущена команда пользователя';
  15.  
  16. #
  17.  }
  18.  
  19. #
  20. }


запущена команда? А если тут много всего? как сюда шаблонизатор прикрутить? И в итоге разметка вползёт и сюда...


Вобщем хочу видеть более живой пример исполдьзования!

Published   07.07.2010

Man
Вобщем хочу видеть более живой пример использования!

Понимаю. Тогда надо подождать неопределённое время, когда у меня будет оно для написанию других частей и развития этих.

Если не хотите ждать, то можете посмотреть код существующих фреймворков, допустим Zend Framework, и если вы поняли то, что тут написано, то там тоже поймёте и узнаете как взаимодействуют контроллер с представлением.


By   Чтец
Published   10.07.2010

Как-то немного ебануто получается - чтобы тупо вызвать метод-экшен контроллера мы не делаем так


а намудячил ты сука отдельно класс-экшен... пиздюк ты такой. В Зенде же не так уёбски.


И ты сука чтобы вызвать экшен - ты создал класс экшен (а то ты не знаешь как напрямую метод по имени позвать - я так понимаю) и потом сунул в другой класс который уже зовёт РАН


Эх пиздючёк. Раскусил я тебя - не знал ты как метод позвать, сучка.

Published   10.07.2010

Чтец ты тупишь. Я что, не мог даже в Зенде посмотреть? Ну ты конь-агонь!

ПЫСЫ:

Listing №1 (PHP)
  1.  
  2. //Zend_Controller_Action::dispatch
  3.     $this->$action();
  4. } else {
  5.     $this->__call($action, array());


pps: прогеры из Бутово самые крутые прогеры в мире!!!

By   Юрий Хабаров
Published   11.08.2010

Большое спасибо за разъяснение понятий "контроллер страниц" (Page Controller) и "контроллер запросов" (Front Controller). Довольно интересно почитать необычное решение.


Хотя, ИМХО, существуют более простые решения, чем реализация диспатчера - уж больно сложным он получился для такой простой задачи.


Более простое решение для диспатчера:

- определение класса (если нету - использовать default)

- определение действия (если нету - использовать default)


затем вызов класса->действия(параметры)


default класс должен быть всегда (например, класс, выводящий сообщение о том, что страница не найдена или редиректит на основную страницу).

default действие в каждом классе должно иметь одно и то же название (как __construct__).

Published   11.08.2010

Да, Юрий. Большинство существующих фреймворков используют именно такую схему. Многие пишут в комментах, что я написал какой-то бред, но я как раз пытался написать другой подход - идеи для которого берутся из стандартных механизмов управления событиями с использованием шаблона Command.


В стандартном подходе обычно делается Front Controller в виде одиночки, запускающем метод в конкретной реализации контроллера:


Listing №1 (PHP)
  1. <?php
  2. class FrontController {
  3.   protected $_controller;
  4.   protected $_action;
  5.   protected $_params;
  6.   protected $_result;
  7.  
  8.   private static $_instance = null;
  9.   //получаем единственный экземаляр в любой точке приложения
  10.   public static function getInstance()
  11.   {
  12.     if (self::$_instance === null) {
  13.       self::$_instance = new FrontController();
  14.     }
  15.     return self::$_instance;
  16.   }
  17.   //скрытый конструктор инициализирует парамеры
  18.   private function __construct()
  19.   {
  20.     $request = $_SERVER['REQUEST_URI'];
  21.     $pieces = explode('/', $trim($request, '/'));
  22.     //определяем конкретный контроллер и его действие
  23.     //по умолчанию беруться Index->index()
  24.     $this->_controller = ($controller = array_shift($pieces)) ? $controller : 'index';
  25.     $this->_action = ($action = array_shift($pieces)) ? $action : 'index';
  26.     $this->_params = array();
  27.     while (count($pieces)) {
  28.       $this->_paramsp[array_shift($pieces)] = array_shift($pieces);
  29.     }
  30.     $this->_result = null;
  31.   }
  32.   //маршрутизатор выполняет проверку и инициализацию конкретного контроллера и его экшена
  33.   public function route()
  34.   {
  35.     if (class_exists($this->_controller)) {
  36.       $rc = new ReflectionClass(this->_controller);
  37.       if ($rc->implementsInterface('IActionController')) {
  38.         if ($rc->hasMethod($this->_action)) {
  39.           //Запускаем конкретное действие
  40.           //устанавливая результат
  41.           $controller = $rc->newInctance();
  42.           $controller->setParams($this->_params);
  43.           $action = $this->_action;
  44.           $this->_result = $controller->$action();
  45.         }
  46.         else {
  47.           throw new Exception('not method exists');
  48.         }
  49.       }
  50.       else {
  51.         throw new Exception('not implement interface');
  52.       }
  53.     }
  54.     else {
  55.       throw new Exception('not class exists');
  56.     }
  57.   }
  58. }
  59.  
  60. //Все контроллеры должны иметь интерфейс
  61. interface IActionController {
  62.  
  63.   public function setParams($params);
  64. }


Ну где-то приблизительно так.

Create comment
 
Formatting
Comment can not be edited. Please, use the button "Preview"
By
  (Enter prev char)
Comment
Categories
PHP
12
articles
Прочее
4
articles
Delphi
0
article
C/C++
0
article
C#
0
article
Java
0
article
Perl
0
article
Python
0
article
Enter
Cookie must be "ON"
Login
Password
 
Popular tags
PHP
9
articles
паттерн
5
articles
framework
5
articles
шаблон
5
articles
Template View
3
articles
Facade
3
articles
Service Stub
3
articles
Page Controller
2
articles
Singleton
2
articles
Gateway
2
articles
MySQL
2
articles
Registry
2
articles
Command
2
articles
Front Controller
2
articles
Action
2
articles
Abstract Factory
2
articles
типовое решение
2
articles
шаблоны проектирования
2
articles
Iterator
2
articles
Transaction Script
2
articles
Rambler's Top100 Правильный CSS!