Применение ограничений маршрутов в атрибутах. Создадаем главную страницу

Последнее обновление: 31.10.2015

В предыдущих главах при обращении к некоторому действию контроллера мы набирали в браузере адрес наподобие следующего http://localhost/Home/Index , где Home являлся именем контроллера без префикса Controller , а Index - именем действия этого контроллера. Если метод Index принимал какой-нибудь параметр, например, типа int: public ActionResult Index(int Id) , то мы могли обратиться к этому методу и передать значение в его параметр с помощью следующей строки: http://localhost/Home/Index/5 . Но мы не говорили еще о том, почему мы должны прописывать маршрут именно так, и как мы собственно можем управлять маршрутами.

Посмотрим, как определен маршрут. Если в MVC 3 для определения маршрута по умолчанию в файле Global.asax.cs создавался специальный метод RegisterRoutes , который определял маршрут по умолчанию и потом вызывался в методе Application_Start:

Using System.Web.Routing; using System.Data.Entity; namespace MvcEmptyApp { public class MvcApplication: System.Web.HttpApplication { protected void Application_Start() { RegisterRoutes(RouteTable.Routes); ............................. } public static void RegisterRoutes(RouteCollection routes) { //Здесь определение маршрутов................................ } } }

То в MVC 4 все начальные настройки конфигурации для файла Global.asax.cs вынесены в классы, расположенные в папке App_Start . И затем эти классы вызываются в файле Global.asax.cs:

Using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; namespace MvcEmptyApp { public class MvcApplication: System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); } } }

Откроем файл RouteConfig.cs , расположенный в папке App_Start, в котором и находится определение маршрута по умолчанию:

Using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; namespace MvcEmptyApp { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapHttpRoute(name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }); routes.MapRoute(name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }); } } }

Цель первой строки routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); отключить обработку запросов для некоторых файлов, например с расширением *.axd (WebResource.axd). Следующие два вызова - routes.MapHttpRoute и routes.MapRoute как раз и задают определение маршрута. Главное отличие состоит в том, что вызов routes.MapHttpRoute устанавливает сопоставления запроса некоторому маршруту для ресурса Web API, а routes.MapRoute устанавливает маршрут для обычного контроллера, которые мы использовали в предыдущих главах.

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

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

Здесь мы сначала задаем имя маршрута с помощью свойства name (в данном случае имя Default ). С помощью параметра url мы задаем шаблон Url , с которым будет сопоставляться данный маршрут.

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

При этом именовать параметры можно как угодно, используя любые алфавитно-цифровые символы. При получении запроса механизм маршрутизации парсит строку URL и помещает значения маршрута в словарь - в объект RouteValueDictionary , доступный через контекст приложения RequestContext . В качестве ключей используются имена параметров URL, а соответствующие сегменты URL в качестве значений. То есть, если строка запроса URL выглядит следующим образом: http://localhost/Home/Index/5 , то у нас образуются следующие пары ключей и значений в словаре RouteValueDictionary:

Параметр

Значение

Следующий параметр - defaults определяет значения по умолчанию для маршрута. И если вдруг в строке запроса мы не указали все параметры и попытались обратиться по адресу http://localhost/ , то система маршрутизации вызовет метод Index контроллера Home, как указано в параметре defaults . Также, если мы не укажем метод контроллера, например, http://localhost/Home/ , также будет вызван метод Index контроллера Home.

Поэтому если мы захотим, к примеру, чтобы у нас по умолчанию клиент обращался не к методу Index контроллера HomeController, а, например, к методу Show контроллера BookController, то мы можем соответственно изменить значения данного параметра:

Defaults: new { controller = "Book", action = "Show", id = UrlParameter.Optional }

Последний параметр объявлен как необязательный id = UrlParameter.Optional , поэтому, если он не указан в строке запроса, он не будет учитываться и передаваться в словарь параметров RouteValueDictionary . Например, запрос http://localhost/Home/Create/3 вызовет метод Create контроллера Home, передав в этот метод в качестве параметра число 3. В то же время запрос http://localhost/Home/Create/ также вызовет метод Create контроллера Home, хотя последний параметр в нем не указан.

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

Public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapHttpRoute(name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }); routes.MapRoute(name: "Default", url: "{controller}/{action}"); } }

При запуске из Visual Studio или при запуске в браузере по адресу http://mysyte.com/ мы получим информацию об ошибке. Ошибка будет состоять в том, что теперь нам полностью надо набирать в строке запроса адрес ресурса. Поэтому следующий адрес http://mysyte.com/Home/Index будет нормально работать (если у вас, конечно, определен контроллер Home с методом Index и соответствующим ему представлением).

И если мы теперь перейдем по адресу http://localhost/Home/ , как мы это делали выше, то получим ошибку, так как у нас указан только одни сегмент. А в определении маршрута у нас указано два сегмента - {controller}/{action} . Если для параметров не определены значения по умолчанию, то строка запроса должна иметь такое же число сегментов, для которых не определены значения по умолчанию.

В то же время если запрос будет состоять из трех сегментов, например, http://localhost/Home/Index/1 , то мы также получим ошибку, потому что число сегментов в запросе больше числа, определенного в шаблоне URL данного маршрута.

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

Вы можеть быть даже слышали о шаблонах проектирования и даже листали эти прекрасные книги:

  • Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидесс «Приемы объектно ориентированного проектирования. Паттерны проектирования»;
  • М. Фаулер «Архитектура корпоративных программных приложений».
А многие, не испугавшись огромных руководств и документаций, пытались изучить какой-либо из современных фреймворков и столкнувшись со сложностью понимания (в силу наличия множества архитектруных концепций хитро увязанных между собой) отложили изучение и применение современных интсрументов в «долгий ящик».

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

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

1. Теория

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

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

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

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

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

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

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

1.1. Front Controller и Page Controller

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

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

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

Первый вариант:

  1. www.example.com/article.php?id=3
  2. www.example.com/user.php?id=4
Здесь каждый сценарий отвечает за выполнение определённой команды.

Второй вариант:

  1. www.example.com/index.php?article=3
  2. www.example.com/index.php?user=4
А здесь все обращения происходят в одном сценарии index.php .

Подход с множеством точек взаимодействия вы можете наблюдать на форумах с движком phpBB. Просмотр форума происходит через сценарий viewforum.php , просмотр топика через viewtopic.php и т.д. Второй подход, с доступом через один физический файл сценария, можно наблюдать в моей любимой CMS MODX, где все обращения проходят черезindex.php .

Эти два подхода совершенно различны. Первый — характерен для шаблона контроллер страниц (Page Controller), а второй подход реализуется паттерном контроллер запросов (Front Controller). Контроллер страниц хорошо применять для сайтов с достаточно простой логикой. В свою очередь, контроллер запросов объединяет все действия по обработке запросов в одном месте, что даёт ему дополнительные возможности, благодаря которым можно реализовать более трудные задачи, чем обычно решаются контроллером страниц. Я не буду вдаваться в подробности реализации контроллера страниц, а скажу лишь, что в практической части будет разработан именно контроллер запросов (некоторое подобие).

1.2. Маршрутизация URL

Маршрутизация URL позволяет настроить приложение на прием запросов с URL, которые не соответствуют реальным файлам приложения, а также использовать ЧПУ , которые семантически значимы для пользователей и предпочтительны для поисковой оптимизации.

К примеру, для обычной страницы, отображающей форму обратной связи, URL мог бы выглядеть так:
http://www.example.com/contacts.php?action=feedback

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

  1. подключит файл model_portfolio.php из папки models, содержащий класс Model_Portfolio;
  2. подключит файл controller_portfolio.php из папки controllers, содержащий класс Controller_Portfolio;
  3. создаст экземпляр класса 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 предназначен для формирования вида. В него передаются следующие параметры:

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

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

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

2.3. Реализация классов потомков Model и Controller, создание View"s

Теперь начинается самое интересное! Наш сайт-визитка будет состоять из следущих страниц:
  1. Главная
  2. Услуги
  3. Портфолио
  4. Контакты
  5. А также — страница «404»
Для каждой из страниц имеется свой контроллер из папки controllers и вид из папки views. Некоторые страницы могут использовать модель или модели из папки models.


На предыдущем рисунке отдельно выделен файл template_view.php — это шаблон, содержащий общую для всех страниц разметку. В простейшем случае он мог бы выглядеть так:
<html lang ="ru" > <head > <meta charset ="utf- 8 "> <title > Главнаяtitle > head > <body > $content_view ; ?> body > html >
Для придания сайту презентабельного вида сверстаем CSS шаблон и интегририруем его в наш сайт путем изменения структуры HTML-разметки и подключения CSS и JavaScript файлов:
<link rel ="stylesheet" type ="text/css" href ="/css/style.css" /> <script src ="/js/jquery-1.6.2.js" type ="text/javascript" > script >

В конце статьи, в разделе «Результат», приводится ссылка на 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 :
<h1 > Добро пожаловать!h1 > <p > <img src ="/images/office-small.jpg" align ="left" > <a href ="/" > ОЛОЛОША TEAMa > - команда первоклассных специалистов в области разработки веб-сайтов с многолетним опытом коллекционирования мексиканских масок, бронзовых и каменных статуй из Индии и Цейлона, барельефов и изваяний, созданных мастерами Экваториальной Африки пять-шесть веков назад... p >
Здесь содержиться простая разметка без каких либо PHP-вызовов.
Для отображения главной странички можно воспользоваться одним из следующих адресов:

  • методы библиотек, реализующих абстракицю данных. Например, методы библиотеки 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 .

    Портфолио

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


    Ссылка на 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 5 появилась поддержка новой методики, известной как маршрутизация с помощью атрибутов , при которой маршруты определяются атрибутами C#, примененными непосредственно к классам контроллеров. В последующих разделах будет показано то, как создавать и конфигурировать маршруты с использованием атрибутов, которые могут свободно смешиваться со стандартными маршрутами на основе соглашений.

    Сравнение маршрутизации на основе соглашений и маршрутизации с помощью атрибутов

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

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

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

    Включение и применение маршрутизации с помощью атрибутов

    Маршрутизация с помощью атрибутов по умолчанию отключена. Включается она посредством расширяющего метода MapMvcAttributeRoutes() , который вызывается на объекте RouteCollection, передаваемом в качестве аргумента статическому методу RegisterRoutes(). В примере ниже приведено содержимое файла RouteConfig.cs, в который был добавлен вызов метода MapMvcAttributeRoutes(), а также упрощены маршруты в приложении, чтобы можно было сосредоточить внимание на использовании атрибутов:

    Using System.Web.Mvc; using System.Web.Routing; using System.Web.Mvc.Routing.Constraints; using UrlsAndRoutes.Infrastructure; namespace UrlsAndRoutes { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.MapMvcAttributeRoutes(); routes.MapRoute(name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, namespaces: new { "UrlsAndRoutes.Controllers" }); } } }

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

    Using System.Web.Mvc; namespace UrlsAndRoutes.Controllers { public class CustomerController: Controller { // ... public ActionResult Index() { ViewBag.Controller = "Customer"; ViewBag.Action = "Index"; return View("ActionName"); } } }

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

    Свойство Name

    Назначает маршруту имя, используемое для генерации исходящих URL из специфичного маршрута.

    Свойство Template

    Определяет шаблон, который будет использоваться для сопоставления с URL, нацеленными на данный метод действия.

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

    В рассматриваемом примере атрибут Route применяется для указания на то, что действие Index контроллера Customer может быть доступно через URL вида /Test. Результат можно видеть на рисунке ниже:

    Когда метод действия декорирован атрибутом Route, он больше не может быть доступен через маршруты на основе соглашений, определенные в файле RouteConfig.cs. В данном примере это означает, что действие Index контроллера Customer не будет доступно через URL вида /Customer/Index. Подобный подход к системе маршрутизации можно увидеть на популярном конструкторе логотипов http://logoservis.ru/ .

    Атрибут Route препятствует нацеливанию маршрутов, основанных на соглашениях, на метод действия, даже если маршрутизация с помощью атрибутов отключена. Позаботьтесь о вызове метода MapMvcAttributeRoutes() в файле RouteConfig.cs, иначе вы создадите недостижимые методы действий.

    Атрибут Route влияет только на методы, к которым он применен, а это значит, что хотя метод действия Index() в контроллере Customer достижим через URL вида /Test, к методу действия List() по-прежнему придется обращаться с использованием URL в форме/Customer/List. Атрибут Route можно применять к одному и тому же методу действия несколько раз, при этом каждый случай использования приводит к созданию нового маршрута.

    Создание маршрутов с переменными сегментов

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

    Using System.Web.Mvc; namespace UrlsAndRoutes.Controllers { public class CustomerController: Controller { // ... public string Create(string user, int id) { return string.Format("Пользователь: {0}, Id: {1}", user, id); } } }

    Здесь был добавлен метод действия по имени Create(), который принимает аргументы string и int. Для простоты из метода возвращается результат типа string, поэтому создавать представление не придется. Маршрут, определенный атрибутом Route, смешивает статический префикс (Users/Add) с переменными сегментов user и id, которые соответствуют аргументам метода.

    Инфраструктура MVC Framework применяет средство привязки моделей, чтобы преобразовать значения переменных сегментов в подходящие типы для вызова метода Create(). Результат перехода на URL вида /Users/Add/Alex/120 показан на рисунке ниже:

    Обратите внимание на то, что каждый экземпляр атрибута Route функционирует независимо, а это означает возможность создания совершенно разных маршрутов для нацеливания на методы действий в контроллере, как показано в таблице ниже:

    Применение ограничений маршрутов в атрибутах

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

    Using System.Web.Mvc; namespace UrlsAndRoutes.Controllers { public class CustomerController: Controller { // ... public string Create(string user, int id) { return string.Format("Пользователь: {0}, Id: {1}", user, id); } public string ChangePass(string user, string password) { return string.Format("Метод действия ChangePass() - Пользователь: {0}, Пароль: {1}", user, password); } } }

    Новый метод действия по имени ChangePass() принимает два аргумента типа string. С помощью атрибута Route действие ассоциируется с тем же самым шаблоном, что и метод Create(): статическим префиксом /Users/Add, за которым следуют две переменные сегментов. Чтобы различать эти действия, к атрибуту Route для метода Create() применяется ограничение:

    Здесь указано имя переменной сегмента (id), двоеточие и затем int. Это сообщает системе маршрутизации о том, что метод действия Create() должен быть ориентирован на запросы, в которых значение, предоставляемое для сегмента id, является допустимым значением типа int. Ограничение int соответствует классу ограничения IntRouteConstraint, и в таблице из предыдущей статьи был приведен набор атрибутов, которые можно использовать для доступа к встроенным ограничениям на основе типов и значений.

    Чтобы увидеть эффект от примененного ограничения, необходимо запустить приложение и запросить URL двух видов - /Users/Add/Alex/120 и /Users/Add/Alex/Пароль12345. Последний сегмент в первом URL представляет собой допустимое значение int и направляется методу Create(). Последний сегмент второго URL не является значением int, поэтому он направляется методу ChangePass(), как показано на рисунке ниже:

    Комбинирование ограничений

    К переменной сегмента можно применять несколько ограничений с целью дальнейшего сужения диапазона значений, которые будут соответствовать маршруту. В следующем примере демонстрируется комбинирование ограничений alpha и length в маршруте для метода ChangePass():

    // ... public string ChangePass(string user, string password) { return string.Format("Метод действия ChangePass() - Пользователь: {0}, Пароль: {1}", user, password); } // ...

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

    Применяя ограничения, соблюдайте осторожность. Маршруты, определяемые с помощью атрибута Route, работают точно таким же образом, как маршруты, определяемые в файле RouteConfig.cs, и для URL, которые не соответствуют ни одному из методов действий, браузеру будет отправляться результат 404 - Not Found (не найдено). Всегда предусматривайте запасной маршрут, который будет давать соответствие независимо от значений, содержащихся в URL.

    Использование префикса маршрута

    С помощью атрибута RoutePrefix можно определить общий префикс, который будет применяться ко всем маршрутам, заданным в контроллере. Это может оказаться удобным при наличии множества методов действий, направление на которые должно осуществляться с использованием одного и того же корня URL. В примере ниже демонстрируется применение атрибута RoutePrefix к классу CustomerController:

    Using System.Web.Mvc; namespace UrlsAndRoutes.Controllers { public class CustomerController: Controller { public ActionResult List() { // ... } public ActionResult Index() { // ... } public string Create(string user, int id) { // ... } public string ChangePass(string user, string password) { // ... } } }

    Атрибут RoutePrefix используется для указания на то, что маршруты, связанные с методом действия, должны снабжаться префиксом Users. Определив такой общий префикс, можно удалить его из атрибутов Route для методов действий Create() и ChangePass(). Инфраструктура MVC Framework будет комбинировать общий префикс с шаблоном URL автоматически при создании маршрутов.

    Обратите внимание на изменение также и шаблона URL для атрибута Route, применяемого к методу действия Index(), следующим образом:

    За счет предварения URL префиксом "~/" мы указываем инфраструктуре MVC Framework на то, что атрибут RoutePrefix не должен применяться к методу действия Index(), поэтому он по-прежнему будет доступен через URL вида /Test.

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

    И так начнем. Паттерн MVC подразумевает одну точку входа – index.php, через это скрипт будут проходить все запросы, через него будет работать вся логика проекта. Для того чтобы реализовать такой подход необходимо настроить сервер, подразумевается, что сайт работает на сервере apache, поэтому нам достаточно создать файл.htaccess, в котором мы укажем правила маршрутизации URL. Помимо определения точки входа, маршрутизация позволяет создавать ЧПУ(человеко-понятные урлы). То есть после правильной настройки, адреса страниц буду выглядеть вот так site.ru/article/new.
    Для начала, давайте составим.htaccess, который перенаправит обработку всех страниц на скрипт index.php. Код выглядит вот так:

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

    Файл.htaccess должен лежать в корневой папке сайта, тут же необходимо создать скрипт index.php, который является точкой входа. Давайте запишем в index.php одну строку, для проверки работы перенаправления:
    echo "test";

    Теперь можно проверять работу перенаправления, введите любой адрес и посмотрите, что получиться: test-mvc.web/sdf/sdf/ или test-mvc.web/sdf/sdf/2342/не важно, на экране в любом случае, должно появиться «Test». Если Вы увидели эту надпись, значит, у нас все получилось.
    Продолжим, давайте для удобства создадим в корне сайта файл config.php, в котором будем задавать различные константы, облегчающие своим существование настройку сайта. Это могут быть различные пути к скриптам, подступы к базе данных и так далее. Сейчас в конфиге давайте зададим следующее:
    // Задаем константы: define ("DS", DIRECTORY_SEPARATOR); // разделитель для путей к файлам $sitePath = realpath(dirname(__FILE__) . DS); define ("SITE_PATH", $sitePath); // путь к корневой папке сайта // для подключения к бд define("DB_USER", "root"); define("DB_PASS", ""); define("DB_HOST", "localhost"); define("DB_NAME", "blog_mvc");

    Для того, чтобы константы и другие данные конфига мы могли использовать во всем проекте, в файле index.php необходимо подключить скрипт config.php.
    Помимо подключения файла с настройками, в index.php нужно создать подключение к базе данных, подключить скрипт с ядром сайта и запустить роутер, в котором будет происходить маршрутизация.
    Теперь по порядку, создание соединения с базой данных будет находиться в index.php для того, чтобы соединение открывалось только один раз. Единожды открыв соединение, мы сможем использовать его во всех контроллерах и моделях, но об этом чуть позже. Сейчас просто создадим соединение с базой. Для работы с бд я решил использовать PDO. Подробнее почитать про PDO можно тут.
    Ядро сайта расположим в папке core и назовем скрипт core.php, тут мы напишем функцию, которая будет сама подключать, необходимы для работы классы. Такая функция очень облегчит и упростит нам работу с контролерами, моделями и тд. Поскольку, забегая вперед скажу, что каждый контролер и каждая модель будут представлять собой отдельный класс.
    Помимо авто подключения классов, добавим в ядро создания хранилища (реестра), в котором будем хранить все необходимые объекты и переменные, которые могут пригодиться в любом месте проекта.
    Роутер тоже подключим в индексном файле, он будет анализировать URL и подключать необходимый контроллер и экшен. Что такое контролер я писал в предыдущей статье, а информацию про экшен я пропустил умышленно, не став нагружать лишней информацией. Так что же такое экшен?
    Контролер это класс, в котором заключены различные методы, при MVC подходе каждый метод будет являться экшеном. То есть экшен(action) – это метод класса, который будет обрабатывать данные и передавать их в представление (в шаблон). Может быть, пока не совсем понятно, но после примера все станет на свои места.
    На данном этапе теории достаточно, давайте перейдем к практике. Приведу код файлов, работу которых, я описывал выше.
    Код скрипта index.php:
    // включим отображение всех ошибок error_reporting (E_ALL); // подключаем конфиг include ("/config.php"); // Соединяемся с БД $dbObject = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASS); // подключаем ядро сайта include (SITE_PATH . DS . "core" . DS . "core.php"); // Загружаем router $router = new Router($registry); // записываем данные в реестр $registry->set ("router", $router); // задаем путь до папки контроллеров. $router->setPath (SITE_PATH . "controllers"); // запускаем маршрутизатор $router->start();

    Скрипт core.php:
    // Загрузка классов "на лету" function __autoload($className) { $filename = strtolower($className) . ".php"; // определяем класс и находим для него путь $expArr = explode("_", $className); if(empty($expArr) OR $expArr == "Base"){ $folder = "classes"; }else{ switch(strtolower($expArr)){ case "controller": $folder = "controllers"; break; case "model": $folder = "models"; break; default: $folder = "classes"; break; } } // путь до класса $file = SITE_PATH . $folder . DS . $filename; // проверяем наличие файла if (file_exists($file) == false) { return false; } // подключаем файл с классом include ($file); } // запускаем реестр (хранилище) $registry = new Registry;
    Класс хранилища Registry.php, будет находиться в папке /classes/
    // Класс хранилища Class Registry { private $vars = array(); // запись данных 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]); } }
    Код файла router.php, который находиться в папке /classes/
    // класс роутера Class Router { private $registry; private $path; private $args = array(); // получаем хранилище function __construct($registry) { $this->registry = $registry; } // задаем путь до папки с контроллерами function setPath($path) { $path = trim($path, "/\\"); $path .= DS; // если путь не существует, сигнализируем об этом if (is_dir($path) == false) { throw new Exception ("Invalid controller path: `" . $path . "`"); } $this->path = $path; } // определение контроллера и экшена из урла private function getController(&$file, &$controller, &$action, &$args) { $route = (empty($_GET["route"])) ? "" : $_GET["route"]; unset($_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 . DS; array_shift($parts); continue; } // Находим файл if (is_file($fullpath . ".php")) { $controller = $part; array_shift($parts); break; } } // если урле не указан контролер, то испольлзуем поумолчанию index if (empty($controller)) { $controller = "index"; } // Получаем экшен $action = array_shift($parts); if (empty($action)) { $action = "index"; } $file = $cmd_path . $controller . ".php"; $args = $parts; } function start() { // Анализируем путь $this->getController($file, $controller, $action, $args); // Проверка существования файла, иначе 404 if (is_readable($file) == false) { die ("404 Not Found"); } // Подключаем файл include ($file); // Создаём экземпляр контроллера $class = "Controller_" . $controller; $controller = new $class($this->registry); // Если экшен не существует - 404 if (is_callable(array($controller, $action)) == false) { die ("404 Not Found"); } // Выполняем экшен $controller->$action(); } }
    Теперь необходимо создать папки для хранения контроллеров, шаблонов и моделей – в корне создадим три папки controllers, views и models. И создадим несколько тестовых файлов /controllers/index.php, /views/index/index.php и /models/model_users.php, а теперь заполним файлы:
    Для контроллера:
    // контролер Class Controller_Index Extends Controller_Base { // шаблон public $layouts = "first_layouts"; // экшен function index() { $model = new Model_Users(); $userInfo = $model->getUser(); $this->template->vars("userInfo", $userInfo); $this->template->view("index"); } }
    Для отображения(/views/index/index.php)
    Test view
    id:
    name: И модель: // модель Class Model_Users{ public function getUser(){ return array("id"=>1, "name"=>"test_name"); } }
    Как вы могли заметить, класс контролера наследуется от родительского класса Controller_Base. Это сделано, для того, чтобы упростить класс контролера. Поскольку нам еще необходимо подключать класс для работы с шаблонами, его подключение вынесено в Controller_Base.
    Приведу его код, он расположен в папке /classes/ и называется controller_base.php:
    // абстрактый класс контроллера Abstract Class Controller_Base { protected $registry; protected $template; protected $layouts; // шаблон public $vars = array(); // в конструкторе подключаем шаблоны function __construct($registry) { $this->registry = $registry; // шаблоны $this->template = new Template($this->layouts, get_class($this)); } abstract function index(); }
    Теперь осталось только разобраться с шаблонами. В абстрактном классе Controller_Base мы вызываем класс Template и передаем ему имя шаблона и имя контроллера.
    Код класса Template, который лежит тут /classes/ и называется template.php
    // класс для подключения шаблонов и передачи данных в отображение Class Template { private $template; private $controller; private $layouts; private $vars = array(); function __construct($layouts, $controllerName) { $this->layouts = $layouts; $arr = explode("_", $controllerName); $this->controller = strtolower($arr); } // установка переменных, для отображения function vars($varname, $value) { if (isset($this->vars[$varname]) == true) { 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 view($name) { $pathLayout = SITE_PATH . "views" . DS . "layouts" . DS . $this->layouts . ".php"; $contentPage = SITE_PATH . "views" . DS . $this->controller . DS . $name . ".php"; if (file_exists($pathLayout) == false) { trigger_error ("Layout `" . $this->layouts . "` does not exist.", E_USER_NOTICE); return false; } if (file_exists($contentPage) == false) { trigger_error ("Template `" . $name . "` does not exist.", E_USER_NOTICE); return false; } foreach ($this->vars as $key => $value) { $$key = $value; } include ($pathLayout); } }
    Если вы внимательно прочитали код, то наверняка поняли, что для отображения на страницах у нас используется шаблон first_layouts и вьюха(отображение) index.php – ее код я приводил чуть выше. Все что нам осталось, это создать файл шаблона first_layouts. Расположим его в папке /views/layouts/first_layouts.php
    Шаблон будет содержать вот такой код:

    header

    footer


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

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

    Маршрутизация и URL-адреса

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

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

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

    array("model" => "AboutModel", "view" => "AboutView", "controller" => "AboutController"), "portfolio" => array("model" => "PortfolioModel", "view" => "PortfolioView", "controller" => "PortfolioController")); foreach($data as $key => $components){ if ($page == $key) { $model = $components["model"]; $view = $components["view"]; $controller = $components["controller"]; break; } } if (isset($model)) { $m = new $model(); $c = new $controller($model); $v = new $view($model); echo $v->output(); } }

    URL -адреса будут выглядеть следующим образом:

    example.com/index.php?page=about

    example.com/index.php?page=portfolio

    MVC PHP пример загружает конкретный набор «Модели », «Представления » и «Контроллера » для запрашиваемой страницы. Если параметр URL -страницы — это “about ”, то будет отображаться страница About . Если “portfolio ”, то будет отображаться страница Portfolio .

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

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

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

    Можно создать разделы или страницы, создавая взаимосвязи между «Моделью », «Представлением » и «Контроллером ». Например:

    output(); }

    Наш новый URL -адрес будет выглядеть так:

    example.com/index.php?controller=controllername;model=modelname&view=viewname&action=actionname

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

    Это может быть переменная URL -адреса события, отдельная переменная или данные, собранные контроллером. Не забывайте, «Контроллер » никогда не должен загружать данные или непосредственно передавать их в «Представление »; он должен взаимодействовать только с «Моделью » и вводимыми пользователем данными.

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

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

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

    DRY (Don’t Repeat Yourself) и шаблоны

    Для меня одним из главных аргументов в пользу использования MVC является возможность сделать всю систему как можно более организованной. Любой разработчик согласится, что самое худшее для любого приложения – это повторение одного и того же кода. Принцип поддержания простоты кода и многократного использования компонентов получила название философия DRY — Don’t Repeat Yourself (не повторяйтесь ).

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

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

    Корректная реализация DRY — это когда при изменении одного элемента системы несвязанные с ним элементы не меняются. Поэтому DRY так важен при разработке с использованием MVC паттерна.

    Шаблоны

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

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

    Таким образом, данные будут готовы для отображения с помощью структуры блочного кода, или с помощью echo , print . Главное помнить, что ваши данные должны быть готовы для вывода через шаблон. Если у вас в шаблоне данные обрабатываются по-другому, скорее всего, у вас неверно задана архитектура MVC .

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

    tstring = "The string has been loaded through the template."; $this->template = "tpl/template.php"; } } controller = $controller; $this->model = $model; } public function output(){ $data = "

    " . $this->model->tstring ."

    "; require_once($this->model->template); } } The Template name

    Шаблон PHP MVC передается через «Модель », которая может назначать шаблон в зависимости от того, для чего предназначено каждое конкретное «Представление ». Этот метод шаблонов позволяет создавать эффективные и расширяемые системы, предоставляя возможность разделения back-end и front-end разработки. В этом и заключается основная цель MVC .

    Заключение

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

    ГодПроектОписание
    " .$row ["Year" ]."" .$row ["Site" ]."" .$row ["Description" ]."