Mvc php пошаговая инструкция. Паттерны для новичков: MVC vs MVP vs MVVM. Диаграмма Состояний игровой клетки

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 общается с остальными весьма специфичными способами. Коммуницирование реализуется последовательностью событий, которые обычно запускаются взаимодействием пользователя с приложением. Последовательность событий выглядит так:

  • Пользователь взаимодействует с элементом интерфейса (например, нажимает на кнопку в Представлении).
  • Представление отсылает событие нажатия Контроллеру, чтобы решить, как это нажатие обработать.
  • Контроллер меняет Модель на основе того, что он решил относительно нажатия кнопки.
  • Модель информирует Представление о том, что состояние Модели изменилось.
  • Представление читает информацию о состоянии в Модели и самостоятельно видоизменяется.
  • Это очень простая схема того, как взаимодействуют элементы 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 паттерна, который лежит в основе всех современных веб-фреймворков, а также получить «пищу» для дальнейших размышлений над тем - «как стоит делать». В конце статьи приводится подборка полезных ссылок, которые также помогут разобраться из чего состоят веб-фреймворки (помимо MVC) и как они работают.

    Прожженные PHP-программисты вряд ли найдут в данной статье что-то новое для себя, но их замечания и комментарии к основному тексту были бы очень кстати! Т.к. без теории практика невозможна, а без практики теория бесполезна, то сначала будет чуть-чуть теории, а потом перейдем к практике. Если вы уже знакомы с концепцией MVC, можете пропустить раздел с теорией и сразу перейти к практике.

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

    Рассмотрим концептуальную схему шаблона MVC (на мой взгляд - это наиболее удачная схема из тех, что я видел):

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

    Типичную последовательность работы MVC-приложения можно описать следующим образом:

  • При заходе пользователя на веб-ресурс, скрипт инициализации создает экземпляр приложения и запускает его на выполнение.
    При этом отображается вид, скажем главной страницы сайта.
  • Приложение получает запрос от пользователя и определяет запрошенные контроллер и действие. В случае главной страницы, выполняется действие по умолчанию (index ).
  • Приложение создает экземпляр контроллера и запускает метод действия,
    в котором, к примеру, содержаться вызовы модели, считывающие информацию из базы данных.
  • После этого, действие формирует представление с данными, полученными из модели и выводит результат пользователю.
  • Модель - содержит бизнес-логику приложения и включает методы выборки (это могут быть методы ORM), обработки (например, правила валидации) и предоставления конкретных данных, что зачастую делает ее очень толстой, что вполне нормально.
    Модель не должна напрямую взаимодействовать с пользователем. Все переменные, относящиеся к запросу пользователя должны обрабатываться в контроллере.
    Модель не должна генерировать HTML или другой код отображения, который может изменяться в зависимости от нужд пользователя. Такой код должен обрабатываться в видах.
    Одна и та же модель, например: модель аутентификации пользователей может использоваться как в пользовательской, так и в административной части приложения. В таком случае можно вынести общий код в отдельный класс и наследоваться от него, определяя в наследниках специфичные для подприложений методы.

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

    Контроллер - связующее звено, соединяющее модели, виды и другие компоненты в рабочее приложение. Контроллер отвечает за обработку запросов пользователя. Контроллер не должен содержать SQL-запросов. Их лучше держать в моделях. Контроллер не должен содержать HTML и другой разметки. Её стоит выносить в виды.
    В хорошо спроектированном MVC-приложении контроллеры обычно очень тонкие и содержат только несколько десятков строк кода. Чего, не скажешь о Stupid Fat Controllers (SFC) в CMS Joomla. Логика контроллера довольно типична и большая ее часть выносится в базовые классы.
    Модели, наоборот, очень толстые и содержат большую часть кода, связанную с обработкой данных, т.к. структура данных и бизнес-логика, содержащаяся в них, обычно довольно специфична для конкретного приложения.

    1.1. Front Controller и Page Controller В большинстве случае, взаимодействие пользователя с web-приложением проходит посредством переходов по ссылкам. Посмотрите сейчас на адресную строку браузера - по этой ссылке вы получили данный текст. По другим ссылкам, например, находящимся справа на этой странице, вы получите другое содержимое. Таким образом, ссылка представляет конкретную команду web-приложению.

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

    Рассмотрим два варианта адресной строки, по которым показывается какой-то текст и профиль пользователя.

    Приблизительный код обработки в таком случае:
    switch($_GET["action"]) { case "about" : require_once("about.php"); // страница "О Нас" break; case "contacts" : require_once("contacts.php"); // страница "Контакты" break; case "feedback" : require_once("feedback.php"); // страница "Обратная связь" break; default: require_once("page404.php"); // страница "404" break; }
    Думаю, почти все так раньше делали.

    С использованием движка маршрутизации URL вы сможете для отображения той же информации настроить приложение на прием таких запросов:
    http://www.example.com/contacts/feedback

    Здесь contacts представляет собой контроллер, а feedback - это метод контроллера contacts, отображающий форму обратной связи и т.д. Мы еще вернемся к этому вопросу в практической части.

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

    2. Практика Для начала создадим следующую структуру файлов и папок:


    Забегая вперед, скажу, что в папке core будут храниться базовые классы Model, View и Controller.
    Их потомки будут храниться в директориях controllers, models и views. Файл index.php это точка в хода в приложение. Файл bootstrap.php инициирует загрузку приложения, подключая все необходимые модули и пр.

    Будем идти последовательно; откроем файл index.php и наполним его следующим кодом:
    ini_set("display_errors", 1); require_once "application/bootstrap.php";
    Тут вопросов возникнуть не должно.

    Следом, сразу же перейдем к фалу bootstrap.php :
    require_once "core/model.php"; require_once "core/view.php"; require_once "core/controller.php"; require_once "core/route.php"; Route::start(); // запускаем маршрутизатор
    Первые три строки будут подключать пока что несуществующие файлы ядра. Последние строки подключают файл с классом маршрутизатора и запускают его на выполнение вызовом статического метода start.

    2.1. Реализация маршрутизатора URL Пока что отклонимся от реализации паттерна MVC и займемся мрашрутизацией. Первый шаг, который нам нужно сделать, записать следующий код в .htaccess :
    RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule .* index.php [L]
    Этот код перенаправит обработку всех страниц на index.php , что нам и нужно. Помните в первой части мы говорили о Front Controller?!

    Маршрутизацию мы поместим в отдельный файл route.php в директорию core. В этом файле опишем класс Route, который будет запускать методы контроллеров, которые в свою очередь будут генерировать вид страниц.

    Содержимое файла route.php

    class Route { static function start() { // контроллер и действие по умолчанию $controller_name = "Main"; $action_name = "index"; $routes = explode("/", $_SERVER["REQUEST_URI"]); // получаем имя контроллера if (!empty($routes)) { $controller_name = $routes; } // получаем имя экшена if (!empty($routes)) { $action_name = $routes; } // добавляем префиксы $model_name = "Model_".$controller_name; $controller_name = "Controller_".$controller_name; $action_name = "action_".$action_name; // подцепляем файл с классом модели (файла модели может и не быть) $model_file = strtolower($model_name).".php"; $model_path = "application/models/".$model_file; if(file_exists($model_path)) { include "application/models/".$model_file; } // подцепляем файл с классом контроллера $controller_file = strtolower($controller_name).".php"; $controller_path = "application/controllers/".$controller_file; if(file_exists($controller_path)) { include "application/controllers/".$controller_file; } else { /* правильно было бы кинуть здесь исключение, но для упрощения сразу сделаем редирект на страницу 404 */ Route::ErrorPage404(); } // создаем контроллер $controller = new $controller_name; $action = $action_name; if(method_exists($controller, $action)) { // вызываем действие контроллера $controller->$action(); } else { // здесь также разумнее было бы кинуть исключение Route::ErrorPage404(); } } function ErrorPage404() { $host = "http://".$_SERVER["HTTP_HOST"]."/"; header("HTTP/1.1 404 Not Found"); header("Status: 404 Not Found"); header("Location:".$host."404"); } }


    Замечу, что в классе реализована очень упрощенная логика (несмотря на объемный код) и возможно даже имеет проблемы безопасности. Это было сделано намерено, т.к. написание полноценного класса маршрутизации заслуживает как минимум отдельной статьи. Рассмотрим основные моменты…

    В элементе глобального массива $_SERVER["REQUEST_URI"] содержится полный адрес по которому обратился пользователь.
    Например: example.ru/contacts/feedback

    С помощью функции explode производится разделение адреса на составлющие. В результате мы получаем имя контроллера, для приведенного примера, это контроллер contacts и имя действия, в нашем случае - feedback .

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

    Таким образом, при переходе, к примеру, по адресу:
    example.com/portfolio
    или
    example.com/portfolio/index
    роутер выполнит следующие действия:

  • подключит файл model_portfolio.php из папки models, содержащий класс Model_Portfolio;
  • подключит файл controller_portfolio.php из папки controllers, содержащий класс Controller_Portfolio;
  • создаст экземпляр класса Controller_Portfolio и вызовет действие по умолчанию - action_index, описанное в нем.
  • Если пользователь попытается обратиться по адресу несуществующего контроллера, к примеру:
    example.com/ufo
    то его перебросит на страницу «404»:
    example.com/404
    То же самое произойдет если пользователь обратится к действию, которое не описано в контроллере.2.2. Возвращаемся к реализации MVC Перейдем в папку core и добавим к файлу route.php еще три файла: model.php, view.php и controller.php


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

    Содержимое файла model.php
    class Model { public function get_data() { } }
    Класс модели содержит единственный пустой метод выборки данных, который будет перекрываться в классах потомках. Когда мы будем создавать классы потомки все станет понятней.

    Содержимое файла view.php
    class View { //public $template_view; // здесь можно указать общий вид по умолчанию. function generate($content_view, $template_view, $data = null) { /* if(is_array($data)) { // преобразуем элементы массива в переменные extract($data); } */ include "application/views/".$template_view; } }
    Не трудно догадаться, что метод generate предназначен для формирования вида. В него передаются следующие параметры:

  • $content_file - виды отображающие контент страниц;
  • $template_file - общий для всех страниц шаблон;
  • $data - массив, содержащий элементы контента страницы. Обычно заполняется в модели.
  • Функцией include динамически подключается общий шаблон (вид), внутри которого будет встраиваться вид
    для отображения контента конкретной страницы.

    В нашем случае общий шаблон будет содержать header, menu, sidebar и footer, а контент страниц будет содержаться в отдельном виде. Опять же это сделано для упрощения.

    Содержимое файла controller.php
    class Controller { public $model; public $view; function __construct() { $this->view = new View(); } function action_index() { } }
    Метод action_index - это действие, вызываемое по умолчанию, его мы перекроем при реализации классов потомков.

    2.3. Реализация классов потомков Model и Controller, создание View"s Теперь начинается самое интересное! Наш сайт-визитка будет состоять из следущих страниц:
  • Главная
  • Услуги
  • Портфолио
  • Контакты
  • А также - страница «404»
  • Для каждой из страниц имеется свой контроллер из папки controllers и вид из папки views. Некоторые страницы могут использовать модель или модели из папки models.


    На предыдущем рисунке отдельно выделен файл template_view.php - это шаблон, содержащий общую для всех страниц разметку. В простейшем случае он мог бы выглядеть так:
    Главная
    Для придания сайту презентабельного вида сверстаем CSS шаблон и интегририруем его в наш сайт путем изменения структуры HTML-разметки и подключения CSS и JavaScript файлов:

    В конце статьи, в разделе «Результат», приводится ссылка на GitHub-репозиторий с проектом, в котором проделаны действия по интеграции простенького шаблона.

    2.3.1. Создадаем главную страницу Начнем с контроллера controller_main.php , вот его код:
    class Controller_Main extends Controller { function action_index() { $this->view->generate("main_view.php", "template_view.php"); } }
    В метод generate экземпляра класса View передаются имена файлов общего шаблона и вида c контентом страницы.
    Помимо индексного действия в контроллере конечно же могут содержаться и другие действия.

    Файл с общим видом мы рассмотрели ранее. Рассмотрим файл контента main_view.php :
    Добро пожаловать!

    ОЛОЛОША TEAM - команда первоклассных специалистов в области разработки веб-сайтов с многолетним опытом коллекционирования мексиканских масок, бронзовых и каменных статуй из Индии и Цейлона, барельефов и изваяний, созданных мастерами Экваториальной Африки пять-шесть веков назад...


    Здесь содержиться простая разметка без каких либо PHP-вызовов.
    Для отображения главной странички можно воспользоваться одним из следующих адресов:

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

    2.3.2. Создадаем страницу «Портфолио» В нашем случае, страница «Портфолио» - это единственная страница использующая модель.
    Модель обычно включает методы выборки данных, например:
  • методы нативных библиотек pgsql или mysql;
  • методы библиотек, реализующих абстракицю данных. Например, методы библиотеки PEAR MDB2;
  • методы ORM;
  • методы для работы с NoSQL;
  • и др.
  • Для простоты, здесь мы не будем использовать SQL-запросы или ORM-операторы. Вместо этого мы сэмулируем реальные данные и сразу возвратим массив результатов.
    Файл модели model_portfolio.php поместим в папку models. Вот его содержимое:
    class Model_Portfolio extends Model { public function get_data() { return array(array("Year" => "2012", "Site" => "http://DunkelBeer.ru", "Description" => "Промо-сайт темного пива Dunkel от немецкого производителя Löwenbraü выпускаемого в России пивоваренной компанией "CАН ИнБев"."), array("Year" => "2012", "Site" => "http://ZopoMobile.ru", "Description" => "Русскоязычный каталог китайских телефонов компании Zopo на базе Android OS и аксессуаров к ним."), // todo); } }

    Класс контроллера модели содержится в файле controller_portfolio.php , вот его код:
    class Controller_Portfolio extends Controller { function __construct() { $this->model = new Model_Portfolio(); $this->view = new View(); } function action_index() { $data = $this->model->get_data(); $this->view->generate("portfolio_view.php", "template_view.php", $data); } }
    В переменную data записывается массив, возвращаемый методом get_data , который мы рассматривали ранее.
    Далее эта переменная передается в качестве параметра метода generate , в который также передаются: имя файла с общим шаблон и имя файла, содержащего вид c контентом страницы.

    Вид содержащий контент страницы находится в файле portfolio_view.php .
    Портфолио

    Все проекты в следующей таблице являются вымышленными, поэтому даже не пытайтесь перейти по приведенным ссылкам.
    ГодПроектОписание


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

    2.3.3. Создаем остальные страницы Остальные страницы создаются аналогично. Их код досутпен в репозитории на GitHub, ссылка на который приводится в конце статьи, в разделе «Результат».3. Результат А вот что получилось в итоге:

    Скриншот получившегося сайта-визитки



    Ссылка на GitHub: https://github.com/vitalyswipe/tinymvc/zipball/v0.1

    А вот в этой версии я набросал следующие классы (и соответствующие им виды):

    • Controller_Login в котором генерируется вид с формой для ввода логина и пароля, после заполнения которой производится процедура аутентификации и в случае успеха пользователь перенаправляется в админку.
    • Contorller_Admin с индексным действием, в котором проверяется был ли пользователь ранее авторизован на сайте как администратор (если был, то отображается вид админки) и действием logout для разлогинивания.
    Аутентификация и авторизация - это другая тема, поэтому здесь она не рассматривается, а лишь приводится ссылка указанная выше, чтобы было от чего оттолкнуться.4. Заключение Шаблон MVC используется в качестве архитектурной основы во многих фреймворках и CMS, которые создавались для того, чтобы иметь возможность разрабатывать качественно более сложные решения за более короткий срок. Это стало возможным благодаря повышению уровня абстракции, поскольку есть предел сложности конструкций, которыми может оперировать человеческий мозг.

    Но, использование веб-фреймворков, типа Yii или Kohana, состоящих из нескольких сотен файлов, при разработке простых веб-приложений (например, сайтов-визиткок) не всегда целесообразно. Теперь мы умеем создавать красивую MVC модель, чтобы не перемешивать Php, Html, CSS и JavaScript код в одном файле.

    Данная статья является скорее отправной точкой для изучения CMF, чем примером чего-то истинно правильного, что можно взять за основу своего веб-приложения. Возможно она даже вдохновила Вас и вы уже подумываете написать свой микрофреймворк или CMS, основанные на MVC. Но, прежде чем изобретать очередной велосипед с «блекджеком и шлюхами», еще раз подумайте, может ваши усилия разумнее направить на развитие и в помощь сообществу уже существующего проекта?!

    P.S.: Статья была переписана с учетом некоторых замечаний, оставленных в комментариях. Критика оказалась очень полезной. Судя по отклику: комментариям, обращениям в личку и количеству юзеров добавивших пост в избранное затея написать этот пост оказалось не такой уж плохой. К сожалению, не возможно учесть все пожелания и написать больше и подробнее по причине нехватки времени… но возможно это сделают те таинственные личности, кто минусовал первоначальный вариант. Удачи в проектах!

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

    Теги: Добавить метки

    По всему интернет-миру разбросаны миллионы веб-приложений. Есть совсем простые, есть такие, что сам «архитектор матрицы ногу сломит». Но их объединяет одно — MVC .

    Самый популярный архитектурный паттерн в мире среди веб-приложений — модель-представление-контроллер (Model View Controller или просто MVC). Впервые, он был использован ещё в конце 70-х двадцатого века, в приложениях на языке Smalltalk . А затем, его приютили программисты Java и расшарили для всего мира и всех языков программирования. PHP не стал исключением. Сегодня, только малая часть программистов, коллекционирующих раритетный PHP-код может себе позволить не смотреть в сторону MVC.

    Таким популярным он стал неспроста. Он просто рождён для создания гибких и масштабируемых приложений, которые легко сопровождать и достраивать.
    Цель нашего тьюториала — показать на простом примере, как работает паттерн MVC.

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

    Примечания:

    • Мы предполагаем, что у вас есть базовые знания PHP.
    Паттерн MVC

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

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

    Схематично потоки данных в этой модели можно представить так:

    Вход в реальность

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

    Мы не будем сейчас рассматривать архитектуру всей социальной сети. Мы возьмём только маленькую подзадачку, представим всю её серьёзность и применим к ней паттерн MVC.

    Как только мы начинаем его использовать, то сразу задумываемся — а как бы нам расположить скрипты нашего решения так, что бы всё было под рукой? Для этого, разместим каждый из трёх разделов нашей MVC-системы по отдельным папкам и, таким образом, получим простую структуру каталогов, в которой легко найти то, что нам нужно. Кроме того, эти три папки поместим в каталог lib, и вынесем его выше корневого веб-каталога www:

    /lib --/controller ---- FrendCnt.php --/model ---- Frend.php ---- FrendList.php --/view ---- frendlist.php ---- frendone.php /www -- index.php -- .htaccess

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

    Контроллер

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

    В нашем простом примере, контроллер будет сконцентрирован в одном классе FrendCnt . Подробнее его опишем позже.А сейчас немного о точке входа в веб-приложение — это, конечно, будет файл index.php . В нём, мы определим точку отсчёта для подключения наших скриптов. Создадим экземпляр контроллера, и вызовем у него метод, который начнёт обрабатывать HTTP-запрос и определит что делать дальше.

    Листинг №1 (файл index.php):

    $baseDir = dirname(__FILE__) . "/.."; include_once($baseDir . "/lib/controller/FriendCnt.php"); $controller = new FriendCnt(); $controller->invoke();

    Теперь о контроллере. У нас — это класс FriendCnt . Вы уже заметили, что экземпляр этого класса создаётся в index.php . Он имеет только один метод invoke() , который вызывается сразу после создания экземпляра. В конструкторе контроллера, создаётся объект на основе класса модели — FrendList (список друзей) для оперирования с данными.

    В функции invoke() , на основе пришедшего HTTP-запроса, принимается решение: какие данные потребуются от модели. Затем происходит вызов метода извлекающего данные. Далее происходит подключение шаблонов для отображения, которым передаются данные из контроллера. Обратите внимание, что контроллер ничего не знает о базе данных или о том, как страница генерится.

    Листинг №2 (файл контроллера FriendCnt.php):

    Require_once($baseDir . "/lib/model/FriendList.php"); class FriendCnt { public $oFriendList; public function __construct() { $this->oFriendList = new FriendList(); } public function invoke() { global $baseDir; $oFriendList = $this->oFriendList; if(isset($_GET["key"])) { $oFriendList->setKey($_GET["key"]); $oFriend = $oFriendList->fetch(); include $baseDir . "/lib/view/friendone.php"; }else { $aFriend = $oFriendList->fetch(); include $baseDir . "/lib/view/friendlist.php"; } } }

    Модель и сущности

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

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

    У нас к модели относятся два скрипта, в каждом из которых определён свой класс. Центральный класс FriendList и класс-сущность Friend . В центральном классе, происходит манипуляция с данными: получение данных от контроллера и их обработка. Класс-сущность служит контейнером для переноса данных между моделью и представлением, а также определяет их формат. При хорошей реализации паттерна MVC, классы сущности не должны упоминаться в контроллере, и они не должны содержать какую-либо бизнес-логику. Их цель - только хранение данных.
    В классе FriendList , работающем со списком друзей, мы создали функцию, которая моделирует взаимодействие этого класса с базой данных. Метод getFriendList() возвращает массив из объектов, созданных на основе класса Friend . Для обеспечения удобства работы с данными, также была создана функция, индексирующая массив объектов. Контроллеру оказались доступны только два метода: setKey() — устанавливает поле ключа, по которому возвращаются детальные данные о друге; fetch() — возвращает или конкретный объект или весь список друзей.

    Листинг №3 (файл модели FriendList.php):

    Require_once($baseDir . "/lib/model/Friend.php"); class FriendList { private $oneKey; private function getFriendList() { return array(new Friend("Александр", "1985", "[email protected]"), new Friend("Юрий", "1987", "[email protected]"), new Friend("Алексей", "1989", "[email protected]"),); } private function getIndexedList() { $list = array(); foreach($this->getFriendList() as $val) { $list[$val->getKey()] = $val; } return $list; } public function setKey($key) { $this->oneKey = $key; } public function fetch() { $aFriend = $this->getIndexedList(); return ($this->oneKey) ? $aFriend[$this->oneKey] : $aFriend; } }

    В зависимости от реализации объектов Сущности, данные о ней, могут быть оформлены в виде XML-документа или JSON-объекта.

    Листинг №4 (файл сущности Friend.php):

    Class Friend { private $key; private $name; private $yearOfBirth; private $email; public function __construct($name, $yearOfBirth, $email) { $this->key = md5($name . $yearOfBirth . $email); $this->name = $name; $this->yearOfBirth = $yearOfBirth; $this->email = $email; } public function getKey() { return $this->key; } public function getName() { return $this->name; } public function getYearOfBirth() { return $this->yearOfBirth; } public function getEmail() { return $this->email; } }

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

    Теперь нам нужно представить данные в наилучшем свете для пользователя.

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

    Главное здесь показать, что представление отделено от контроллера и модели. При этом контроллер занимается передачей данных от модели к представлению.

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

    Листинг №5 (файл для вывода списка друзей friendlist.php):

    Мои друзья

    Имя Год рождения