Совместное использование принципов MVC и ООП. Локальное и удалённое внедрение файлов: эксплуатация уязвимости и обход фильтров

Совместное использование принципов MVC и ООП. Локальное и удалённое внедрение файлов: эксплуатация уязвимости и обход фильтров

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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


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

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

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



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

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

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

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

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

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

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

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

    Представьте сайт, состоящий из двух страниц. На главной старице отображается некоторый текст:

    Вторая страница представляет собой его редактор:


    На ней мы можем изменять содержание главной страницы и сохранять изменения.

    В верхней части страниц расположена "шапка" сайта, которая состоит из двух элементов. Первый из них неизменен и отражает название сайта. Второй содержит название текущей страницы: либо "Чтение", либо "Редактирование".

    Ниже шапки расположено меню, которое одинаково для всех страниц сайта и содержит ссылки на эти страницы.

    Также неизменным остается "подвал" страницы, в котором отображается дополнительная информация о сайте, например, адрес, телефон, информация о создателе страницы.

    Центральную часть станицы "Чтение" занимает текст, который мы можем задавать на странице "Редактирование", используя обычную HTML-форму с элементом textarea

    Исходные положения

    Нам предстоит объединить два подхода - MVC и ООП. Для этого при разработке архитектуры системы будем руководствоваться следующими правилами.

  • Согласно шаблону проектирования MVC файлы контроллеров, моделей и представлений будут разнесены по трем отдельным каталогам.
  • Каждый контроллер будет представлен отдельным файлом, в котором будет объявлен класс контроллера, инкапсулирующий логику работы контроллера и предоставляющий методы для обработки HTTP-запроса пользователя.
  • Каждой странице сайта будет соответствовать отдельный контроллер, отвечающий за ее вывод.
  • Модели системы также будут реализованы в виде классов.
  • Представления будут реализованы в виде обычных PHP-файлов с HTML- разметкой с вкраплениями PHP-кода для отображения значения переменных сценария.
  • HTTP-запросы к системе будут осуществляться через специальную точку входа - файл index.php.
  • Хоть эта тема уже и обсуждалась, повторим, что следует четко представлять различия между назначением классов контроллеров и классов модели. Классы контроллеров служат для того, чтобы обеспечить обработку HTTP-запроса пользователя, т. е. получить параметры входного запроса, и подготовить результирующую HTML-страницу. При выполнении этой работы контроллеры задействуют классы модели, которые представляют отдельные функциональные блоки: работу с базой данных, работу с файлами, работу с аккаунтами пользователей и т. п. Каждый такой логически связный блок представляется отдельным классом. Например, в вашей системе может быть класс для работы с базой данных, который будет хранить дескриптор текущего соединения с БД и предоставлять набор методов для выполнения запросов к БД и получения различных наборов данных.

    Точка входа

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

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

    Давайте разберем работу данного сценария построчно.

    Include_once("inc/C_View.php"); include_once("inc/C_Edit.php");

    Первые две строчки подключают файлы контроллеров для страниц "Чтение" и "Редактирование".

    Switch ($_GET["c"])

    Здесь происходит анализ GET-параметра с именем c :

    Case "edit": $controller = new C_Edit(); break;

    Если данный параметр хранит строку "edit ", тогда переменной $controller присваивается объект класса C_Edit , описание которого находится в файле inc/C_Edit.php , подключенном в самом начале сценария.

    В случае если параметр с не задан или имеет значение, отличное от "edit " , выполняется другая ветка оператора switch :

    Default: $controller = new C_View();

    Здесь переменной $controller присваивается экземпляр класса C_View .

    Наконец, последняя строка вызывает метод Request() у созданного объекта:

    $controller->Request();

    Обратите внимание, здесь мы можем увидеть типичный пример полиморфизма . Переменная $controller может хранить как экземпляр класса C_Edit , так и экземпляр класса C_View . Поэтому мы не можем заранее знать, метод Request() какого именно класса будет вызван в сценарии. Важно лишь то, что для корректной работы сценария оба класса должны иметь данный метод.

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

    Иерархия контроллеров

    Давайте приступим к проектированию системы и для начала определим, какие контроллеры будут в ней присутствовать. Из файла index.php видно, что в системе есть как минимум два контроллера, представленные в виде классов C_Edit и C_View . Задача класса C_Edit - организовать вывод страницы "Редактирование", класса C_View - страницы "Чтение". Обработка входных параметров и вывод страницы инкапсулируется внутри метода Request() , который должен присутствовать как в классе C_View , так и в C_Edit . Это может натолкнуть на мысль о создании общего родительского класса для этих двух контроллеров. И действительно, реализация С_View будет во многом совпадать с C_Edit , поэтому выделение родительского класса базового контроллера - это полезное действие.

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

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

    Очевидно, каждый контроллер будет содержать метод Request() , отвечающий за обработку запроса. Следовательно, его объявление также можно вынести в класс C_Base и сделать его абстрактным для того, чтобы классы-наследники были обязаны реализовать данный метод.

    Возможно, имеет смысл выделить еще какие-то общие свойства или методы для всех контроллеров сайта. Подумайте над этим самостоятельно. Подчеркнем, C_Base - это базовый класс, который объединяет в себе все общие элементы каждого контроллера конкретного сайта.

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

    Не будем томить и продемонстрируем иерархию классов контроллеров, которую мы вам предлагаем принять за образец:

    Данная диаграмма используется в описательном языке UML и называется диаграммой классов. Каждый прямоугольник на ней соответствует отдельному классу. В верхней части прямоугольника жирным выделено название класса. Стрелками показаны отношения наследования. Курсивом выделены свойства классов, прямым шрифтом - его методы. Знак – перед свойствами или методами говорит о том, что свойство или метод объявлены с модификатором private , знак # соответствует модификатору protected ,+ - модификатору public .

    Класс Controller

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

  • Template() - для подстановки в шаблон набора переменных и вывода его на экран;
  • IsGet() - для проверки, был ли выполнен GET-запрос;
  • IsPost() - для проверки, был ли выполнен POST-запрос.
  • В классе Controller присутствует объявление уже известного нам метода Request() .
    Давайте посмотрим на его реализацию:

    Public function Request() { $this->OnInput(); $this->OnOutput(); }

    В методе Request() мы делим обработку запроса на две части: обработку входных данных (метод OnInput() ) и формирование результирующей страницы (метод OnOutput() ). Такое разделение довольно удобно, и мы предлагаем его использовать и в будущих проектах. Методы OnInput() и OnOutput() должны быть переопределены в дочерних классах.

    Класс C_Base

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

    Кроме того, в классе C_Base нужно переопределить методы OnInput() и OnOutput() и вынести в них действия, общие для всех контроллеров сайта. Контроллеры, которые будут наследоваться от класса C_Base , будут переопределять эти методы, но они смогут вызвать родительские методы с помощью ключевого слова parent .

    Мы предлагаем следующую реализацию методов OnInput() и OnOutput() в классе C_Base .

    Protected function OnInput() { $this->title = "Главная страница"; $this->content = ""; } protected function OnOutput() { $vars = array("title" => $this->title,"content" => $this->content); $page = $this->Template("main", $vars); echo $page; }

    В методе OnInput() мы можем инициализировать переменные шаблона значениями по умолчанию. В методе OnOutput() мы сначала формируем массив переменных шаблона, а затем используем метод Template() , который определен в классе Controller и отвечает за подстановку переменных в шаблон. Его реализация остается за вами. По нашей задумке метод Template() принимает два параметра: первый из них - название подключаемого шаблона, второй параметр - это массив переменных, которые должны быть подставлены в шаблон. В завершение сформированная страница выводится на экран. Как вы понимаете, это последняя операция, которую должен выполнить сценарий. Поэтому функция OnOutput() класса C_Base должна вызываться в самом конце нашего PHP-сценария.

    Контроллеры C_View и C_Edit

    C_Viev и C_Edit - контроллеры конкретных страниц сайта, они переопределяют методы OnInput() и OnOutput() , но также передают управление контроллеру C_Base .

    Давайте рассмотрим пример реализации этих методов для класса C_View .

    Protected function OnInput() { parent::OnInput(); $this->title = $this->title . " :: Чтение"; $this->text = $this->getText(); } protected function OnOutput() { $vars = array("text" => $this->text); $this->content = $this->Template("theme/v_view.php", $vars); parent::OnOutput(); }

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

    Метод OnOutput() начинается с формирования массива, который передается в функцию формирования шаблона Template() . Результат ее выполнения сохраняется в поле content . После чего вызывается родительский метод OnOutput() . Вернитесь к тексту метода OnOutput() класса C_Base и обратите внимание на то, что в нем как раз-таки используется значение поля content , который мы формируем в текущем методе OnOutput() класса С_View .

    Если вы взглянете на код еще раз, то заметите, что вызов родительских методов OnInput() и OnOuput() как бы обрамляет цикл обработки запроса в классе C_View .

    Цикл обработки запроса

    Теперь давайте посмотрим на общий вид обработки запроса, который мы получили. Выполнение сценария начинается с файла index.php. Здесь выбирается нужный контроллер, и ему передается управление вызовом метода Request() .

    Этот метод запускает две фазы обработки запроса:

  • Обращение к модели (OnInput());
  • Генерация HTML (OnOutput()).
  • Первая фаза должна отработать в такой последовательности:
  • Базовый контроллер (C_Base);
  • Конкретный контроллер (C_View или C_Edit).
  • Далее начинается фаза генерации HTML, она должна пройти в обратной последовательности:
  • Конкретный контроллер (C_View или C_Edit);
  • Базовый контроллер (C_Base).
  • На диаграмме данный цикл можно представить так:


    В заключение

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

    Программирование еще никому не давалось без усилий. С каждым днем практики, с каждым новым PHP-сценарием вы набираете собственный бесценный опыт, который в будущем позволит вам быстро, грамотно и эффективно разрабатывать интернет-системы любой сложности. Залог такой возможности - это профессиональный подход к веб-разработке.

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

    Введение

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

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

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

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

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

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

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

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

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

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

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



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