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

Вы можете авторизоваться на сайте всего одним кликом:

2016-08-05
Основы PHP → MVC и ООП
MVC, ООП, Как применять, Использовать, Разработка

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

Совместное использование принципов MVC и ООП

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

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

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

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

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

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

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

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

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

  1. Согласно шаблону проектирования MVC файлы контроллеров, моделей и представлений будут разнесены по трем отдельным каталогам.
  2. Каждый контроллер будет представлен отдельным файлом, в котором будет объявлен класс контроллера, инкапсулирующий логику работы контроллера и предоставляющий методы для обработки HTTP-запроса пользователя.
  3. Каждой странице сайта будет соответствовать отдельный контроллер, отвечающий за ее вывод.
  4. Модели системы также будут реализованы в виде классов.
  5. Представления будут реализованы в виде обычных PHP-файлов с HTML- разметкой с вкраплениями PHP-кода для отображения значения переменных сценария.
  6. HTTP-запросы к системе будут осуществляться через специальную точку входа — файл index.php.

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

Точка входа

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

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

<?php
include_once('inc/C_View.php');
include_once('inc/C_Edit.php');

// Выбор контроллера.
switch ($_GET['c'])
{
   case 'edit':
      $controller = new C_Edit();
      break;
   default:
      $controller = new C_View();
}

// Передача управления нужному контроллеру.
$controller->Request();
?>

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

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

  1. Template() — для подстановки в шаблон набора переменных и вывода его на экран;
  2. IsGet() — для проверки, был ли выполнен GET-запрос;
  3. 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().

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

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

На диаграмме данный цикл можно представить так:

В заключение

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

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

1278
1
Пожалуйста, авторизируйтесь, чтобы скачать архив с файлами урока
Ollo Vein
Знакомая тема из курса от ШП :)
2016-07-21 02:25:51