Mvc модель представления. Создадаем страницу «Портфолио». Подборка полезных ссылок по сабжу

Mvc модель представления. Создадаем страницу «Портфолио». Подборка полезных ссылок по сабжу

09.05.2019

В этом руководстве Вы узнаете, как построить простую систему по архитектуре MVC (Model-View-Controller, Модель-Отображение-Контроллер) на PHP 5.1 с использованием возможностей библиотеки SPL (Standard PHP Library, Стандартная Библиотека PHP).

Введение

Добро пожаловать в первое полноценное руководство для PHP 5. Вам понадобится PHP 5.1 с установленной библиотекой SPL, так как мы воспользуемся некоторыми из самых последних возможностей PHP 5.

В этом руководстве я собираюсь показать, как построить простую MVC-систему (архитектура MVC является наиболее распространённым шаблоном проектирования для больших web-приложений). Я проведу Вас через все шаги от начала и до конца создания полноценной MVC-системы.

Одна точка входа

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

У нас будет один файл, обрабатывающий все запросы. Это значит, что нам не придётся мучиться с подключением global.php каждый раз, когда нам нужно создать новую страницу. Эта «одна точка входа» будет называться index.php и на данный момент будет такой:

Как Вы можете заметить, этот скрипт пока ещё ничего не делает, но погодите минутку.

Чтобы направить все запросы на главную страницу, мы воспользуемся mod_rewrite и установим в.htaccess директиву RewriteRule. Вставим следующий код в файл.htaccess и сохраним его в той же директории, что и index.php:

RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?route=$1

Сперва мы проверяем, существует ли запрашиваемый файл, используя директиву RewriteCond, и, если нет, то перенаправляем запрос на index.php. Такая проверка на существование файла необходима, так как иначе index.php будет пытаться обрабатывать все запросы к сайту, включая запросы на изображения. А это нам как раз и не надо.

Если у Вас нет возможности использовать.htaccess или mod_rewrite, то Вам придётся вручную адресовать все запросы к index.php. Другими словами, все ссылки должны будут иметь вид «index.php?route=[здесь-идёт-запрос]». Например, «index.php?route=chat/index».

Теперь, когда все запросы идут через одну точку входа, мы можем начать написание скрипта index.php. Первая вещь, которую мы должны сделать, это инициализация системы. Создадим директорию includes, а в ней файл startup.php (он будет у нас файлом инициализации). Вставим следующий код в index.php:

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

Следующая вещь, которую необходимо сделать, это объект Registry (журнал, реестр) для хранения глобальных значений. Он будет передаваться в отдельные объекты системы и использоваться для доступа к глобальным значениям, причём без необходимости обозначать переменные как «global» или обращаться к массиву $GLOBALS. Почитайте статью «Использование глобальных значений в PHP» для более подробной информации об объекте реестра.

Добавьте следующий код в файл startup.php после того кода, что приведён в предыдущем примере:

$registry = new Registry;

Если сейчас попробовать запустить систему, то можно увидеть следующую ошибку:

Fatal error: Class "Registry" not found in g:\Projects\PHP\content\simple mvc php5\demo\includes\startup.php on line 12

Это, конечно, не большой сюрприз для нас, ведь мы ещё не написали сам класс Registry. Файл с классом можно было бы просто подключить, используя функцию include() (Прим. пер.: кстати говоря, include() не такая уж и функция, а всё-таки выражение языка, управляющая структура, если смотреть по ману), но давайте воспользуемся одной из новых возможностей PHP 5: __autoload().

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

Вставьте этот код перед кодом из предыдущего примера:

// Загрузка классов «на лету» function __autoload($class_name) { $filename = strtolower($class_name) . ".php"; $file = site_path . "classes" . DIRSEP . $filename; if (file_exists($file) == false) { return false; } include ($file); }

Наша функция __autoload() берёт имя класса, переданное ей как аргумент, и проверяет, существует ли файл с похожим именем в директории с классами. Если файла нет, то функция просто вернёт false и выскочит фатальная ошибка. Но если файл существует, он будет загружен. Т.е. объявится необходимый класс, и никакой ошибки не будет.

Мы ещё не создали сам класс Registry, поэтому ошибка всё ещё будет появляться. Давайте же займёмся этим.

Создание класса Registry

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

Для начала создадим директорию classes и в ней файл registry.php. Вставим следующий код в registry.php:

Теперь у нас есть «скелет» класса Registry и нужно нагрузить его методами. Напишем 2 метода: set(), чтобы устанавливать значения и get(), чтобы значения получать. Также можно написать метод remove() для удаления значений. Добавим эти методы в класс Registry:

Function set($key, $var) { if (isset($this->vars[$key]) == true) { throw new Exception("Unable to set var `" . $key . "`. Already set."); } $this->vars[$key] = $var; return true; } function get($key) { if (isset($this->vars[$key]) == false) { return null; } return $this->vars[$key]; } function remove($var) { unset($this->vars[$key]); } ?>

Эти методы простые, они устанавливают, получают и удаляют элементы из массива $vars, который является атрибутом класса. В методе set() мы заодно проверяем, не существует ли уже значение с указанным ключом, и, если существует, то мы генерируем исключение. Это нужно, чтобы избежать случайной перезаписи значений.

Теперь у нас есть полноценный класс Registry, но мы не будем останавливаться на этом. Воспользуемся одной из возможностей библиотеки SPL: ArrayAccess. SPL (сокращённо от Standard PHP Library, Стандартная Библиотека PHP) - это коллекция интерфейсов и классов, предназначенных для решения стандартных проблем. Один из интерфейсов SPL, ArrayAccess, может быть использован, чтобы предоставить доступ к объекту, как к обычному массиву. Посмотрим на такой пример:

set ("name", "Dennis Pallett"); // Получаем значение, используя get() echo $registry->get ("name"); // Получаем значение, используя доступ как к массиву echo $registry["name"] ?>

Фокус в том, что $registry становится как бы массивом, хотя на самом деле это объект. Конечно, ArrayAccess не даёт никаких особых преимуществ, но он позволяет сократить объём кода, так как не придётся каждый раз писать «->get()». Чтобы воспользоваться этим интерфейсом, нужно исправить первую строчку класса («Class Registry») таким образом:

Class Registry Implements ArrayAccess {

Ключевое слово «Implements» говорит интерпретатору, что этим классом мы реализуем интерфейс, чем на самом деле ArrayAccess и является.

Класс, реализующий интерфейс ArrayAccess, должен иметь следующие методы:

Function offsetExists($offset) { return isset($this->vars[$offset]); } function offsetGet($offset) { return $this->get($offset); } function offsetSet($offset, $value) { $this->set($offset, $value); } function offsetUnset($offset) { unset($this->vars[$offset]); }

Эти методы должны быть понятны сами по себе. Дополнительную информацию можно найти в документации SPL.

Теперь, реализовав интерфейс ArrayAccess, мы можем обращаться к объекту, как к обычному массиву. Это наглядно продемонстрировано, как в предыдущем примере, так и в этом:

get ("name"); // Получаем значение, используя доступ как к массиву echo $registry["name"] ?>

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

Модель

«M» или модель – часть MVC-системы, которая отвечает за запросы к базе данных (или другому внешнему источнику) и предоставление информации контроллеру. Можно было бы загружать необходимую модель в зависимости от запроса, но я предпочитаю немного стереть границы между моделью и контроллером именно в этом месте, т.е. контроллер работает с БД непосредственно через библиотеку взаимодействия с БД, нежели чем через отдельную модель. Может быть, Вам захочется сделать это по-другому, тут дело вкуса.

Нам нужно написать код, необходимый для установки соединения с БД и поместить его в index.php. Существует множество замечательных библиотек для работы с БД (включая мою собственную, AutoCRUD), но в PHP 5 уже есть такая библиотека – PDO. Поэтому нет нужды использовать какую-либо другую.

Вставим следующий код в файл index.php (после подключения файла инициализации):

# Соединяемся с БД $db = new PDO("mysql:host=localhost;dbname=demo", "", ""); $registry->set ("db", $db);

В этом примере мы сначала создаём новый экземпляр библиотеки PDO и соединяемся с нашей БД MySQL. Потом делаем переменную $db доступной глобально при помощи нашего класса Registry.

Модельная компонента нашей системы готова, поэтому давайте перейдём к написанию контроллера.

Написание контроллера подразумевает также и написание класса Router, ответственного за загрузку нужного контроллера в зависимости от запроса (вспомните, в index.php через URL передаётся переменная $route).

Класс Router

Класс Router будет разбирать запрос, а потом загружать требуемый контроллер. Создадим «скелет» класса:

registry = $registry; } } ?>

Затем добавим следующие строки в index.php:

# Загружаем router $router = new Router($registry); $registry->set ("router", $router);

Первая вещь, которую мы напишем, это метод setPath() для установки директории, где будут лежать все наши контроллеры. Метод выглядит следующим образом и должен быть добавлен в класс Router:

Function setPath($path) { $path = trim($path, "/\\"); $path .= DIRSEP; if (is_dir($path) == false) { throw new Exception ("Invalid controller path: `" . $path . "`"); } $this->path = $path; }

Потом добавим следующие строки в index.php:

$router->setPath (site_path . "controllers");

Теперь, когда мы установили путь до наших контроллеров, напишем сам метод, ответственный за загрузку контроллера. Этот метод будет называться delegate(), он и будет анализировать запрос. Первый кусочек этого метода такой:

Function delegate() { // Анализируем путь $this->getController($file, $controller, $action, $args);

Как Вы можете видеть, он использует ещё один метод, getController(), чтобы получить название контроллера и несколько других переменных. Этот метод выглядит так:

Private function getController(&$file, &$controller, &$action, &$args) { $route = (empty($_GET["route"])) ? "" : $_GET["route"]; if (empty($route)) { $route = "index"; } // Получаем раздельные части $route = trim($route, "/\\"); $parts = explode("/", $route); // Находим правильный контроллер $cmd_path = $this->path; foreach ($parts as $part) { $fullpath = $cmd_path . $part; // Есть ли папка с таким путём? if (is_dir($fullpath)) { $cmd_path .= $part . DIRSEP; array_shift($parts); continue; } // Находим файл if (is_file($fullpath . ".php")) { $controller = $part; array_shift($parts); break; } } if (empty($controller)) { $controller = "index"; }; // Получаем действие $action = array_shift($parts); if (empty($action)) { $action = "index"; } $file = $cmd_path . $controller . ".php"; $args = $parts; }

Пробежимся по этому методу. Сначала он берёт значение переменной $route из запроса, потом разбивает его на части с помощь функции explode(). Например, запрос «members/view» преобразуется в такой массив: array(‘members’, ‘view’).

Потом при помощи цикла foreach он проходит по каждой части и проверяет, является ли эта часть директорией. Если является, то он приписывает её к пути до файла и проверяет следующую часть. Это позволяет поместить контроллеры в поддиректориях и, таким образом, получить иерархию контроллеров. Если же текущая часть запроса не является директорией, но является файлом, она сохраняется в переменную $controller, и мы выходим из цикла, так как нашёлся контроллер, который нам нужен.

После цикла мы проверяем переменную с именем контроллера. Если она пустая, то используем контроллер «index», который будет у нас контроллером по умолчанию. Потом метод определяет действие, которое необходимо выполнить. Контроллер – это класс, который состоит из нескольких методов. Действие же указывает на конкретный метод. Если действие не указано, будем использовать «index» - действие по умолчанию.

И, наконец, получаем полный путь до файла контроллера, объединяя три переменные: путь, имя контроллера и расширение «php».

Теперь, когда мы проанализировали запрос, пора вызывать метод delegate() для загрузки контроллера и выполнения действия. Полностью метод delegate() выглядит так:

Function delegate() { // Анализируем путь $this->getController($file, $controller, $action, $args); // Файл доступен? if (is_readable($file) == false) { die ("404 Not Found"); } // Подключаем файл include ($file); // Создаём экземпляр контроллера $class = "Controller_" . $controller; $controller = new $class($this->registry); // Действие доступно? if (is_callable(array($controller, $action)) == false) { die ("404 Not Found"); } // Выполняем действие $controller->$action(); }

Проанализировав запрос при помощи метода getController(), мы проверяем, существует ли в действительности файл, и, если нет, то возвращаем простое сообщение об ошибке.

После этого мы подключаем файл с контроллером и создаём экземпляр его класса, называться который должен «Controller_[имя]». Чуть позже мы поговорим о контроллерах более подробно.

Потом мы проверяем, есть ли указанное действие (т.е. метод) и возможно ли к нему обратиться (используем для этого функцию is_callable()). Наконец, мы выполняем непосредственно само действие, на чём роль класса Router и завершается.

Написав полностью метод delegate(), добавим следующую строчку в файл index.php:

$router->delegate();

Если попробовать сейчас запустить систему, то мы увидим следующую ошибку (разумеется, если директории controllers ещё нет):

Fatal error: Uncaught exception "Exception" with message "Invalid controller path: `g:\Projects\PHP\content\simple mvc php5\demo\controllers\`" in g:\Projects\PHPit\content\simple mvc php5\demo\classes\router.php:18 Stack trace: #0 g:\Projects\PHP\content\simple mvc php5\demo\index.php(13): Router->setPath("g:\Projects\PHP...") #1 {main} thrown in g:\Projects\PHP\content\simple mvc php5\demo\classes\router.php on line 18

Или же мы увидим ошибку «404 Not Found», так как ещё нет ни одного контроллера. Но этим-то мы сейчас и займёмся.

Контроллер

Контроллеры в нашей MVC-системе будут достаточно простыми и потребуют совсем немного времени. Во-первых, удостоверимся, что директория controllers существует. Создадим файл controller_base.php в директории classes и вставим в него следующий код:

registry = $registry; } abstract function index(); } ?>

Этот абстрактный класс будет родительским классом для всех наших контроллеров. Он будет делать всего лишь две вещи: сохранять локальную копию класса Registry и при помощи абстрактного метода index() заставлять все дочерние контроллеры реализовывать этот метод.

Напишем наш первый контроллер. Создадим файл index.php в директории controllers и вставим в него такой код:

Только что мы создали наш первый контроллер и, если попробовать запустить систему, то можно увидеть следующее:

Это означает, что класс Router выполнил свою работу и запустил требуемое действие из требуемого контроллера. Давайте напишем ещё один контроллер, который будет соответствовать запросу «members/view». Создадим файл members.php в директории контроллеров и вставим в него такой код:

Теперь зайдём в нашу MVC-систему по запросу «members/view» или же «index.php?route=members/view». Мы должны увидеть такой результат:

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

Теперь, когда у нас есть контроллеры, осталась лишь одна вещь: «V» или «View» («Отображение»).

Отображение

Как и в случае с моделями, есть несколько различных вариантов создания компоненты View в MVC-системе. Мы могли бы научить класс Router автоматически загружать ещё один файл, названный как-нибудь так: «view_{имя}.php». Но чтобы сделать руководство более понятным, напишем класс Template, который будет заниматься выводом шаблонов.

Сначала создадим файл template.php в директории classes и вставим в него следующий код:

registry = $registry; } } ?>

Теперь у нас есть основная структура нашего класс Template. Следующим шагом добавим такой код в файл index.php прямо перед строками, связанными с классом Router:

# Создаём объект шаблонов $template = new Template($registry); $registry->set ("template", $template);

Так как нам понадобится использовать значения из моделей и контроллеров, то напишем метод set() для установки переменных, доступных в шаблонах. Посмотрим на пример:

Function set($varname, $value, $overwrite=false) { if (isset($this->vars[$varname]) == true AND $overwrite == false) { trigger_error ("Unable to set var `" . $varname . "`. Already set, and overwrite not allowed.", E_USER_NOTICE); return false; } $this->vars[$varname] = $value; return true; } function remove($varname) { unset($this->vars[$varname]); return true; }

Методы set() и remove() достаточно простые и используются, соответственно, для установки и удаления переменных.

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

Голосов: 745 | Просмотров: 8083

SRC:
O’Rеillу. ActionScript 3.0 Design Patterns.
Глава 12. Model-View-Controller Pattern.


Что такое шаблон Модель-Представление-Контроллер?

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

Модель (Model)
Содержит данные и логику приложения для управления состоянием этого приложения

Представление (View)
Реализует пользовательский интерфейс и состояние приложения, наблюдаемые на экране

Контроллер (Controller)
Обрабатывает действия пользователя, влияющие на состояние приложения.

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

Модель

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

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

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

Контроллер

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

Допустим, наш цифровой плеер имеет кнопки Громче и Тише в Представлении. Громкость звука является переменной состояния. Модель будет отслеживать эту переменную, чтобы менять значение этой переменной в соответствии с логикой приложения. Если значение громкости звука проградуировать от 0 до 10, Контроллер определит, насколько нужно прибавить или убавить звук при одиночном нажатии на одну из этих кнопок. Поведение может сообщить Модели, что нужно увеличить громкость на 0,5 или на 0,1, или любое другое значение, как задано программно. В таком ключе, Контроллер - это специфичные реализации, которые определяют, каким образом приложение ответит на ввод пользователя.

Хотя каждый элемент в триаде MVC имеет отдельную и уникальную зону ответственности, они не функционируют в изоляции. На самом деле, с тем чтобы составить шаблон MVC, каждому элементу следует коммуницировать с остальными. Что это значит, мы рассмотрим ниже.

Взаимодействие элементов MVC

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

  1. Пользователь взаимодействует с элементом интерфейса (например, нажимает на кнопку в Представлении).
  2. Представление отсылает событие нажатия Контроллеру, чтобы решить, как это нажатие обработать.
  3. Контроллер меняет Модель на основе того, что он решил относительно нажатия кнопки.
  4. Модель информирует Представление о том, что состояние Модели изменилось.
  5. Представление читает информацию о состоянии в Модели и самостоятельно видоизменяется.

Это очень простая схема того, как взаимодействуют элементы MVC. В некоторых случаях Контроллер может просто указать Представлению чтобы оно изменилось. Это единственный случай, когда изменения в Представлении становятся необходимыми из-за действий пользователя и не требуют изменений в Модели, а просто приводят к одним только визуальным изменениям. Например, вспомните о том, как пользователь выбирает песню на цифровом плеере. Он выбирает песню из списка кнопками прокрутки. Представление должно сообщить Контроллеру, что нажаты кнопки Листать вверх или Листать вниз, но Контроллеру не нужно информировать об этом Модель. Он напрямую говорит Представлению прокрутить список песен в нужном направлении. Такое действие пользователя не требует изменений в Модели. Однако, когда пользователь выберет песню из списка и запустит её на воспроизведение, Контроллер изменит Модель, чтобы отразить это изменение в значении воспроизводимой в данный момент песни.

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

Другой особенностью шаблона MVC является то, что каждая такая Модель может иметь более одного Представления, ассоциированного с ней. Например, в нашем портативном плеере установки громкости могут быть отображены на дисплее с помощью индикатора уровня. А кроме того, уровень звука на аудио-выходе соотносится с громкостью звука в наушниках. И дисплей, и звук в наушниках являются Представлениями состояния устройства.

Взгляните на Рисунок 12-1 и обратите внимание на направление стрелок. Они показывают, кто инициирует взаимодействие между элементами. Для того, чтобы один MVC-элемент смог сообщаться с другим, ему нужно знать о нем и владеть ссылкой на этот элемент.

Думайте о Модели, Представлении и Контроллере как о трех разных классах. Давайте посмотрим, каким классам нужно иметь ссылки на остальные классы:

Представление
Ему нужно иметь ссылку и на Модель, и на Контроллер

Контроллер
Ему необходимо владеть ссылкой на Модель

Мы начали с того что заявили, что MVC – это составной шаблон, который объединяет несколько шаблонов. Вам, должно быть, интересно, какие шаблоны вошли в данный составной шаблон. Или, точнее, чем они могут быть представлены? Главным преимуществом использования шаблона MVC является возможность разъединять его на составляющие три элемента. Это позволяет нам увязать несколько Представлений с одной Моделью, заменять Модели и Контроллеры, не затрагивая другие элементы. Но некоторые элементы в триаде MVC должны поддерживать ссылки на остальные элементы, а также поддерживать активный обмен данными между ними. Как мы можем назвать такое разделение? Это имеет отношение к паттернам Observer (Обозреватель) , Strategy (Стратегия) и Composite (Компоновщик) .

Внедрение шаблонов в MVC

Как мы уже рассмотрели, Модель может быть ассоциирована с несколькими Представлениями. В MVC Модели нужно информировать все связанные с ней Представления о происходящих изменениях. К тому же, это нужно делать без знания специфичных подробностей относительно Представлений, и даже без информации о том, скольким Представлениям следует измениться. Эта задача лучше всего решается применением реализации шаблона Обозреватель (см. Главу 8).

Каждая Модель может иметь несколько Представлений, с ней связанных. Так же, Представления могут быть сложными, с несколькими окнами или панелями, внутри которых находятся другие элементы пользовательского интерфейса. Например, такие элементы интерфейса как кнопки, текстовые поля, списки, ползунки и т.п. могут быть сгруппированы в панель с закладками, а панель в свою очередь может быть частью окна наравне с другими панелями. Каждая кнопка или группа кнопок может быть Представлением. То же самое с коллекцией текстовых полей. Представляется полезным обращаться с панелью или окошком, которые содержат коллекции простых Представлений, таким же образом, как мы обращаемся с любыми другими Представлениями. Именно здесь использование шаблона Компоновщик сэкономит нам много сил (см. Главу 6). Почему реализация шаблона Компоновщик столь полезна в данном контексте? Если Представления могут быть вложенными, а они таковы, если созданы с применением шаблона Компоновщик, процесс обновления упрощается. Событие обновления автоматически обойдет все потомственные Представления. Создание сложных Представлений становится проще, когда нет необходимости рассылать индивидуальные сообщения об обновлении каждому вложенному Представлению.

Представления ограничивают свою деятельность лишь внешним представлением состояния Модели. Они передают события пользовательского интерфейса Контроллеру. Поэтому, Контроллер – это в большей степени алгоритм того, как обработать пользовательский ввод в конкретном Представлении. Такое делегирование инкапсулирует реализацию того как конкретный элемент пользовательского элемента ведет себя в условиях изменения Модели. Мы может довольно просто заменить один Контроллер для одного и того же Представления, чтобы получить другое поведение. Это идеальный контекст для реализации щаблона Стратегия.

Минималистичный пример шаблона MVC

Этот простой пример отслеживает нажатия клавиш. Когда нажимается очередная клавиша, это приводит к изменению Модели и информированию Представления о необходимости обновиться. Представление задействует стандартное окно вывода Output во Flash, чтобы поместить туда код символа, который ввел пользователь. Код символа - это числовое значение символа в кодовой таблице текущей раскладки. Этот пример объясняет, каким образом шаблоны Обозреватель, Стратегия и Компоновщик интегрированы в MVC.

Модель как Конкретный Субьект в шаблоне Обозреватель

Взаимоотношения между Моделью и Представлением - это связь между Субъектом и Обозревателем (См. Главу 8). Модель должна реализовать интерфейс Субъекта, который является частью шаблона Обозреватель. К счастью, ActionScript 3.0 имеет встроенные классы, уже реализующие этот принцип, используя модель событий ActionScript чтобы информировать Обозревателей об изменениях.

Класс EventDispatcher в ActionScript 3.0

Класс EventDispatcher снабжен интерфейсом IEventDispatcher. Наравне с другими методами, интерфейс IEventDispatcher определяет следующие методы, необходимые для субъекта в шаблоне Обозреватель. (См. документацию по AS3 для подробной информации о всех параметрах методов).

addEventListener(type :String , listener:Function , useCapture:Boolean = false , priority:int = 0 , useWeakReference:Boolean = false ) :void removeEventListener(type :String , listener:Function , useCapture:Boolean = false ) :void dispatchEvent(event:Event) :Boolean

Чтобы Модель могла выступить в качестве Конкретного Субъекта в шаблоне Обозреватель, необходимо реализовать интерфейс IEventDispatcher. Однако, самый простой способ для определенного пользовательского класса заполучить способность распространять события – это наследовать от класса EventDispatcher.

Обозреватель регистрирует методы слушателя, чтобы получать уведомлении от объектов EventDispatcher, методом addEventListener().

Модель

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

Пример 12-1. IModel.as

package { import flash.events .* ; public interface IModel extends IEventDispatcher { function setKey(key :uint) :void ; function getKey() :uint; } }

Интерфейс IModel, показанный в Примере 12-1, расширяет интерфейс IEventDispatcher и определяет пару методов для прочтения и установки кода символа последней нажатой клавиши. Поскольку интерфейс IModel расширяет IEventDispatcher, любой класс, реализующий его, должен реализовать все методы, определенные в обоих интерфейсах. Класс Model, показанный в Примере 12-2, реализует интерфейс IModel.

Пример 12-2. Model.as

package { import flash.events .* ; public class Model extends EventDispatcher implements IModel { private var lastKeyPressed:uint=0 ; public function setKey(key :uint) :void { this .lastKeyPressed =key ; dispatchEvent(new Event(Event.CHANGE ) ) ; // распространяется событие } public function getKey() :uint { return lastKeyPressed; } } }

Класс Model расширяет класс EventDispatcher, который уже реализовал интерфейс IEventDispatcher. Обратите внимание, что функция dispatchEvent() вызывается внутри метода setKey(). Она отсылает событие CHANGE всем зарегистрированным обозревателям, как только значение lastKeyPressed изменится внутри метода setKey().

Контроллер как Конкретная Стратегия в шаблоне Стратегия.

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

Контроллер

В нашем минималистичном примере поведение, требуемое от Контроллера, это всего лишь принятие события о нажатии клавиши. IKeyboardInputHandler - это интерфейс Стратегии (Пример 12-3), где определен единственный метод keyPressHandler().

Пример 12-3. IKeyboardInputHandler.as

package { import flash.events .* ; public interface IKeyboardInputHandler { function keyPressHandler(event:KeyboardEvent) :void ; } }

Конкретным Контроллером будет класс Controller (Пример 12-4), который реализует интерфейс IKeyboardInputHandler.

Пример 12-4. Controller.as

package { import flash.events .* ; public class Controller implements IKeyboardInputHandler { private var model:IModel; public function Controller(aModel:IModel) { this .model =aModel; } public function keyPressHandler(event:KeyboardEvent) :void { model.setKey (event.charCode ) ; // изменяем модель } } }

Обратите внимание, что Контроллер имеет конструктор, который в качестве параметра принимает экземпляр Модели. Это необходимо для того, чтобы Контроллер смог установить связь с Моделью, как это показано на Рисунке 12-1. Поэтому необходимо хранить ссылку на Модель.

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

Представление как Конкретный Обозреватель в шаблоне Обозреватель и Контекст в шаблоне Стратегия

Представление, возможно, наиболее сложный элемент в шаблоне MVC. Он играет роль интегрирующей части в реализации шаблонов как Обозревателя, так и Стратегии, что формирует основу его взаимоотношения с Моделью и Контроллером. Класс View, показанный в Примере 12-5, реализует Представление в минималистичном примере.

Пример 12-5. View.as

package { import flash.events .* ; import flash.display .* ; public class View { private var model:IModel; private var controller:IKeyboardInputHandler; public function View(aModel:IModel,oController:IKeyboardInputHandler,target :Stage ) { this .model =aModel; this .controller =oController; // подписывается на получение уведомлений от Модели model.addEventListener (Event.CHANGE ,this .update ) ; // подписывается на получение нажатий клавиш от сцены target .addEventListener (KeyboardEvent.KEY_DOWN ,this .onKeyPress ) ; } private function update(event:Event) :void { // получение данных от Модели и обновление Представления trace (model.getKey () ) ; } private function onKeyPress(event:KeyboardEvent) :void { // обработка передается в Контроллер (Стратегия) на обработку controller.keyPressHandler (event) ; } } }

Представление нуждается в ссылках на Модель и на Контроллер для взаимодействия с ними, как показано на Рисунке 12-1. И экземпляр Модели, и экземпляр Контроллера передаются Представлению в его конструкторе. К тому же, Представление в нашем примере нуждается в ссылке на сцену (Stage), чтобы зарегистрировать себя как получателя событий нажатия клавиш.

Кроме того что класс View рисует пользовательский интерфейс, он выполняет еще пару важных задач. Он регистрируется у Модели для получения событий об обновлении, и делегирует Контроллеру обработку ввода пользователя. В нашем примере Представление не имеет внешнего видимого присутствия на сцене, но отображает состояние Модели в окне вывода Output. Ему нуждается в получении события нажатия клавиши, и регистрирует метод onKeyPress() для получения события KEY_DOWN от сцены. Вторая задача – это зарегистрировать метод слушателя update() для получения события CHANGE от модели. При получении уведомления об изменении, метод update() прочитывает код последней нажатой клавиши из Модели и печатает его в окне вывода, используя функцию trace().

Построение триады MVC

Мы рассмотрели реализацию каждой из трех составляющих шаблон MVC частей по отдельности. Однако, должен существовать клиент, который инициализирует каждый элемент и построит модель MVC. На самом деле, никакого сложного построения не будет - все что нужно уже сделано при написании классов Модели, Представления и Контроллера. В Примере 12-6 приводится класс Flash-документа, который иллюстрирует элементы MVC.

Пример 12-6. Main.as (основной класс минималистичного примера)

package { import flash.display .* ; import flash.events .* ; /** * Main Class * @ purpose: Document class for movie */ public class Main extends Sprite { public function Main() { var model:IModel=new Model ; var controller:IKeyboardInputHandler=new Controller(model) ; var view:View=new View(model,controller,this .stage ) ; } } }

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

Вам нужно запретить шорткаты для тестирования нажатий клавиш. В противном случае пользовательский интерфейс Flash перехватит события нажатия клавиш, которые соответствуют шорткатам. Чтобы запретить шорткаты, выберите Disable Keyboard Shortcuts из меню Control во время выполнения ролика.

Обратите внимание, что экземпляр Модели передается Контроллеру. Подобным образом экземпляры Модели и Контроллера передаются Представлению. Мы можем просто заместить существующие Модель и Контроллер другими, при условии, что они реализуют интерфейсы IModel и IKeyboardInputHandler. Дополнительные Представления также могут быть безболезненно добавлены прямо в отношения Субъект-Обозреватель между Моделью и Представлением. Модель ничего не знает о Представлениях, так как это забота Представления - зарегистрировать себя в качестве слушателя уведомлений об изменении Модели. Это большой плюс шаблона MVC; Модель, Представление и Контроллер разделены, слабо связаны, что придает гибкости в их использовании.

Вложенные Представления и узлы шаблона Компоновщик

Как вы помните, Представление, возможно, самый сложный элемент в триаде MVC, поскольку в контексте MVC он задействован как в реализации шаблона Обозреватель, так и в Стратегии. Наши элементы Представления способны быть более сложными, поскольку могут реализовать третий шаблон - Компоновщик (см. примеры шаблона Компоновщик в Главе 6). Реализация Представлений как элементов шаблона Компоновщик позволяет разобраться со сложными пользовательскими интерфейсами, которые содержат множественные Представления. Вложение Представлений превносит некоторые преимущества в процесс обновления пользовательского интерфейса, так как обновления могут распространяться по ветвям структурного дерева составного Представления. Также составные Представления могут добавлять и удалять вложенные Представления, основываясь на режиме работы приложения и пользовательских настройках. Хорошим примером сложного интерфейса является панель Properties Inspector в среде разработки Flash. Содержимое Properties Inspector зависит от контекста, и элементы интерфейса появляются и исчезают в зависимости от того, какой объект выделен на сцене.

Компонент и составное Представление

Первым шагом будет создание компонента и составных классов для Представления. Эти классы должны быть описаны как абстрактные, должны быть подклассами и не должны порождать экземпляры, как показано в Примере 12-7.

Пример 12-7. ComponentView.as

package { import flash.errors .IllegalOperationError ; import flash.events .Event ; import flash.display .Sprite ; public class ComponentView extends Sprite { { protected var model:Object ; protected var controller:Object ; public function ComponentView(aModel:Object ,aController:Object =null ) { this .model =aModel; this .controller =aController; } public function add (c:ComponentView) :void { throw new IllegalOperationError("add operation not supported" ) ; } public function remove(c:ComponentView) :void { throw new IllegalOperationError("remove operation not supported" ) ; } public function getChild(n:int ) :ComponentView { throw new IllegalOperationError("getChild operation not supported" ) ; return null ; } // АБСТРАКТНЫЙ метод(должен быть замещен в классе-потомке) public function update(event:Event=null ) :void { } } } }

Класс ComponentView из Примера 12-7 определяет абстрактный интерфейс для Представления компонента. Это похоже на класс классического компонента из Главы 6, но с несколькими ключевыми отличиями. Класс ComponentView хранит ссылку на Модель и Представление, и содержит конструктор. Не все Представления обрабатывают ввод пользователя, и компонентное Представление может быть сконструировано с простой передачей экземпляра Модели. Поэтому параметр aController принимает в конструкторе значение null по умолчанию. Также обратите внимание, что класс ComponentView унаследован от класса Sprite. Это логично, поскольку большинство Представлений рисуют пользовательский интерфейс на сцене. Мы можем использовать свойства и методы, реализованные в классе Sprite, для рисования и добавления объектов в список отображения.

Метод update() должен вести себя как абстрактный метод. Дочерние Представления, являющиеся потомками ComponentView, должны заместить и реализовать метод update(), чтобы уметь обновлять свою часть пользовательского интерфейса. По этой причине методу передается параметр типа Event. Этот параметр также по умолчанию установлен в null, что позволяет вызывать update() без передачи события как параметра. Такой подход полезен, когда изначально отрисованный пользовательский интерфейс находится в своем состоянии по умолчанию, и наш следующий пример иллюстрирует это.

Класс CompositeView расширяет ComponentView и замещает методы, которые отвечают за дочерние Представления.

Пример 12-8. CompositeView.as

package { import flash.events .Event ; // АБСТРАКТНЫЙ класс (от него нужно наследовать, не создавая экземпляра данного класса) public class CompositeView extends ComponentView { private var aChildren:Array ; public function CompositeView(aModel:Object ,aController:Object =null ) { super (aModel,aController) ; this .aChildren =new Array ; } override public function add (c:ComponentView) :void { aChildren.push (c) ; } override public function update(event:Event=null ) :void { for each (var c:ComponentView in aChildren) { c.update (event) ; } } } }

Обратите внимание на замещаемую (override) функцию update() класса CompositeView в Примере 12-8. Она вызывает метод update у всех дочерних классов. Поэтому, вызов функции update() в корне структуры составного Представления приведет к распространению обновления по структуре и обойдет дерево компонента, обновляя все Представления. Давайте расширим классы CompositeView и ComponentView, и создадим структуру Представлений, чтобы посмотреть как это работает

Концепция MVC (Model-View-Controller: модель-вид-контроллер) очень часто упоминается в мире веб программирования в последние годы. Каждый, кто хоть как-то связан с разработкой веб приложений, так или иначе сталкивался с данным акронимом. Сегодня мы разберёмся, что такое - концепция MVC, и почему она стала популярной.

Древнейшая история

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

Впервые она была описана в 1979 году, конечно же, для другого окружения. Тогда не существовало концепции веб приложения. Tim Berners Lee (Тим Бернерс Ли) посеял семена World Wide Web (WWW) в начале девяностых и навсегда изменил мир. Шаблон, который мы используем сегодня, является адаптацией оригинального шаблона к веб разработке.

Бешеная популярность данной структуры в веб приложениях сложилась благодаря её включению в две среды разработки, которые стали очень популярными: Struts и Ruby on Rails. Эти две среды разработки наметили пути развития для сотен рабочих сред, созданных позже.

MVC для веб приложений

Идея, которая лежит в основе конструкционного шаблона MVC, очень проста: нужно чётко разделять ответственность за различное функционирование в наших приложениях:

Приложение разделяется на три основных компонента, каждый из которых отвечает за различные задачи. Давайте подробно разберём компоненты на примере.

Контроллер (Controller)

Контроллер управляет запросами пользователя (получаемые в виде запросов HTTP GET или POST, когда пользователь нажимает на элементы интерфейса для выполнения различных действий). Его основная функция — вызывать и координировать действие необходимых ресурсов и объектов, нужных для выполнения действий, задаваемых пользователем. Обычно контроллер вызывает соответствующую модель для задачи и выбирает подходящий вид.

Модель (Model)

Модель - это данные и правила, которые используются для работы с данными, которые представляют концепцию управления приложением. В любом приложении вся структура моделируется как данные, которые обрабатываются определённым образом. Что такое пользователь для приложения — сообщение или книга? Только данные, которые должны быть обработаны в соответствии с правилами (дата не может указывать в будущее, e-mail должен быть в определённом формате, имя не может быть длиннее Х символов, и так далее).

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

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

Вид (View)

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

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

Разберём пример

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

У нас есть определённый контроллер для обработки всех действий, связанных с книгами (просматривать, редактировать, создавать и так далее). Давайте назовем его books_controller.php в нашем примере. Также нам нужна модель, например, book_model.php , которая обрабатывает данные и логику, связанные с позицией в магазине. В заключение, нам нужно несколько видов для представления данных, например, список книг, страница для редактирования и так далее.

Следующий рисунок показывает, как обрабатывается запрос пользователя для просмотра списка книг по теме фэнтези :

Контроллер (books_controller.php) получает запрос пользователя (запрос HTTP GET или POST). Мы можем организовать центральный контроллер, например, index.php, который получает запрос и вызывает books_controller.php.

Контроллер проверяет запрос и параметры, а затем вызывает модель(book_model.php), запрашивая у неё список доступных книг по теме фэнтези .

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

Контроллер использует подходящий вид для представления данных пользователю . Если запрос приходит с мобильного телефона, используется вид для мобильного телефона; если пользователь использует определённое оформление интерфейса, то выбирается соответствующий вид, и так далее.

В чем преимущества?

Самое очевидное преимущество, которое мы получаем от использования концепции MVC — это чёткое разделение логики представления (интерфейса пользователя) и логики приложения.

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

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

А зачем использовать рабочую среду?

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

Рассмотрим cakePHP в качестве примера рабочей среды MVC. После установки у вас будет три основных директории:

  • cake/
  • vendors/

Папка app является местом размещения ваших файлов. Это место для разработки вашей части приложения.

В папке cake размещаются файлы cakePHP (функциональность рабочей среды).

Папка vendors служит для хранения библиотек PHP сторонних разработчиков.

Ваше рабочее пространство (директория app) имеет следующую структуру:

  • app/
    • config/
    • controllers/
    • locale/
    • models/
    • plugins/
    • tests/
    • vendors/
    • views/
    • webroot/

Вам нужно размещать ваши контроллеры в директории controllers , модели в директории models и виды в директории views !

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

Использование рабочей среды для нашего примера

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

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

Итак, как только пользователь нажимает кнопку, браузер запрашивает данный url:

Www.ourstore.com/books/list/fantasy

CakePHP форматирует URL по шаблону /controller/action/param1/param2 , где action - это функция, которая вызывается контроллером. В старом классическом виде url будет выглядеть так:

Www.ourstore.com/books_controller.php?action=list&category=fantasy

Контроллер

В рабочей среде cakePHP, наш контроллер будет выглядеть так:

class BooksController extends AppController {

Function list($category) {

$this->set("books", $this->Book->findAllByCategory($category));

Function add() { ... ... }

Function delete() { ... ... }

... ... } ?>

Просто, не так ли?. Данный контроллер будет сохранен как books_controller.php и размещён в /app/controllers . Он содержит список функций, которые выполняют действия для нашего примера, а также другие функции для выполнения связанных с книгами операций (добавить новую книгу, удалить книгу, и так далее).

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

Все что нужно сделать в списке действий — вызвать модель для получения данных и затем выбрать вид для представления их пользователю. Вот как это делается.

this->Book - это наша модель, и часть кода:

$this->Book->findAllByCategory($category)

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

Метод set в строке:

$this->set("books", $this->Book->findAllByCategory($category));

Контроллер передаёт данные виду. Переменная books принимает данные, возвращённые моделью, и они становятся доступными для вида.

Теперь остаётся только вывести на экран вид, но эта функция выполняется автоматически в cakePHP, если мы используем вид по умолчанию. Если мы хотим использовать другой вид, то надо явно вызвать метод render .

Модель

Модель даже ещё проще:

class Book extends AppModel {

Почему она пустая? Потому что она является наследником базового класса, который обеспечивает необходимую функциональность и нам нужно использовать соглашение об именах в CakePHP для того, чтобы рабочая среда выполняла все другие задачи автоматически. Например, cakePHP известно на основании имени, что данная модель используется в BooksController , и что она имеет доступ к таблице базы данных с именем books.

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

Код сохраняем как book.php в папке /app/models .

Вид

Все, что нам нужно теперь сделать — это создать вид (по крайней мере, один) для списка действий. Вид будет иметь код HTML и несколько (как можно меньше) строк кода PHP для организации цикла по массиву книг, которые предоставляется моделью.












Название Автор Цена

Как можно заметить, вид создаёт не полноценную страницу, а лишь фрагмент HTML (таблицу в данном случае). Потому, что CakePHP обеспечивает другой способ для определения шаблона страницы, и вид вставляется в данный шаблон. Рабочая среда также обеспечивает нас некоторыми вспомогательными объектами для выполнения общих задач во время создания частей HTML страницы (вставка форм, ссылок, Ajax или JavaScript).

Сохраняем вид как list.ctp (list — это имя действия, а ctp означает шаблон CakePHP) в папке /app/views/books (потому, что это вид для действия контроллера).

Вот так выполняются все три компонента с помощью рабочей среды CakePHP!

Что такое MVC?

Итак, MVC - это про пользовательский интерфейс (UI). Не обязательно графический, голосовое управление тоже годится. Не забудем, что программа может не иметь пользовательского интерфейса, может иметь программный интерфейс (API) или вообще никакого не иметь и всё ещё быть полезной.

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

Программа совершенно абстрактная, любой предметный код. Она умеет делать что-то полезное, и у пользователя есть нужды, которые можно удовлетворить с помощью этой программы. Тогда появляются кусочки логики, которые «знают», как, используя эту программу, сделать непосредственно то, что хочет пользователь. Кусочки - не предметные, предметная логика в программе. Они больше относятся к пользователю с его конкретными потребностями, и представляют собой комбинации вызовов и обращений к программе.

Юзкейсы

В качестве примера представьте терминал для торговли на бирже. Пользователь терминала выставляет заявку, в которой указывает, что он хочет купить акции компании «Светлый путь» в количестве 20 штук по цене 1500 рублей за акцию. Также указывает, что заявка действительна в течение четырёх часов, и с какого из его счетов списать деньги, в случае успешной сделки.

Ощутимое количество атрибутов. Проходит некоторое время, и он понимает, что по такой цене купить не удастся и готов поднять цену до 1550 рублей, оставив все остальные значения. Тогда он выбирает эту заявку, нажимает кнопку «изменить», указывает новую цену, да. Это удобно.

Но на бирже нельзя изменить заявку, в предметной области нет такого понятия. Заявку можно только выставить и отменить. Чтобы дать пользователю возможность в один клик менять заявку, надо запоминать старые значения, снимать заявку, давать редактировать то, что запомнили, и выставлять новую заявку. Такая комбинация. Но для пользователя она выглядит как одно простое действие: изменение заявки. Это называется - use case.

Дополним нашу диаграмму местом под юзкейсы.

Ещё пользователю надо дать возможность дёргать эти юзкейсы и получать результат. Это могут быть кнопки и другие графические элементы ввода-вывода, жесты, распознавание и синтез речи. Любой вариант обмена данными и командами. Вуаля:

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

Так где же тут все-таки MVC?

Все, что осталось - это только раздать знакомые имена образовавшимся компонентам.

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

Теперь немного частностей.

Это был классический вариант MVC - Active Model. Бывает и так, что модель не оповещает об изменениях. Тогда эту обязанность берёт на себя контроллер. Он знает, какие манипуляции производит над моделью, и, очевидно, знает, какие изменения в состоянии модели могут последовать. Это Passive Model.

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

За материал благодарим нашего подписчика Станислава Ильичева

Паттерн Model-View-Controller (MVC) , открытый в в конце 1970-х, представляет собой шаблон проектирования архитектуры программного обеспечения, основной задачей которого является отделение функций работы с данными от их представления. Теоретически, грамотно спроектированное MVC-приложение позволит фронтенд и бэкенд разработчикам в ходе работы не вмешиваться в зоны ответственности друг друга, то есть фронтенд-разработчику не понадобиться что-либо знать о «кухне» своего бэкенд-коллеги и наоборот.

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

В этой статье мы рассмотрим базовые принципы MVC, начав с определения паттерна и продолжив его применением в небольшом примере. Эта статья будет прежде всего полезна тем, кто ещё никогда не сталкивался с этим паттерном в жизни, а также, возможно, и тем, кто желает освежить в памяти знания об MVC.

Понимание MVC

Как уже было сказано, название паттерна происходит от аббревиатуры трёх слов: Model (модель), View (представление) и Controller (контроллер) . Вкратце принцип работы паттерна можно проиллюстрировать одной схемой ( можно найти на Википедии):

Эта схема наглядно показывает однонаправленность потока информации в паттерне, а также описывает роли каждого компонента.

Модель

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

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

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

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

Представление — это то, где данные, полученные от модели, выводятся в нужном виде. В традиционных веб-приложениях, разработанных в рамках MVC-паттерна, представление — это часть системы, где выполняется генерация HTML-кода. Представление также отвечает за получение действий от пользователя с тем чтобы отправить их контроллеру. Например, представление отображает кнопку в пользовательском интерфейсе, а после её нажатия вызывает соответствующее действие контроллера.

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

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

Помимо этого, существует распространённое заблуждение о том, что представление — это просто темплейт-файл. Как заметил Tom Butler, это заблуждение имеет огромный масштаб из-за того, что многие разработчики с самого начала неправильно понимают структуру MVC, после чего начинают вливать эти «знания» дальше, массы начинающих разработчиков. В действительности представление — это гораздо больше, чем просто темплейт, однако много фреймворков, построенных на базе MVC-паттерна, настолько исказили концепцию представления, что уже всем пофигу, насколько правильными являются их приложения с точки зрения MVC-паттерна.

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

Контроллер

Контроллер — это последняя часть связки MVC. Задачей контроллера является получение данных от пользователя и манипуляция моделью. Именно контроллер, и только он, является той частью системы, которая взаимодействует с пользователем.

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

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

MVC в PHP

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

string = "MVC + PHP = Awesome!"; } } controller = $controller; $this->

" . $this->model->string . "

"; } } model = $model; } }

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

output();

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

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

string = “MVC + PHP = Awesome, click here!”; } } controller = $controller; $this->model = $model; } public function output() { return "

model->string . "

"; } } model = $model; } public function clicked() { $this->model->string = “Updated Data, thanks to MVC and PHP!” } }

И в завершение немного модернизируем связующий код:

{$_GET["action"]}(); } echo $view->output();

Итоги

В этой небольшой статье мы рассмотрели основные концепции шаблона проектирования MVC и разработали простенькое приложение на его базе, хотя конечно, нам ещё очень далеко до того, чтобы использовать это в реальной жизни. В следующей статье мы рассмотрим основные затруднения, с которыми вы столкнётесь, если плотнее займётесь построением архитектуры приложения на базе MVC-паттерна. Stay tuned!



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows