Подборка полезных ссылок по сабжу. Определились «зачем?», перейдем к «как?»

Добро пожаловать во вторую статью о 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 для Веб.

Перевод статьи «The MVC Pattern and PHP. Part 2 » был подготовлен дружной командой проекта Сайтостроение от А до Я.

Давайте поговорим о интересных полезностях, которые рано или поздно пригодятся любому web-developer`у.

Начнем с Роутинга (eng. “Routing” – маршрутизация). Сразу же появляется вопрос, зачем же нужна на сайте маршрутизация, если и раньше все хорошо работало и устраивало? Все просто. Если вы хотите получить более гибкую систему и уменьшить время на настройку/перенастройку сайта, то маршрутизация вам необходима. К тому же централизованное управление сайтом упростит работу с кодом.

Когда то я сам пользовался обычной switch-case маршрутизацией и думал, что это удобно (на самом деле даже такая маршрутизация куда лучше, чем ее отсутствие).

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

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

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

Определились «зачем?», перейдем к «как?».

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

  • RewriteEngine On
  • RewriteCond %{REQUEST_FILENAME} !-f
  • RewriteCond %{REQUEST_FILENAME} !-d
  • RewriteRule . * index. php [L]

Этот код перенаправит обработку всех страниц на index. php, что даст нам возможность упростить управление контентом. Для увеличения понимания советую обратить внимание на статью DarkKemper`a

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

  • packageName1. ClassName/MethodName
  • packageName2. ClassName/MethodName
  • packageName3. ClassName/MethodName
  • Все довольно просто: в файле-маршрутизаторе вы можете указывать настройки как для отдельных URL, так и для каких-либо статичных частей страниц. В примере я указал модули для шапки сайта, так как она у меня не меняется. Замечу, что кроме модулей, вы можете указывать настройки, какие-либо передаваемые параметры, правила… В общем все, что угодно.

    Последний и самый важный шаг – пишем класс Router, который и будет заведовать всей маршрутизацией.

    Для начала нам нужно разобрать xml-файл:

    • private static function parse($configPath) {
    • return simplexml_load_file($configPath);

    Метод parse() получает путь к вашему конфигу (можно использовать не только для маршрутизации) и возвращает SimpleXML object.

    Теперь вы можете обращаться к каким либо настройкам следующим образом: $xml->header->modules …

    • public static function GetContent($configPath) {
    • $content = "";
    • $router = self::parse($configPath);
    • foreach($router as $page) {
    • if(preg_match(
    • "#^" . $page->uri . "$#"
    • , str_replace($_SERVER["QUERY_STRING"], "", $_SERVER["REQUEST_URI"])
    • , $uriParts
    • if(! empty($page->modules->module)) {
    • foreach($page->modules->module as $modul) {
    • $pageModules = $modul;
    • //Modul::Load() занимается загрузкой пакетов из ...
    • $content . = Modul::Load($modul, $uriParts?: $uriParts);
    • //в своем конфиге я создал группу "page404", которая обрабатывается, если для данной страницы не заданы модули
    • if(empty($pageModules)) {
    • foreach($router->page404->modules->module as $modul) {
    • $pageModules = $modul;
    • $content . = Modul::Load($modul);
    • return $content;

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

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

    Ну, вот и все. Ничего сложного тут нет.

    Выводы можно сделать следующие:

    • + Система очень простая,
    • + Легко и быстро можно изменить страницы и разделы до неузнаваемости,
    • + Благодаря регулярным выражениям можно легко связать модули (мы можем передавать в них параметры).
    • - Если ваш файл-маршрутизатор большой, то без кэширования лучше не работать.

    Последнее обновление: 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 подразумевает одну точку входа – 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 . Сегодня мы создадим роутинг . Поехали!

    Для начала разберемся, как будет работать наш роутинг . У нас будет единая точка входа - index.php . Туда будут отправляться все запросы. URL будет такого вида:

    http://site.ru/controller/method/param

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

    Думаю, как это будет работать, вы поняли. Теперь откроем файл .htaccess и пропишем следующее:

    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-l
    RewriteRule ^(.+)$ index.php?url=$1

    Разберемся, что мы здесь написали. Сначала включаем движок перезаписи, потом задаем 3 условия: если это не физическая директория, файл или ссылка, то берем весь URL и отправляем на файл index.php (нашу единую точку входа), передав GET параметр url со значением нашего URL .

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

    $url = $_GET["url"];
    echo $url;
    ?>

    Теперь вы увидите ваш url . Давайте создадим контроллер index.php в папке controllers .

    class Index {

    echo "Мы в контроллере INDEX";
    }
    }
    ?>

    Теперь в нашем главном файле index.php подключим его

    $url = $_GET["url"];
    echo $url;
    require_once "controllers/".$url.".php";
    $controller = new $url;
    ?>

    Теперь мы увидим надпись, которую выводит конструктор нашего класса. Создадим еще один контроллер help.php в папке controllers .

    class Help {
    public function __construct() {

    }
    }
    ?>

    Теперь если в адресной строке после названия сайта(домена) ввести "/help ", вы увидите, что сработал контроллер Help .

    Итак, думаю, вы поняли, как это работает. Теперь, чтобы не затягивать, я приведу сразу готовый код главного файла index.php , а потом поясню его.

    $url = $_GET["url"];
    $url = rtrim($url, "/");
    $url = explode("/", $url);
    require "controllers/".$url.".php";
    $controller = new $url;
    if(isset($url)) {
    $controller->$url($url);
    }
    else {
    if(isset($url)) {
    $controller->$url();
    }
    }
    ?>

    Вот такой вот получился файл index.php . Сначала мы получаем наш url адрес и записываем его в переменную $url . Дальше наш нужно удалить последний слэш, иначе у нас будет ошибка. Для этого мы воспользовались функцией rtrim , куда первым параметром передаем, что мы хотим удалить, а вторым - где. После, используя функцию explode мы разбиваем нашу строку на массив по слэшу. Теперь первым параметром у нас будет название контроллера, который мы и подключаем строчкой ниже и создаем его объект. Теперь проверяем, есть ли параметры. Если да, то вызываем переданный метод и передаем параметр, а если нет, то просто вызываем наш метод.

    Давайте теперь в нашем контроллере help.php создадим метод other .

    class Help {
    public function __construct() {
    echo "Мы в контроллере HELP";
    }

    Public function other() {

    }
    }
    ?>

    Теперь напишем в адресной строке следующее: ваш_домен.ru/help/other

    И у нас вызовится метод other контроллера help .

    Теперь изменим наш метод other

    class Help {
    public function __construct() {
    echo "Мы в контроллере HELP";
    }

    Public function other($arg = false) {
    echo "Мы в методе other контроллера Help";
    echo "Параметры: ".$arg;
    }
    }
    ?>

    Теперь в адресной строке браузера напишем так: ваш_домен.ru/help/other/10 В метод передастся 10 , и он это выведит.

    Итак, вот мы уже и сделали наш роутер рабочим. Конечно, это только начало, но оно уже положено. Дальше будет только интересней. Спасибо за внимание!