Практическое MVC
Введение
Данная статья рассчитана на программистов, имеющих общие знания в области PHP, его ОО модели и практикующих модульную архитектуру. Я постараюсь не углубляться в особенности тех или иных элементов ООП, чтобы не смешивать теорию с практикой, а постараюсь описать только практические части реализации классической MVC архитектуры и некоторых ее доработок.
Совсем немного теории
MVC архитектура является подмножеством более общей модели, называемой "Архитектурой с расслоением". Данная архитектура регламентирует создание таких систем, каждая функционально-завершенная область, которых будет не просто взаимодействовать с другими, но и образовывать отдельный модуль (или слой) со своим уровнем абстракции. В общем смысле это означает, что система расслаивается на следующие основные компоненты (в порядке обобщения):
- Слой источника данных - данный слой включает механизмы непосредственного взаимодействия с наиболее нижними уровнями системы, на пример с файловой системой, базой данных и т.д. В данный слой может входить множество небольших классов, каждый из которых решает строго определенные задачи;
- Слой служб (сервисов) - данный слой включает множество классов из шаблона "Фассад", которые предоставляют более абстрактный доступ к слою источника данных, на пример это службы взаимодействия с БД. В отличии от слоя источника данных, данная служба практически не содержит логики взаимодействия с БД, а только предоставляет более удобный интерфейс для этого взаимодействия, а так же позволяет легко изменять классы слоя испточника данных без необходимости переписывания кода в остальных слоях;
- Модель домена - данный слой содержит классы, реализующие логику самой программы. Если речь идет о гостевой книге, то данный слой будет содержать классы, представляющие предметную область, API системы и логику взаимодействия с хранилищами (сессии, БД и т.д.);
- Пользовательский интерфейс - данный слой содержит элементы взаимодействия с пользователем (GUI, консоль и т.д.).
Архитектура MVC затрагивает два последних слоя: Модель домена и Пользовательский интерфейс - позволяя разделить эти два слоя через добавление еще одного компонента - Контроллера.
В общем смысле, MVC использует модульную структуру, так как регламентирует создание множества триад - Модель, Вид, Контроллер. Для примера, гостевая книга с использованием MVC может выглядеть следующим образом:
Модель
<?php
interface Message{
function __construct($autor, $content);
public function edit($newContent);
public function remove();
}
interface Book{
// Возвращает объект класса Message по его id
public function getMessage($idMessage);
// Возвращает объекты класса Message по их id
public function getMessagesFromIds(array $idsMessages);
// Возвращает объекты класса Message из временного интервала
public function getMessagesFromInterval(\DateInterval $interval);
// Добавляет новое сообщение в БД
public function createMessage(Message $message);
}
Модель будет использовать сервисы для записи сообщений в БД, их чтение и редактирования.
Вид
<?php
interface Tape{
public function printMessage(Message $message);
}
Как правило, вид использует сервисы шаблонизации и шаблон "Издатель/подписчик".
Контроллер
<?php
class ControllerBook{
// Функция, обрабатывающая все данные от вида (пример)
public static function main(){
$message = $_REQUEST;
switch($message['Active']){
case 'CreateMessage':
// Никакой логики, только передача управления модели!
$newMess = new Message($message['Autor'], $message['Content']);
$book = new Book;
$book->createMessage($newMessage);
break;
case 'DeleteMessage':
// Никакой логики, только передача управления модели!
$book = new Book;
$book->getMessage($message['Id'])->remove();
break;
case 'EditMessage':
// Никакой логики, только передача управления модели!
$book = new Book;
$book->getMessage($message['Id'])->edit($message['Content']);
break;
}
}
}
// При выполнении этого скрипта, управление передается в метод main контроллера, потому этот скрипт отлично подходит для обработки всех GET и POST запросов от клиента
// Его задачей является направить эти запросы в модель
ControllerBook::main();
Как видно, модель MVC всего лишь добавляет дополнительный контроллер, который принимает GET и POST запросы от клиента (в данном случае) и перенаправляет их нужным объектам модели.
Небольшие дополнения
Я решил пойти дальше и дополнить мою систему следующими механизмами:
- Единая точка входа - все GET и POST запросы всех видов передаются в одиный контроллер, который передает их ответственному контроллеру, а тот, в свою очередь, модели. Этот подход позволяет вынести некоторую общую логику приема данных (на пример фильтрацию входящих данных) в один класс, а не дублировать ее в каждом контроллере;
- Роутер модулей - для автоматизации поиска нужного контроллера, я использовал роутер, который позволит быстро находить требуемый контроллер;
- DataMapper с архитектурой "Отдельная таблица для каждого листа" - сложный механизм, позволяющий записывать объекты в реляционный БД с учетом наследования.
Все эти дополнения будут описаны далее, но они не являются частью классического MVC, пример которого был приведен выше.
Инструменты
Прежде чем описывать реализацию системы с использованием MVC, приведу несколько интерфейсов, необходимых для работы системы. Данные инструменты относятся к слою служб, так как доступны всем модулям модели домена, с другой стороны модули модели домена доступны только своим пользовательским интерфейсам и могут взаимодействовать только через заранее определенный механизм.
Роутер
Роутер позволяет Единой точки входа находить нужный для данного запроса контроллер. Роутер имеет следующий интерфейс:
<?php
/**
* Интерфейс управления роутингом модулей
*/
interface ModulesRouterInterface{
/**
* Метод возвращает контроллер для данного модуля
* @abstract
* @param string $moduleName Имя модуля
* @return mixed
*/
public function getController($moduleName);
/**
* Метод определяет, определен ли данный модуль в системе
* @abstract
* @param string $moduleName Имя модуля
* @return boolean true - если модуль установлен, иначе - false
*/
public function isModuleExists($moduleName);
/**
* Метод добавляет новой путь в роутер
* @abstract
* @param string $moduleName Имя модуля
* @param \PPHP\tools\patterns\metadata\reflection\ReflectionClass $controller Отображение класса контроллера для данного модуля
*/
public function setController($moduleName, \PPHP\tools\patterns\metadata\reflection\ReflectionClass $controller);
/**
* Метод удаляет данные модуля из роутера
* @abstract
* @param string $moduleName Имя модуля
* @return boolean true - если модуль был успешно удален из роутера, иначе - false
*/
public function removeController($moduleName);
}
Он очень прост в реализации, для работы он использует ini файл со следующей структурой:
GuestBook=\PPHP\model\modules\guestBook\Controller
При запросе у центрального контроллера модуля GuestBook, он передает запрос к контроллеру model\modules\guestBook\Controller
Приемник
Данный класс включает механизмы получения данных из массива $_REQUEST и имеет следующий вид:
<?php
/**
* Класс предоставляет доступ к виду и позволяет обмениваться с ним сообщениями.
*/
class ViewProvider implements \PPHP\tools\patterns\singleton\Singleton{
use \PPHP\tools\patterns\singleton\TSingleton;
/**
* Метод возвращает сообщение, полученное от вида.
* @return mixed|null Содержимое сообщения или null - если сообщение не было передано.
*/
public function getMessage(){
if(!isset($_REQUEST['ViewMessage'])){
return null;
}
return json_decode($_REQUEST['ViewMessage']);
}
/**
* Метод передает сообщение виду.
* @param mixed $message Содержимое сообщения.
*/
public function sendMessage($message){
echo json_encode($message);
}
}
Как можно заметить, это очень простой класс, инкапсулирующий механизмы получения данных от клиента.
Центральный контроллер (единая точка входа)
Данный класс вызывается всегда, при передаче команд от вида в модель. Он реализован следующим образом:
<?php
/**
* Класс является центральным контроллером системы и отвечает за вызов и передачу модулю сообщений вида, а так же за возврат ему ответа модуля.
*/
class CentralController{
/**
* Данный ментод отвечает за передачу модулю сообщения и возврат ответа.
* @static
* @throws ModuleNotFoundException Выбрасывается в случае, если требуемый модуль не зарегистрирован в системе.
*/
public static function main(){
// Используем приемник для получения текущей команды
$viewProvider = \PPHP\services\view\ViewProvider::getInstance();
$viewMessage = $viewProvider->getMessage();
// Используем роутер для получения ответственного контроллера
$modulesRouter = \PPHP\services\modules\ModulesRouter::getInstance();
if(!$modulesRouter->isModuleExists($viewMessage->module)){
throw new ModuleNotFoundException('Требуемого модуля "' . $viewMessage->module . '" не существует.');
}
$controller = $modulesRouter->getController($viewMessage->module);
$controller = $controller::getInstance();
// Запускаем в ответственном контроллере его метод-обработчик. Ответ передаем обратно в вид.
$send = new \stdClass();
try{
$send->answer = $controller->main(is_null($viewMessage->message)? null : $viewMessage->message);
}
catch(\Exception $e){
$send->exception = $e;
}
// Возврат ответа модели в вид с помощью Приемника
$viewProvider->sendMessage($send);
}
}
// Любой запуск скрипта приведет к запуску центрального контроллера
CentralController::main();
Интерфейс контроллеров модуля
Как можно заметить, центральный контроллер всегда использует однин и тот же метод ответственного контроллера - main - значит нужно чтобы все контроллеры реализовали
этот метод, для этого определим интерфейс контроллеров модуля:
<?php
/**
* Все конкретные контроллеры модулей должны реализовывать данный интерфейс.
*/
abstract class ModuleController implements \PPHP\tools\patterns\metadata\reflection\Reflect, \PPHP\tools\patterns\singleton\Singleton{
// Все контроллеры модуля являются Singletom и могут быть аннотированы
use \PPHP\tools\patterns\metadata\reflection\TReflect;
use \PPHP\tools\patterns\singleton\TSingleton;
/**
* Точка входа в контроллер модуля.
* @abstract
* @param mixed|null $message Сообщение, переданное от вида.
* @return mixed Возвращаемые виду данные.
*/
abstract public function main($message = null); // Данный метод отвечает за перенаправления запросов от вида в модель
}
Пример контроллера
Простейший контроллер:
<?php
namespace PPHP\model\modules\guestBook;
class Controller extends \PPHP\model\classes\ModuleController{
public function main($message = null){
return 0;
}
}
Данный контроллер ничего не обрабатывает и всегда возвращает ответ 0, но он показывает как реализовать контроллер модуля. Достаточно создать класс, реализующий интерфейс ModuleController и инициализировать контроллер в роутере добавив запись в ini файл:
GuestBook=\PPHP\model\modules\guestBook\Controller
Вы наверно уже заметили, что данная запись как раз указывает на нужный контроллер: namespace PPHP\model\modules\guestBook; class Controller ... Так роутер и находит требуемые контроллеры. Все просто.
Примеры
Выкладываю мою незаконченную систему управления с использованием изложенных выше принципов. Если будут вопросы, прошу в приват, или, если администрация разрешит, можно создать отдельную тему. Надеюсь, будет актуально
Архив:
http://upwap.ru/2607316
Пароль: 3129
URL:
https://visavi.net/articles/436