Поэтому, прочитав и услышав много о шаблоне Модель / Представление / Контроллер, я решил применить его к новому веб-приложению в офисе. После долгих проб и ошибок, редизайна, рефакторинга и большего количества чтения я придумал простую архитектуру, которая на самом деле работает довольно хорошо. Если вы совсем не знакомы с MVC, я предлагаю вам прочитать вики-страницу для краткого введения. Готовы?
Мое приложение может быть разбито примерно на 5 объектов: объект FrontOffice, контроллеры, действия, ActionResults и представления.
FrontOffice
FrontOffice — это место, где поступает запрос. Поскольку запрос может поступать через любой протокол, я определил FrontOffice как абстрактный класс, расширяющий его для каждого протокола. В этом случае мы будем использовать объект FrontOffice_URL, чтобы извлечь запрос из URL. FrontOffice просто переводит, передает запрос контроллеру, который отвечает за обработку запрошенного действия. Это называется единой точкой входа.
Контроллер
Я решил разрезать контроллер на три части, чтобы обеспечить большую абстракцию. Контроллер заботится только о сопоставлении псевдонима действия с классом действия, предоставляя модель и выполняя результат.
Действие
Объекты действия имеют дело с авторизацией действия (бизнес-правилами) и выполнением действия. Это на самом деле реализация командного шаблона. В зависимости от результата метода Action :: execute () , Action возвращает объект ActionResult. Это определяет маршрутизацию приложения. Например: если запись успешно добавлена в БД, перенаправьте клиента на страницу обзора. В противном случае снова откройте страницу «Добавить запись» и отобразите сообщение об ошибке.
ActionResults
Объекты ActionResult возвращаются объектом Action в контроллер. Они реализуют простой интерфейс, который просто говорит, что каждый объект ActionResult должен иметь метод execute (). Примерами объектов ActionResult являются ActionResult_View (присутствует HTML), ActionResult_Redirect (клиент перенаправления) и ActionResult_Download (представляет загрузку файла).
Представления
Представления — это простые php-файлы, которые определяют html-код страницы и объединяют некоторые простые данные.
На диаграмме ниже показано, как система обрабатывает запрос.
Модель
Итак, увидев это, я слышу, как вы говорите: где модель? Хороший. Это может быть сюрпризом, но в моей модели нет классов Model. На мой взгляд, модель должна обрабатывать основные правила проверки и санитарии ваших данных. Для этого я использую мощный механизм БД, написанный моим другом Арнольдом Дэниелсом. Он называется QDB и доступен бесплатно на его сайте., Для каждой из моих таблиц базы данных у меня есть файл конфигурации .ini (вы можете использовать yaml или что вы предпочитаете), который определяет тип данных и правила проверки (требуется да / нет, максимальная длина, проверка электронной почты и т. Д.). Поэтому, когда мне нужна модель в моем контроллере, я просто вызываю библиотеку QDB для записи / таблицы / набора записей, и она дает мне данные. Вам не нужно беспокоиться о заявлениях mysql или о чем-то другом, об этом все позаботятся. В общем, контроллер не знает тип базы данных, на которой вы работаете. Посмотрите примеры кода для простоты QDB.
Теперь давайте посмотрим на простой пример.
Пример
Начнем с простого примера приложения MVC, основанного на этой архитектуре. Допустим, наше приложение содержит таблицу с сотрудниками, и мы хотим обновить запись (с id = 3) в этой таблице. Я использую mod_rewrite в Apache для чистых URL. Фактический URL-адрес для этого действия будет http://mywebsite.com/index.php?__controller=Employee&__action=update&id=3, но он переписан на http://mywebsite.com/Employee/update?id=3 .
# initialize the FrontOffice
$fo = FrontOffice::factory('URL');
# process the request
$fo->dispatch();
Это может быть весь код PHP в корне вашего приложения. На самом деле, это мой index.php (безопасный код инициализации и аутентификация пользователя, например include ‘config.inc’; ). Похоже, волшебство, не так ли?
Так что же происходит?
Сначала мы инициализируем FrontOffice. Я использую фабричный метод, чтобы сделать это, и это возвращает объект FrontOffice_URL . Вот конструктор класса FrontOffice_URL :
class FrontOffice_URL extends FrontOffice {
public $output = "Html";
/**
* Initialize the FrontOffice - Read from URL the controller and the action
*
* @return FrontOffice object
*/
public function __construct() {
$this->_requestVars = new Data();
# capture get
if ($_GET['__controller']) $this->_controller = urldecode($_GET['__controller']);
if ($_GET['__action']) $this->_action = urldecode($_GET['__action']);
foreach ($_GET as $k=>$v) {
$k = urldecode($k);
$this->_requestVars->{$k} = urldecode($v);
}
# capture post
foreach ($_POST as $k=>$v) $this->_requestVars->{$k} = $v;
}
}
Довольно просто, верно? Контроллер и действие извлекаются из URL, а остальные переменные запроса хранятся в общем объекте Data, который является просто классом void (в PHP вы можете устанавливать свойства на лету). Обратите внимание, что формат вывода установлен на «HTML». В будущем вы можете захотеть, чтобы ваше приложение отвечало в формате XML. В зависимости от настройки $ output, вы можете включить правильные представления. В конце статьи это показано в структуре каталогов.
Итак, мы переходим ко второму утверждению:
# load controller
$cclass = self::getControllerClass($this->_controller);
if (!class_exists($cclass)) throw new Controller_Unknown_Exception("Unknown Controller object `".$this->_controller."` requested");
$controller = new $cclass($this);
# controller must extend Controller class
if (!($controller instanceof Controller)) throw new Controller_Illegal_Exception("Controller `$cclass` is not a valid Controller object");
# execute the action
return $controller->execute($this->_action, $this->_requestVars);
Теперь мы переместили запрос из FrontOffice в объект Controller. Вы не взволнованы? Отсюда мы на самом деле собираемся что-то сделать ?
Контроллером в этом случае является контроллер Employee. Мы сосредоточимся на вызываемом методе execute (). Как я уже сказал, он выполняет простое сопоставление имен действий с классами действий. Я предпочитаю не делать это прямо из URL по нескольким причинам. Метод execute () ищет и загружает класс для запрошенного Action, а затем вызывает метод Action :: execute (). Этот метод в свою очередь возвращает объект ActionResult. ActionResult :: execute () делает все, что требуется для действия (представляет представление, предлагает загрузить файл, перенаправляет клиента и т. Д.).
class Controller_Employee extends Controller {
public function execute($action, $data)
{
$data->mode = $action;
switch (strtolower($action))
{
case "list":
$action = new Action_Overview('Employee', $data);
break;
case "update":
case "show":
case "add":
$action = new Action_Detail('Employee', $data);
break;
case "delete":
$action = new Action_Delete('Employee', $data);
break;
default :
$action = new Action_Overview('Employee', $data);
break;
}
$result = $action->execute();
if (!$result instanceof ActionResult) throw new ActionResult_Illegal_Exception('$result is not a valid ActionResult');
return $result->execute();
}
}
Это код для метода execute ().
В конструкторе Action_Detail мы имеем:
public function __construct($table, $data)
{
if (!$data->id && $data->mode !== 'add') throw new Action_Arguments_Exception('Param id required, not given');
$this->data = $data;
$this->record = QDB::i()->table($table)->load($data->id);
if (!$this->record) throw new Action_Exception('Record not found');
}
Вы можете понять мою благодарность за библиотеку QDB, увидев это. Он полностью лишен операторов MySQL и имеет очень интуитивно понятный API.
Вернуться к Controller_Employee :: execute (). Внутри $ result = $ action-> execute () происходят две вещи .
- Вызывается метод действия () auth (), который проверяет такие вещи, как: если текущий пользователь не является начальником сотрудника, пользователь не может изменить запись сотрудника . В этом примере я ничего не проверю, поэтому я могу просто использовать универсальный объект Action_Detail (его метод auth () всегда возвращает true). В противном случае вы бы создали объект Action_Detail_Employee_Update, расширяет Action_Detail и переопределяете метод auth ().
- Если Action :: auth () возвращает true, мы можем продолжить выполнение действия.
Теперь, когда мы уполномочены обновить этого сотрудника, мы хотим перейти к той части, где мы создаем форму. Для этого я использую проприетарную библиотеку форм, которая сравнивается с PEAR HTML_QuickForm (но на самом деле лучше :)). Так что, ради аргумента, не пытайтесь понять объект $ form. Код для фактического выполнения действия выглядит примерно так:
в Action_Detail :: execute ()
$form = HTML_Form::createFromData('form', $this->record);
# if the form is submitted and validated, redirect to overview
if ($form->isSubmitted() && $form->validate()) {
$this->record->save(); // record updaten
return new ActionResult_Redirect('/'.FrontOffice::currentController().'/list');
}
# create form
if ($this->data->mode === 'show')
return new ActionResult_View('View/Show.php', $this->record);
else
return new ActionResult_View('View/Update.php', $this->record);
ActionResult возвращается в контроллер, где мы видели return $ result-> execute (); код. В этом случае мы получаем объект ActionResult_View, возвращенный нам, с представлением и некоторыми данными для представления в представлении.
Почти там, только ActionResult_View :: execute (), чтобы обернуть это.
function execute()
{
# Make $data available as a global in the View
$data = $this->data;
# determine output format (HTML/XML etc)
$type = ucfirst(strtolower(FrontOffice::i()->output));
# locate View file
$view = preg_replace('/View/', 'View/'.$type, $this->view);
try {
$success = @include($view);
if (!$success) throw new View_Exception("View does not exist");
} catch (View_Exception $e) {
print $e->getMessage();
}
}
Включенный сюда файл View может выглядеть примерно так:
# initialize stuff like menu/layout etc
initLayout();
if ($data && $data instanceof QDB_Record)
{
$form = HTML_Form::createFromData('form', $data);
$form->validate();
$form->display();
}
Вот и все. Мы включаем файл PHP, и он выводит некоторый HTML для клиента.
Чтобы закрыть, вот структура каталогов, которую я использую:
Действие
\ Delete.php
\ detail.php
\ Overview.php
\ Static.php
ActionResult
\ download.php
\ NotAllowed.php
\ redirect.php
\ view.php
MyApplication
\ Action
\ Служащий
\ update.php
\ Controller
\ Employee.php
\ …
\ View
\ …
Посмотреть
\ Html
\ Overview.php
\ Start.php
\ …
\ Xml
\ Overview.php
\ Start.php
\ …
action.php
ActionResult.php
controller.php
Data.php
FrontOffice.php
Вот и все. В настоящее время мы используем эту архитектуру в большом приложении, и она оказывается очень гибкой и простой в использовании. Может потребоваться некоторое время, чтобы привыкнуть к структуре, но высокая возможность повторного использования и небольшие фрагменты кода действительно окупаются в долгосрочной перспективе.
Обратите внимание, что я упростил некоторые вещи, и я не вдавался в подробности о некоторых интерфейсах / абстрактных классах, которые я использую. Я намеревался донести свою идею и с нетерпением жду любых вопросов / предложений и комментариев.