2. Ваше первое MVC приложение | rpilot62.ru MVC 4

Пример MVC в php. Вторая статья. Маршрутизация, контролеры, экшены, шаблоны и модели

Основ­ная цель при­ме­не­ния MVC состоит в раз­де­ле­нии дан­ных и биз­нес-логики от визу­а­ли­за­ции (внеш­него вида). За счет такого раз­де­ле­ния повы­ша­ется воз­мож­ность повтор­ного исполь­зо­ва­ния про­грамм­ного кода и упро­ща­ется сопро­вож­де­ние (изме­не­ния внеш­него вида, напри­мер, не отра­жа­ются на биз­нес-логике).

Кон­цеп­ция MVC раз­де­ляет дан­ные, пред­став­ле­ние и обра­ботку дей­ствий поль­зо­ва­теля на ком­по­нен­ты:

  • Модель (Model) — предо­став­ляет собой объ­ект­ную модель некой пред­мет­ной обла­сти, вклю­чает в себя дан­ные и методы работы с этими дан­ны­ми, реа­ги­рует на запросы из кон­трол­ле­ра, воз­вра­щая дан­ные и/или изме­няя своё состо­я­ние, при этом модель не содер­жит в себе инфор­ма­ции, как дан­ные можно визу­а­ли­зи­ро­вать, а также не «обща­ется» с поль­зо­ва­те­лем напря­мую.
  • Пред­став­ле­ние (View) — отве­чает за отоб­ра­же­ние инфор­ма­ции (визу­а­ли­за­цию), одни и те же дан­ные могут пред­став­ляться раз­лич­ными спо­со­ба­ми, напри­мер, кол­лек­цию объ­ек­тов при помощи раз­ных «вьюх» можно пред­ста­вить как в таб­лич­ном виде, так и спис­ком.
  • Кон­трол­лер (Controller) — обес­пе­чи­вает связь между поль­зо­ва­те­лем и систе­мой, исполь­зует модель и пред­став­ле­ние для реа­ли­за­ции необ­хо­ди­мой реак­ции на дей­ствия поль­зо­ва­теля, как пра­ви­ло, на уровне кон­трол­лера осу­ществ­ля­ется филь­тра­ция полу­чен­ных дан­ных и авто­ри­за­ция (про­ве­ря­ются права поль­зо­ва­теля на выпол­не­ние дей­ствий или полу­че­ние инфор­ма­ции).

Аль­тер­на­тив­ные назва­ния пат­терна MVC:

  • model-view-controller
  • модель-пред­став­ле­ние-пове­де­ние
  • модель-пред­став­ле­ние-кон­трол­лер
  • модель-вид-кон­трол­лер

В этом уроке я расскажу Вам, что такое архитектура сайта, какое у нее назначение и какой она бывает.

Что такое архитектура программы

Несмотря на то, что значение словосочетания «архитектура сайта» может быть вам интуитивно понятным, давайте попробуем изучить ряд определений, принадлежащих авторитетным источникам.

Архитектура — это базовая организация системы, воплощенная в ее компонентах, их отношениях между собой и с окружением, а также принципы, определяющие проектирование и развитие системы [IEEE 1471].

Архитектура программы или компьютерной системы — это структура или структуры системы, которые включают элементы программы, видимые извне свойства этих элементов и связи между ними [Басс (Bass)].

Архитектура — это структура организации и связанное с ней поведение системы. Архитектуру можно рекурсивно разобрать на части, взаимодействующие посредством интерфейсов, связи, которые соединяют части, и условия сборки частей. Части, которые взаимодействуют через интерфейсы, включают классы, компоненты и подсистемы [UML 1.5].

Архитектура программного обеспечения системы или набора систем состоит из всех важных проектных решений по поводу структур программы и взаимодействий между этими структурами, которые составляют системы. Проектные решения обеспечивают желаемый набор свойств, которые должна поддерживать система, чтобы быть успешной. Проектные решения предоставляют концептуальную основу для разработки системы, ее поддержки и обслуживания [McGovern (Мак-Говерн)].

Так что же такое архитектура программы?

Приведенные в предыдущем разделе определения слишком сухие для их восприятия неподготовленным читателем. Постараемся объяснить суть как можно проще.

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

Программу будет сложно расширять и изменять.

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

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

Отношения между компонентами системы также определяются ее архитектурой.

В целом, все значимые решения, направленные на организацию программы, а не на проблемы, решаемые с ее помощью, можно отнести к архитектуре.

Уровни абстракции

При проектировании модулей (выбора групп функций и распределения их по файлам в случае процедурного подхода) важно выделять абстракции и пытаться их разложить по нескольким уровням.

Модули низкого уровня максимально автономны, они не зависят от других частей программы. Хорошо спроектированные модули изолируют «внешний мир» от тонкостей решения поставленной перед ними задачи. Вызывающая сторона знает лишь интерфейс модуля (внешние функции), внутренняя часть для нее закрыта.

Рассмотрим в качестве примера галерею фотографий. Информация об изображениях и пользователях хранится в БД, пользовательский интерфейс разделен на клиентскую часть и панель администратора.

Структура программы могла бы быть такой, как на рисунке ниже:

В этом примере прослеживаются три уровня абстракции.

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

Архитектура MVC

Сейчас популярен шаблон проектирования MVC. Он служит для отделения логики приложения от пользовательского интерфейса. Но сначала проясним, что такое шаблон проектирования.

Это набор типовых решений проектирования, каркас архитектуры или ее фрагмента. Если библиотека — это пакет повторно используемого кода, то шаблон проектирования — это пакет повторно используемых решений.

Что же предлагает нам MVC для отделения логики приложения от пользовательского интерфейса?

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

  1. Модель (Model). Модель предоставляет данные (обычно для Представления), а также реагирует на запросы (обычно от Контроллера), изменяя свое состояние;
  2. Представление (View). Отвечает за отображение информации (пользовательский интерфейс);
  3. Контроллер (Controller). Интерпретирует данные, введенные пользователем, и информирует модель и представление о необходимости соответствующей реакции.

На рисунке ниже показаны отношения между компонентами каркаса. Проиллюстрируем рисунок небольшим примером.

Представьте форму, где можно ввести текст, нажать кнопку Edit и получить его транслитерацию:

Повторим шаги, изображенные на схеме:

  1. Пользователь нажимает кнопку Edit, при этом Представление (View) посылает сообщение Контроллеру (Controller): «Команда: edit»
  2. Контроллер принимает сообщение и обращается к Модели (Model), вызывая метод Edit() .
  3. В результате модель меняет свое состояние (запомненный в ней транслитерированный текст) и оповещает об этом представление: «Событие: changed».
  4. Представление принимает сигнал и обращается к модели за новым значением результата, вызывая ее метод Get() .

Реализация MVC

Реализация MVC предполагает объектно-ориентированный подход (ООП). Однако шаблон проектирования — это всего лишь набор решений. Адаптируем их для PHP без применения ООП. Упрощение делается для того, чтобы сконцентрироваться на сути разделения логики, а также для того, чтобы материал смог применить читатель, не знакомый с ООП.

Рассмотрим снова пример с галереей фотографий.
У нее есть два режима просмотра:

  1. Режим просмотра уменьшенных изображений (всех сразу);
  2. Режим просмотра фотографии полного размера (одной).

Также есть возможность загружать фотографии на сервер. Дополнительно реализуем поддержку типов визуализации, чтобы оценить гибкость каркаса.

На сайте будут две точки входа:

  1. index.php (просмотр галереи);
  2. photo.php (просмотр полноразмерной фотографии).

Эти два файла будем считать Контроллерами.

В качестве Модели будет выступать модуль, обеспечивающий работу с хранилищем изображений. Назовем его gallery.php и поместим в папку model.

В роли Представления будут выступать HTML-шаблоны, они будут находиться в папке templates. Что такое шаблоны и для чего они нужны — будет видно дальше.

Страницы просмотра галереи и просмотра фотографии будут иметь общую шапку и подвал страницы, отличаться будет только центральная часть.

Просмотр галереи будет иметь два типа визуализации:

  1. В виде таблицы (по умолчанию);
  2. В виде списка.

Нам потребуются четыре шаблона:

  1. main.php (каркас страницы);
  2. content_index_table.php (табличный вид содержимого галереи);
  3. content_index_list.php (списочный вид содержимого галереи);
  4. content_photo.php (содержимое страницы просмотра фотографии).

Получается следующая структура сайта:

Файловая структура разделена двумя горизонтальными чертами, образующими три секции. Файлы верхней секции относятся к Модели, файлы средней секции — к Представлению, файлы нижней секции — к Контроллеру.

Модель

Начнем с реализации Модели. В коде ниже приведен не полностью для минимизации и лучшей наглядности примера.

<?php // Функция возвращает объект фотографии (ассоциативный массив). function gallery_item($id) { // Реализация пропущена, // тип результата — array } // Функция возвращает список фотографий. function gallery_list() { // Реализация пропущена, // тип результата — array } // Функция возвращает путь к уменьшенному изображению. function gallery_icon($photo) { // Реализация пропущена, // тип результата — string } // Функция возвращает путь к полноразмерному изображению. function gallery_image($photo) { // Реализация пропущена, // тип результата — string } // Функция добавляет в галерею новую фотографию. function gallery_add($file_path, $file_name) { // Реализация пропущена } ?>

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

Представление

Теперь рассмотрим шаблоны. Начнем с общего каркаса страницы:

<!DOCTYPE html PUBLIC «-//W3C//DTD HTML 4.01 Transitional//EN»> <html> <head> <meta content=»text/html;charset=windows-1251″ http-equiv=»Content-Type»> <title><?=$title?></title> </head> <body> <h1><?=$title?></h1> <?php include $content; ?> </body> </html>

Вас не должно смущать, что в шаблоне используются непонятно откуда взявшиеся переменные $title и $content .

Их подставит Контроллер. Но об этом позже.

<?=$title?> — это сокращенный вариант записи <?php echo $title; ?>.

Его удобно использовать в шаблонах. Также в шаблонах удобнее использовать альтернативные варианты записи конструкций if-else , foreach , for , while . Выглядят они так:

if (<условие>): <тело> endif; foreach (<инициализация цикла>): <тело> endforeach;

Остальные шаблоны будут подставляться в main.php таким образом:

<?php include $content; ?>

В примерах ниже, приведен их код:

Код templates/content_index_table.php

<b>Таблица</b> | <a href=»index.php?view=list»>Список</a> <br/><br/> <table> <tr> <?php $i = 0; ?> <?php foreach ($photos as $photo): ?> <?php if ($i % 3 == 2): ?> </tr><tr> <?php endif; ?> <td> <a href=»photo.php?id=<?=$photo[‘id’]?>»> <img src=»https://rpilot62.ru/wp-content/uploads/2018/06/34668.jpg» /> </a> </td> <?php $i++; ?> <?php endforeach ?> </tr> </table> <br/><br/> <form method=»post» enctype=»multipart/form-data»> <input type=»file» name=»photo» /> <br/> <input type=»submit» value=»Загрузить файл!» /> </form>

Код templates/content_index_list.php

<a href=»index.php»>Таблица</a> | <b>Список</b> <br/><br/> <?php foreach ($photos as $photo): ?> <a href=»photo.php?id=<?=$photo[‘id’]?>»> <img src=»https://rpilot62.ru/wp-content/uploads/2018/06/34668.jpg» /> </a> <br/> <?php endforeach ?> <br/><br/> <form method=»post» enctype=»multipart/form-data»> <input type=»file» name=»photo» /> <br/> <input type=»submit» value=»Загрузить файл!» /> </form> templates/content_photo.php: <a href=»index.php»>Назад</a> <br/><br/> <img src=»https://rpilot62.ru/wp-content/uploads/2018/06/88751.jpg» />

Контроллер

И, наконец, соберем все вместе, описав наши два Контроллера. Их задача заключается в обработке запроса, выборе шаблона и подстановке нужных шаблону данных. Данные берутся, как правило, из модели.

Контроллер галереи загружает фотографию, если пользователь отправил файл. Иначе он извлекает из модели список фотографий, выбирает нужный шаблон (в зависимости от желания пользователя) и выводит этот шаблон, передав ему список фотографий:

Код index.php

<?php // Подключение библиотек. require_once(‘model/gallery.php’); // Загружаем фотографию, если пользователь отправил файл.

Пишем собственный MVC фреймворк на PHP

if (isset($_FILES[‘photo’])) { gallery_add($_FILES[‘photo’][‘tmp_name’], $_FILES[‘photo’][‘name’]); header(‘Locaton: index.php’); exit(); } // Подготовка данных. $photos = gallery_list(); // Заголовок страницы. $title = ‘Галерея фотографий’; // Выбор шаблона содержимого. $content = ($_GET[‘view’] == ‘list’) ? ‘templates/content_index_list.php’ : ‘templates/content_index_table.php’; // Вывод HTML. include ‘templates/main.php’; ?>

Контроллер просмотра фотографии еще проще:

Код photo.php

<?php // Подключение библиотек. require_once(‘model/gallery.php’); // Подготовка данных. $photo = gallery_item($_GET[‘id’]); // Заголовок страницы. $title = ‘Просмотр фотографии’; // Выбор шаблона содержимого. $content = ‘templates/content_photo.php’; // Вывод HTML. include ‘templates/main.php’; ?>

В заключение

Старайтесь уделять достаточно внимания разработке архитектуры вашего приложения. Если изначально создать прочный и расширяемый каркас, усилия окупятся сторицей.

Для реализации модели MVC лучше выбрать объектно-ориентированный подход.

Существует множество готовых решений каркаса, например в Zend Framework. Однако информации, изложенной в текущем уроке, достаточно для того, чтобы понять архитектурные решения MVC и начать их использовать уже сейчас.

Урок 1: Реализация MVC #1

Длительность: 37 минутСложность: Очень сложно

Курс: PHP практика: «Создание интернет-магазина»


Об уроке

Если Вы дошли до этого занятия, то вполне вероятно, что Вы уже создавали простой сайт из нескольких страниц, используя полученные знания. Даже если это не так, то Вас наверняка посещал вопрос о том, как же правильно строить свои сайты?

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

Конкретно в этом уроке мы рассмотрим общие принципы работы данного подхода, увидим как можно реализовать «сердце» шаблона MVC — компоненты Router и Front Controller используя PHP.

P.S. Написанный код и подход к реализации не претендуют на звание единственно верных.


План

1. Общая информация.

2. Как работает сайт, построенный по принципу MVC? Подробное объяснение.

3.

MVC PHP: Понятие, преимущества, пример

Несколько слов о регулярных выражениях.

4. Реализация: Front controller.

5. Реализация: Router.

6. Создание контроллеров.


Видео


Домашние задания

В данный момент я работаю над дополнительными платными материалами и домашними заданиями этого урока. Как только они станут доступны, информация появится в этом блоке.

Если Вы заинтересовались, напишите мне и я постараюсь подготовить их быстрее.


Полезные ссылки

В занятии я упустил вот этот момент: 

trim($_SERVER[‘REQUEST_URI’], ‘/’);

trim() — весьма полезная функция. Будет не лишним с ней ознакомиться.



Поделитесь в социальных сетях

Tweet

Роутинг — это маршрутизация: входящий URL разбирается специальным образом и по его результату выполняется определенный код. С роутингом напрямую связано понятие ЧПУ (человекопонятные урлы), которое позволяет исключить в адресах сложные параметры. Например вместо пришлось бы использовать

Любой входящий URL на сервере разбирается по единому стандарту. Полностью приводить документацию не буду (см. как пример функцию parse_url), важно лишь понять, что в адресе передается параметр path (путь на сервере), которого на сервере реально может не быть. Например в адресе каталога реально может не существовать.

То есть сервер, получив такой адрес, попытается найти каталог , но не найдя его, выдаст 404-страницу (not found).

Чтобы исключить такой вариант, серверу указывается, что для всех несуществующих каталогов и файлов, подключать php-файл (обычно ).

Делается это в файле с помощью Apache-модуля mod_rewrite. Вот довольно типовой вариант (MaxSite CMS):

RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /index.php/$1 [L,QSA]

Тут главная строчка с RewriteRule — именно она определяет шаблон входящего адреса (в примере это регулярное выражение) и что с ним делать. В данном примере будет подключен index.php с параметрами после слэша.

Строчка указывает исключить из обработки реально существующие на сервере файлы и каталоги.

Похожий вариант, только чуть короче, от WordPress:

RewriteRule . /index.php [L]

Вариант LPF:

RewriteRule ^(.*)$ /index.php?page=$1 [QSA]

Здесь принудительно добавляется query-параметр page.

Еще один распространенный вариант (пожалуй самый «типовой»):

RewriteRule (.*) index.php?$1 [QSA,L]

Все эти RewriteRule-правила делают простую вещь: как бы «преобразуют» входящий адрес в набор query-параметров. Например адрес превратится в

Посмотрите на полный код :

<IfModule mod_rewrite.c> RewriteEngine on RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule (.*) /index.php?$1 [QSA,L] </IfModule>

Если это какой-то подкаталог, то он указываетс в RewriteBase и как путь к php-файлу. Например каталог на сервере :

<IfModule mod_rewrite.c> RewriteEngine on RewriteBase /route/ RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule (.*) /route/index.php?$1 [QSA,L] </IfModule>

Теперь, все адреса на сервере будут передаваться в файл , а исходный адрес сохранится в виде query-параметра.

Если в index.php разместить

<?php print_r($_GET);

То мы можем увидеть query-параметры.

В PHP за это отвечается суперглобальная переменная . Например для это будет , для — .

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

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

Второй этап роутинга выполняется полностью на PHP. Получив нужно решить что с ним делать. Например если адрес admin, подключить файл .

Существуют несколько принципиально разных подходов в организации роутинга. Наиболее популярный подход — это когда в адресе передаётся «действие», которое описывается через php-класс. Такой подход хорошо описан в CodeIgniter:

example.com/class/function/id/

Например пусть будет класс admin в нём метод edit, принимающий параметр $id.

example.com/admin/edit/23 class admin { function edit($id) { … } }

Это сильно утрированный пример, но он хорошо показывает соответствие адреса и php-класса.

Другой вариант похожий, но используется не классы, а функции.

example.com/admin/edit/23 function admin($params) { … }

То есть функция — это первый сегмент, а остальные выступают уже как парметры. Встречается более «продвинутый» вариант.

example.com/admin function admin($params) { … } example.com/admin/edit/23 function admin_edit($params) { … }

То есть имя функции строится по сегментам URL.

Третий, тоже распространенный вариант — адрес указывает на подключаемый файл.

example.com/admin $fn = ‘pages/admin.php’; if (file_exists($fn)) reqiure($fn); else reqiure(‘pages/404.php’);

Здесь все файлы хранятся в каталоге pages и подключаются только если реально существуют. Если файла нет, то подключается предопределенный 404-файл.

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

Строго говоря, роутинг «на классах» тоже использует «файловое» подключение. Вначале подключается файл с кодом класса, а уже после этого выполняется сам класс.

В задачу роутинга входит не только необходимое «действие», но и валидация входящего адреса и его лексический разбор.

Адреса могут строиться по шаблону. Например какой-то адрес должен содержать только номер, а не текст (например , но не ). Или адрес может быть неизменным, но обработчик будет меняться от вида запроса GET или POST. В одном случае нужно подключить одну функцию/файл, в другом — другой. Бывают и более сложные задачи, все их перечислять нет смысла, поэтому появились разного рода php-библиотеки для организации роутинга.

Свой «велосипед» не изобретал только ленивый, но я отмечу довольно известный FastRoute, который вобрал в себя наиболее типичные решения.

В первую очередь это использование регулярных выражений при задании правил, например:

$r->addRoute(‘GET’, ‘/user/{id:[0-9]+}’, ‘handler1’);

Примерно такой же подход используется и в роутинге CodeIgniter.

products/shirts/123 $route[’products/([a-z]+)/(d+)’] = «$1/id_$2»;

То есть входящий адрес должен соответствовать шаблону и только в этом случае он «сработает».

В FastRoute реализована поддержка POST и GET-запросов. Такая возможность интересна, хотя на больших проектах такие вещи лучше делать на уровне самого «действия». Но это уже тонкости. Про эту библиотеку я упоминаю в первую очередь из-за того, что она достаточно популярна и уже используется в нескольких интересных проектах: Slim и Lumen.

Для небольших проектов, конечно же, FastRoute будет избыточна, поэтому можно ограничиться вариантом попроще, да и мозги потренировать. 🙂

ТЗ.

Шаблон проектирования MVC и PHP, Часть 1

Пусть роутинг будет обрабатывать адреса вида , по которому будет подключен файл , где каталог content — это общее хранилище всех страниц сайта, а index.php — обязательный файл с кодом страницы. Роутинг должен проверять реальный файл и если его нет, то подключать 404-страницу. Если никаких параметров нет, то это home-страница. Пусть они будут предопределены.

Решение. По сути задача сводится к преобразованию входящего адреса в путь на сервере. У нас есть массив , где первый ключ и есть входящий URL. Дальше формируем путь к файлу и если он есть, то подключаем. Если нет, то подключаем 404-страницу.

Весь код в 2 строчки:

<?php $fn = ($p = key($_GET)) ? ‘content/’ . $p . ‘/index.php’ : ‘content/home/index.php’; (file_exists($fn)) ? require $fn : require ‘content/404/index.php’; # end of file

Это простой роутинг для простых проектов. Достаточно создать в content каталог, как он станет доступен по одноименному адресу.

к оглавлению     к 4GL — визуальным средам       Deutsch

Введение в MVC для интернет-разработок

Впервые услышав такие слова, как Model View Controller, мне было интересно, но немного не ясно, поскольку, не зная ничего о шаблонах проектирования, разобраться в конкретном их случае, достаточно сложно. Мешало малое количество опыта в разработке приложений, да и вообще малая осведомлённость в подобном вопросе.

Новичку, пожалуй, сразу начинать с рассмотрения вопросов шаблонов не следует, но по мере продвижения в области разработки ПО, уделить внимание этой области, так или иначе, необходимо.

Итак, давайте рассмотрим теоретические выкладки, но применительно к веб-приложениям.

Стандартная схема архитектуры «Модель-Вид-Контроллер» изображена на следующем рисунке: (схема заимствована из книги «Ajax in action» издательского дома «Вильямс»)

Разберём по пунктам данную схему.

В шаблоне , как следует из названия, есть три основных компонента: Модель, Представление, и Контроллер.

(вид) отвечает за отображение информации, поступающей из системы или в систему.

является «сутью» системы и отвечает за непосредственные алгоритмы, расчёты и тому подобное внутреннее устройство системы.

является связующим звеном между «представлением» и «моделью» системы, посредством которого и существует возможность произвести разделение между ними. Контроллер получает данные от пользователя и передаёт их в «модель». Кроме того, он получает сообщения от модели, и передаёт их в «представление».

Применительно к интернет-приложениям бытует мнение, что части и объединены, потому что за отображение и одновременно за ввод информации отвечает браузер. С этим можно согласиться, а можно не соглашаться и выделить-таки контроллер в отдельную часть, что мы и сделаем.

MVC php на пальцах?

Итак, условимся:

Представление. Модуль вывода информации. Это может быть шаблонизатор или что-либо подобное, цель которого является только в выводе информации в виде HTML на основе каких-либо готовых данных.

Контроллер. Модуль управления вводом и выводом данных. Данный модуль должен следить за переданными в систему данными (через форму, строку запроса, cookie или любым другим способом) и на основе введённых данных решить:

  • Передавать ли их в модель

  • Вывести сообщение об ошибке и запросить повторный ввод (заставить модуль обновить страницу с учётом изменившихся условий)

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

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

Достаточно сложно с первого раза разобраться и понять. На это требуется время и подходящий проект.

Но на самом деле ничего сверхсложного в этом нет.

Представим себе в качестве представления какой-либо класс, который с помощью шаблонизатора выводит результат или сообщение об ошибке. На его вход подаётся либо массив с данными (объект или что-либо иное), либо переменная, содержащая текст с ошибкой.

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

Этот же класс должен передать данные, полученные в результате работы модели, в класс для вывода.

Одними словами схему потоков данных в этой архитектуре объяснить сложно, поэтому обратимся к языку UML и к диаграмме последовательностей в частности (незначительные отступления от UML, принятые в диаграммах, заключаются в том, что в некоторых случаях вместе с именами сущностей или объектов, даны переводы в скобках).

На этой диаграмме показана последовательность действий, а также последовательность передаваемых данных: от пользователя, к пользователю и между модулями.

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

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

Теперь попробуем составить диаграмму классов для большей наглядности.

Диаграмма классов содержит три класса, по одному для каждого компонента архитектуры . Для удобства, они так и названы: Model, View, Controller.

В есть три функции (хотя, вполне возможно обойтись только лишь одной), которые отвечают за отображение состояния приложения:
displayDefault() – вывод формы по умолчанию.
displayError(error = false) – вывод формы с сообщением об ошибке, либо отдельной формы с ошибкой
displayResults() – вывод результатов вычислений

имеет не только методы, но и поля. С полями всё просто: это ошибка и результаты вычислений. По умолчанию им задаётся значение false, что свидетельствует о том, что пока нет ни ошибки, ни результатов.

Три метода, присутствующие в контроллере, служат для управления и проверки. Метод для проверки (validate()) является необязательным, и вполне может отсутствовать, если никаких проверок не требуется.

Метод processData() служит для вывода формы по умолчанию, однако он включает также метод userRequest(), функциональность которого выполняется лишь в том случае, когда есть введённые пользователем данные. Именно метод userRequest() содержит в себе функцию validate() (если данные не введены, следовательно, незачем делать их проверку) и, кроме того, должен содержать вызов конструктора класса .

В может содержаться любое количество полей и методов.

Однако два метода должны быть обязательными (или даже один. Как удобнее будет).
calculate() – функция, производящая основной расчёт
getData() – функция, возвращающая данные результата.

Разделение функций скорее смысловое. Вполне достаточно создать один метод, который будет и считать, и возвращать результат.

Возвращаемся в метод userRequest() контроллера. После того, как в нём был посчитан результат и возвращён в том или ином виде, его можно смело отдавать на вход функции displayResults() класса View. Однако заметим, что в принципе, можно отдавать на вход и экземпляр класса , если вывод оного хранится в его полях, а их много (если лень, так сказать, создавать структуру, массив либо ещё какой-либо объёмный тип данных).

Если функция validate() контроллера выявила ошибку, и установила значение поля error в значение, отличное от false, контроллер сам вызовет метод displayError() класса View.

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

Итак, собственно, у нас есть три класса и алгоритм взаимодействия между ними. Суть архитектурного шаблона состоит в том, чтобы чётко разделить представление, управление и модель системы. Это очень удобно, ведь если что-либо поменяется в одной из частей системы, других частей эти изменения не коснутся.

Например, в представлении мы можем написать:

// Это код на PHPpublicfunction displayDefault() {echo«<p>Введите имя:  «; echo«<input type=’text’ name=’name’ value=»>  «; }

А потом через месяц ужаснуться, и перейти к использованию шаблонизатора. Скажем, smarty.

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

В заключении, хотелось бы всё же привести некоторый скелет кода на PHP для лучшего усвоения идеи .

<? /** * Пример реализации MVC на PHP * **/ class Controller { private $error; private $result; function __construct() { $this->error =false; $this->result =false; }function processData() { $this->userRequest(); if ($this->error) View::displayError($this->error); elseif ($this->result) View::displayResults($this->result); else View::displayDefault(); }function userRequest() {// данные отправленыif (isset($_POST[‘send’])) { $this->validate(); if (!$this->error) { // основные вычисления $model =new Model(); $model->calculate($_POST[‘name’]); $result = $model->getData(); // проверка на ошибки в самой моделиif (!is_array($result)) $this->error = $result; else $this->result = $result; }}}function validate() {if (empty($_POST[‘name’])) $this->error =‘Не введено имя!’; elseif (strlen(strval($_POST[‘name’])) < 3) $this->error =‘Имя слишком короткое!’; }}// class Controllerclass View { staticfunction displayDefault() {echo«<form method=’POST’ action=»>»; echo«<p>Введите имя:  «; echo«<input type=’text’ name=’name’ value=»>  «; echo«<input type=’submit’ name=’send’ value=’Отправить’>»; echo«</form>»; }staticfunction displayError($error) { echo«<p><b>Ошибка:</b> {$error}«; View::displayDefault(); }staticfunction displayResults($results) { echo«<p><b>Результаты:</b>»; echo«<p>Ваше имя <b>».$results[0]. «</b> означает <i>».$results[1].«</i>»; echo«<p><a href='».$_SERVER[‘REQUEST_URI’]. «‘>Узнать ещё об одном имени</a>»; }}// class Viewclass Model { private $data; function __construct() { $this->data =false; }function calculate($name) { $this->data[]= $name; $len =strlen($name); if ($len == 3) $this->data[]=‘краткость — сестра таланта’; elseif (($len > 3) && ($len < 6)) $this->data[]=‘…нет особого значения’; else $this->data[]=‘невероятно богатая фантазия родителей’; }function getData() {if ($this->data) return $this->data; elsereturn‘Вычисления не произведены!’; }}// class Model $controller =new Controller(); $controller->processData(); ?>

Работу данного кода можно увидеть в примере.

Итак, мы получили простейшую MVC-систему. Выделим положительные и отрицательные стороны:

К минусам можно отнести

  • Увеличение объема кода

  • Необходимость соблюдения заранее заданного интерфейса

  • Для поддержки разработки требуются более квалифицированные специалисты

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

К плюсам отнесём следующее:

  • Несомненно более гибкий код

  • Возможность повторного использования каждой из трёх составных частей MVC

  • Безболезненная замена модели (другие алгоритмы расчета, способа хранения данных и т.д.)

  • Достаточно просто перейти от одного представления, к другому (от HTML к XML или JSON)

Надо сказать, что код примера не идеален. В нём есть просторы для рефакторинга (несмотря на то, что он занимает чуть более ста строк). Скажем, в примере участвует всего лишь одна переменная, поступающая от пользователя (name), но что если их будет много? Как их хранить? Как сделать так, чтобы при изменении метода, не пришлось править код более чем в одном месте?

Всё это оставьте себе в качестве домашнего задания.

Разбор несколько более сложного примера, можно посмотреть в статье «Рейтинг с помощью MVC».

И напоследок: чаще мыслите не кодом, а схемами, шаблонами, абстракциями. Понятно, что это непросто, непривычно и сложно, но учиться этому нужно обязательно.

P.S. Отметим, что класс контроллера вдобавок построен по архитектурному шаблону , суть которого состоит в сокрытии внутреннего устройства системы и предоставлении пользователю только функций самого высшего уровня. Эту суть убедительно доказывают две последние строки кода.

UPD0: На самом деле шаблон MVC куда более общий, мы рассмотрели, скорее, его вариант в виде — Model View Presenter, где контроллер есть «медиатор» между представлением и моделью. Более того, в случае реализации MVC в GUI приложениях используют не так называемый Flow-подход (контроллер сам обращается к модели и представлению), а событийный подход с использованием шаблона .

UPD1: Фреймворк CakePHP использует шаблон MVC, запрос к фреймворку изображён на этой схеме. Схема очень близка к той, что обсуждалась выше.

UPD2: В контроллере, как правило, лучше использовать динамическое связывание или абстрактную фабрику. Обычно это выглядит следующим образом:

<? $model = ModelAbstractFactory::getFactory(); $controller = new Controller($model); View::display($controller); ?>

Есть способ выделить контроллер не в виде класса, а в виде модуля:

<? $model = ModelAbstractFactory::getFactory(); $model->calculate(); View::display($model); ?>

В таком случае, схема MVC становится более похожей на классическую, но тогда модель должна представлять специальный интерфейс для доступа к данным.

Отметим также, что некоторые фреймворки устроены так, что все три части MVC есть три отдельных файла, которые связываются по названию автоматически.

к оглавлению     к 4GL — визуальным средам       Deutsch

Знаете ли Вы, что любой разумный человек скажет, что не может быть улыбки без кота и дыма без огня, что-то там, в космосе, должно быть, теплое, излучающее ЭМ-волны, соответствующее температуре 2.7ºК. Действительно, наблюдаемое космическое микроволновое излучение (CMB) есть тепловое излучение частиц эфира, имеющих температуру 2.7ºK. Еще в начале ХХ века великие химики и физики Д. И. Менделеев и Вальтер Нернст предсказали, что такое излучение (температура) должно обнаруживаться в космосе. В 1933 году проф. Эрих Регенер из Штуттгарта с помощью стратосферных зондов измерил эту температуру. Его измерения дали 2.8ºK — практически точное современное значение. Подробнее читайте в FAQ по эфирной физике.
НОВОСТИ ФОРУМА
Рыцари теории эфира
 

Добавить комментарий

Закрыть меню