Персональный блог Игоря Антонова aka "spider_net"

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


Рубрика: Программирование -> Журнал Хакер -> Статьи
Метки: | | |
Просмотров: 29530
Пишем собственный MVC фреймворк на PHP

CodeIgniter, Yii, Kohana - чертовски продвинутые и популярные фреймворки для разработки web-приложений различного масштаба и уровня сложности. Все они с оригинальными киллер-фичами, однако, в одном они схожи - в основе лежит шаблон проектирования Model View Controller. Этот паттерн прочно закрепился среди разработчиков web-приложений и наверно поэтому каждый новый фреймворк базируется именно на нем. Сегодня мы решили взглянуть на MVC своим строгим глазом и на наглядном примере показать с чего начинается строительство собственного фреймворка.

Зачем писать еще один фреймворк

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

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

“Зачем разбираться с тем, что и так прекрасно работает? Нужно сосредотачиваться непосредственно на своих задачах!” - скажешь ты. Да, на 50% ты прав, но где гарантия, что новый проект не упрется в ограничения любимого инструмента? Что делать если это произойдет? Менять фреймворк и переписывать кучу кода или самостоятельно модифицировать только нужную его часть? Второй вариант более рационален, но его не удастся провернуть, если до этого вся работа с фреймворком сводилась к использованию, лишь документированных возможностей.

Все будет на PHP

Заявляю сразу, все примеры кода будут представлены на PHP. В последнее время на этот язык падает куча гневных отзывов, но мы с тобой не будем идти на поводу крикунов и воспользуемся именно PHP. CodeIgniter, Yii, Kohana написаны на этом “плохом” языке и пользуются колоссальной популярностью, успешно справляясь с возлагаемыми на них задачами. К тому же, PHP уже повзрослел, окреп и избавился от многих детских болезней. Если ты не испытываешь рвотного рефлекса к этому языку, то welcome!

Разбираемся с теорией MVC

Перед тем как ринуться в бой, быстренько ознакомимся с теорией паттерна Model View Controller. Это будет основой нашего фреймворка и без ее понимания двигаться дальше мы не сможем. Точнее сможем, но в итоге придем к тому, отчего ушли. Мы просто изобретем велосипед, на котором можно ездить, но почему он работает именно так, мы не знаем.

Начнем с небольшого экскурса в историю. Многие ошибочно полагают, что паттерн проектирования MVC появился не так давно и изначально стал применяться в области web-разработки. На самом деле это заблуждение молодых разработчиков. Более старшее поколение знает, что концепция MVC была выдвинута и детально описана еще в 1979 году. О том, что такое web тогда никто и не знал, потому что слова web просто не было. Тим Бернес Ли расскажет о нем только через десять лет.

Трюгве Реенскауг (слева)

Своему рождению MVC обязан Трюгве Реенскаугом. В то время он работал в компании Xerox PARC над языком программирования Small Talk. Не трудно догадаться, что свою первую практическую реализацию шаблон MVC увидел именно для языка SmallTalk.

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

  • Активная модель. Главным выделяющимся элементом этой реализации стала идея полного разделение ролей модели, контроллера и представления. Все эти сущности не должны быть связаны между собой. Изменение модели никак не должно отразиться на контроллере или представлении и наоборот.
  • Пассивная модель. В пассивной модели, сущность “модель” не должна иметь способов взаимодействия с контроллером и представлением. По факту, под моделью здесь подразумевается структура данных. За всеми ее изменениями должен следить контроллер, который впоследствии должен принимать решение о перерисовки представления.
  • Причины использования MVC

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

    Модель - сущность, отвечающая за предоставление доступа к данным, алгоритмы обработки и т.д. Если сказать проще, то именно в модель нужно помещать всю бизнес-логику приложения. Исходя из этого, сразу делаем вывод, что модель не должна ничего знать о других звеньях архитектуры MVC - контроллере и представлении. Среди web-разработчиков бытует заблуждение, что модель предназначена сугубо для операций получения/записи данных в базу. Алгоритмы обработки они выносят в контроллер. Это в корне неверно и по факту, это убивает на нет всю идею MVC. Чуть позже мы вернемся к этому.

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

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

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

    Архитектура шаблона MVC

    С определениями разобрались, теперь переходим к причинам, побуждающим нас использоваться MVC в своих проектах. Пожалуй, главной причиной использования модели стала необходимость разделения кода и представления или принято еще говорить: “отделение бизнес логики приложения от представления”. Действительно, проблема разделения кода и отображения встает перед каждым разработчиком. Если изначально об этом не задуматься, то через некоторое время поддерживать такой код станет просто нереально.

    Не будет далеко ходить, и сразу посмотрим на web-приложения. Что в них чаще всего меняется? Конечно же, это функционал и дизайн. Новые тренды в области дизайна, юзабилити появляются часто, и чтобы приложение оставалось популярным необходимо поддерживать его внешний вид в актуальном для своего времени состоянии. Сам понимаешь, что добиться этого, если код приложения перемешен с кодом отображения (html/css) крайне проблематично. Помимо боли это непременно отразится на времени. Чем больше миксованного кода, тем дольше его придется перелопачивать.

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

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

    Разрабатываем собственный фреймворк

    Теоретическая часть позади, и самое время взяться и написать немного кода. Однако перед этим предлагаю определиться с задачами и функциями будущего нашего проекта. Для начала давай определимся с тем, что вообще из себя представляет типичный фреймворк. В первую очередь - это удобный каркас для нового приложения, созданный по канонам модели MVC. Другой важный компонент, абсолютно любого фреймворка - роутинг. Под роутингом подразумевается модуль, отвечающий за разбор запросов, получаемых от клиента. Ну и третьим компонентом можно назвать дополнительные библиотеки. В таких фреймворках как CodeIgniter, Kohana, Yii множество дополнительных модулей с функциями, которые могут пригодиться при разработке WEB-проекта.

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

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

    application
    - controllers
    - core
    - models
    - views
    load.php
    .htaccess
    index.php

    В директориях “controllers”, “models”, “views” будут храниться пользовательские варианты контроллеров, моделей и представлений. Каталог core будет содержать все системные файлы. Тут будут располагаться наши шаблоны контроллеров, моделей и другие вспомогательные модули. Сценарий load.php будет отвечать за подключение всех компонентов и непосредственный запуск приложения.

    Корневая директория содержит файл .htaccess (в нем определим правила для web-сервера) и index.php. Индексный файл будет являться диспетчером нашего приложения. Его единственная задача - принятие запроса (все обращения пользователя будут идти через этот файл) и передача эстафетной палочки загрузчику - load.php.

    Ок, теперь начнем постепенно заполнять наши файлы. Первым делом открывай файл настроек web-сервера (.htaccess) и напиши в нем несколько строчек кода:

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

    Этим кодом мы определили, что все запросы от клиента должны переадресовываться сценарию index.php. Пока он у нас пуст, но сейчас мы исправим ситуацию. Открывай пустышку index.php и напиши в ней:

    <?php
    ini_set('display_errors', 1);
    require_once 'application/load.php';

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

    <?php
    require_once 'core/routing.php';
    require_once 'core/model.php';
    require_once 'core/view.php';
    require_once 'core/controller.php';
    //Запуск роутинга
    Routing::execute();

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

    Листинг 1. Исходный код класса Routing

    <?php
    class Routing
    {
    	static function execute() 
    	{
    		$controllerName = 'Main';	
    		$actionName = 'index';
    		$piecesOfUrl = explode('/', $_SERVER['REQUEST_URI']);
    		
    		if (!empty($piecesOfUrl[1])) 
    		{
    			$controllerName = $piecesOfUrl[1];
    		}
    		if (!empty($piecesOfUrl[2]))
    		{
    			$actionName = $piecesOfUrl[2];
    		}
    		$modelName = 'Model_' . $controllerName;
    		$controllerName = 'Controller_' . $controllerName;
    		$actionName = 'action_' . $actionName;
    		$fileWithModel = strtolower($modelName) . 'php';
    		$fileWithModelPath	= "application/models/" . $fileWithModel;
    		if (file_exists($fileWithModelPath))
    		{
    			include $fileWithModelPath;
    		}
    		$fileWithController = strtolower($controllerName) . '.php';
    		$fileWithControllerPath = "application/controllers/" . $fileWithController;
    		if (file_exists($fileWithControllerPath))
    		{
    			include $fileWithControllerPath;
    		}
    		else
    		{
    			//Здесь нужно добавить обработку ошибки.
    			//Например, перекинуть пользователя на страницу 404
    		}
    		$controller = new $controllerName;
    		$action = $actionName;
    		if (method_exists($controller, $action))
    		{
    			call_user_func(array($controller, $action_name), $piecesOfUrl);
    		}
    		else
    		{
    			//Здесь тоже нужно добавить обработку ошибок
    		}
    	}	
    }

    Простейший роутинг в SublimeText

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

    Теперь разберем код первого листинга. Для начала нам требуется определить контролер и метод, которые будут вызваны по умолчанию. Например, если пользователь хочет зайти на наш ресурс, то не нужно заставлять его писать в адресной строке что-то вроде: http://oursite.com/main/index. В качестве контроллера по умолчанию мы определим Main, а методом у нас будет index. Обрати внимание, все эти значения прописаны жестко в коде. В реальном приложении их лучше вынести в отдельный конфигурационный файл.

    Определив контроллер и метод по умолчанию, мы можем приступать к анализу url, по которому хочет пройти пользователь. Для этого соберем все части url в массив и начнем анализировать. Договоримся, что первым параметром (сразу после слеша) у нас будет имя контроллера, а вторым его метод, который необходимо исполнить. Например, если пользователь ввел в качестве url: http://oursite.com/shop/buy, то мы должны обратиться к контроллеру с именем shop и вызвать его метод buy. Если такой контроллер отсутствует, то нужно генерировать ошибку 404. Для экономии места я не стал приводить код ее отображения, его ты сможешь увидеть в исходниках, прилагаемых к статье.

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

    После успешного подключения необходимых файлов нам требуется сделать еще одну проверку. Файл с контроллером то мы нашли, а где гарантии, что у него есть соответствующий метод? Если его нет, то нужно прерывать дальнейшие действия и генерировать страницу с кодом ошибки 404. Для определения наличия метода в PHP есть функция method_exists. Первым параметром передается класс, а вторым имя метода, наличие которого требуется проверить. Если функция выполняется успешно, то можно вызывать нужный метод контроллера. Для этого применяем функцию call_user_func().

    Все, на этом с разбором роутинга покончено и можно переходить к рассмотрению базовых классов, описывающих заготовки для моделей, контроллеров и представлений. Их код приведен в листингах 2, 3 и 4.

    Листинг 2. Код класса Controller

    <?php
    class Controller {
    	public $model;
    	public $view;
    	function __construct()
    	{
    		$this -> view = new View();
    	}
    	function action_index()
    	{
    	}
    }

    Листинг 3. Код класса Model

    <?php
    class Model
    {
    	public function get()
    	{		
    	}
    }

    Листинг 4. Код класса View

    <?php
    class View
    {
    	function generate($content, $template, $data = null)
    	{
    		include 'application/views/' . template;
    	}
    }

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

    Тестируем фреймворк

    Все! Наш простейший фреймворк можно считать готовым. Да, получилось все максимально просто, но он работает, и в этом ты убедишься через несколько минут. Для тестирования новоиспеченного продукта я подготовил небольшой пример. Для теста я подготовил одну из бесплатных тем оформления и оформил ее в виде представлений. Далее я определил один контроллер и в методе index определил вызов представления:

    $this -> view -> generate('myview', 'template.php');

    Собственный фреймворк в действии

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

    Вместо заключения

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

    Статья опубликована в журнале "Хакер" (http://xakep.ru). Декабрь 2012 г.

    Ссылка на журнал: http://goo.gl/wvKOag

    Комментариев: 6 RSS

    Привет! С днем рождения! А вобще проще взять composer и подключать то что нужно :) Или сразу symfony2 поставить и не париться!

    Спасибо за поздравление!

    Симфония хороша, но мне в свое время было интересно написать простенький фреймворк самостоятельно.

    Спасибо за статью! Игорь, а где я могу скачать исходники демонстрационного проекта?

    2Ghost

    С исходниками пока проблема. Не могу их найти, поэтому сорцы будут доступны позже.

    Ваше хрень + с ошибками.

    $this -> view -> generate(‘myview’, ‘template.php’’); - запускаем и все работает. Ага, хрен там чего работать будет.

    Да вообще, автор, ты это запускал сам то? Я запускал, но с нормальным кодом, а не с этим высером.

    2Вася

    Что конкретно у вас не работает?

    Давайте соблюдать элементарные правила вежливости.

    Оставьте комментарий!
    comments powered by HyperComments