MVC и его реализация на PHP

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

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

Концепция​ ​MVC​ ​впервые​ ​была​ ​описана​ ​аж​ ​в​ ​1978​ ​году,​ ​но​ ​ее​ ​окончательная​ ​версия была​ ​опубликована​ ​только​ ​через​ ​10​ ​лет,​ ​в​ ​1988. Важно​ ​понимать,​ ​что​ ​MVC​ ​применяется​ ​исключительно​ ​в​ ​объектно​ ​ориентированных языках​ ​программирования​ ​и​ ​основной​ ​целью​ ​данной​ ​концепции​ ​является​ ​разделение бизнес​ ​логики​ ​от​ ​визуального​ ​представления​ ​данных.

Бизнес​ ​логика​ ​в​ ​самом​ ​первом​ ​приближении​ ​-​ ​это​ ​некоторые​ ​формулы,​ ​расчеты, ветвления​ ​в​ ​программе​ ​и​ ​тому​ ​подобное.​ ​Всю​ ​эту​ ​бизнес​ ​логику​ ​было​ ​принято определить​ ​в​ ​так​ ​ называемые​ ​модели​ ​(Model).​ ​Весь​ ​контент,​ ​который​ ​должен​ ​быть виден​ ​пользователю​ ​определили​ ​в​ ​представления​ ​(Views).​ ​Прием​ ​данных​ ​от пользователя,​ ​связь​ ​модели​ ​и​ ​контроллера​ ​и​ ​отправку​ ​результата​ ​обратно пользователю​ ​взял​ ​на​ ​себя​ ​ответственность​ ​контроллер​ ​(Controller).​ ​По​ ​этим​ ​трем важным​ ​компонентам​ ​данной​ ​концепции​ ​и​ ​возникла​ ​аббревиатура​ ​MVC​ ​-​ ​Model​ ​View Controller​ ​(Модель​ ​Представление​ ​Контроллер).

images-000_800x600.jpg

Алгоритм​ ​работы​ ​MVC​ ​на​ ​примере​ ​новостного​ ​портала

  • Пользователь​ ​отправляет​ ​запрос​ ​на​ ​получение​ ​всех​ ​новостей
  • Контроллер​ ​принимает​ ​запрос​ ​и​ ​при​ ​помощи​ ​модели​ ​получает​ ​весь​ ​список новостей​ ​и​ ​направляет​ ​их​ ​в​ ​представление
  • Представление​ ​отображает​ ​новости​ ​в​ ​определенном​ ​виде

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

Структура​ ​проекта​ ​в​ ​файловой​ ​системе​ ​выглядит​ ​следующим​ ​образом:

images-001_800x600.jpg

Существует​ ​4​ ​директории:

  1. Controllers​ ​-​ ​директория​ ​в​ ​которой​ ​хранится​ ​весь​ ​список​ ​контроллеров.​ ​В​ ​рамках данного​ ​проекта​ ​будет​ ​использоваться​ ​единственный​ ​контроллер​ ​“home”. Принято​ ​в​ ​рамках​ ​хорошего​ ​тона​ ​в​ ​качестве​ ​суффикса​ ​в​ ​наименовании контроллера​ ​указывать​ ​текст​ ​“Controller”
  2. Models​ ​-​ ​директория​ ​для​ ​хранения​ ​моделей.​ ​Пока​ ​здесь​ ​хранится​ ​всего​ ​одна модель​ ​“News”,​ ​которая​ ​содержит​ ​всю​ ​бизнес​ ​логику​ ​для​ ​работы​ ​с​ ​новостями приложения.
  3. Views​ ​-​ ​содержит​ ​список​ ​всех​ ​возможных​ ​представлений.​ ​Представления​ ​в данном​ ​контексте​ ​-​ ​это​ ​обычные​ ​файлы​ ​формата​ ​“php”,​ ​в​ ​которых​ ​содержится HTML​ ​код​ ​с​ ​PHP​ ​ вставками.​ ​В​ ​рамках​ ​проекта​ ​имеется​ ​всего​ ​два представления:​ ​“index.php”​ ​ ​-​ ​главная​ ​страница​ ​портала​ ​и​ ​“news.php”​ ​-​ ​новости портала
  4. System​ ​-​ ​директория​ ​в​ ​которой​ ​хранятся​ ​все​ ​системный​ ​классы​ ​и​ ​файлы,​ ​а именно:
    1. App​ ​-​ ​главный​ ​класс​ ​приложения,​ ​реализующий​ ​в​ ​себе​ ​функционал простейшего​ ​роутинга
    2. View​ ​-​ ​класс​ ​отвечающий​ ​за​ ​рендер​ ​представлений
    3. autoload​ ​-​ ​файл​ ​в​ ​котором​ ​реализована​ ​автозагрузка​ ​классов​ ​по стандарту​ ​PSR-0

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

Конфигурация​ ​ виртуального​ ​хоста​ ​для​ ​веб-сервера​ ​nginx​ ​должна​ ​выглядеть следующим​ ​образом:

location / {
# Redirect everything that isn't a real file to index.php
    try_files $uri $uri/ /index.php$is_args$args;
}

Директива​ ​try_files​ ​проверяет​ ​существование​ ​файлов​ ​в​ ​заданном​ ​порядке​ ​и​ ​использует для​ ​обработки​ ​запроса​ ​первый​ ​найденный​ ​файл.​ ​С​ ​помощью​ ​слэша​ ​в​ ​конце​ ​имени можно​ ​проверить​ ​существование​ ​каталога.​ ​В​ ​случае,​ ​если​ ​ни​ ​один​ ​файл​ ​не​ ​найден,​ ​то делается​ ​внутреннее​ ​перенаправление​ ​на​ ​uri,​ ​заданный​ ​последним​ ​параметром. То​ ​есть​ ​сначала​ ​производится​ ​попытка​ ​найти​ ​запрошенный​ ​файл,​ ​если​ ​файл​ ​не​ ​был найден,​ ​то​ ​проверяется​ ​существование​ ​директории.​ ​Но​ ​если​ ​запрошенная​ ​директория не​ ​существует,​ ​то​ ​выборка​ ​выпадает​ ​на​ ​последнюю​ ​запись,​ ​т.е.​ ​на​ ​нашу​ ​единую​ ​точку входа​ ​“index.php”

В​ ​Apache​ ​для​ ​того,​ ​чтобы​ ​добиться​ ​перенаправления​ ​всех​ ​запросов​ ​в​ ​единую​ ​точку входа,​ ​используется​ ​модуль​ ​“mod_rewrite”.​ ​В​ ​случае​ ​веб-сервера​ ​Apache​ ​можно​ ​не править​ ​напрямую​ ​виртуальный​ ​хост​ ​относящийся​ ​к​ ​проекту,​ ​а​ ​воспользоваться файлом​ ​“.htaccess”,​ ​который​ ​будет​ ​выглядеть​ ​приблизительно​ ​следующим​ ​образом

RewriteEngine On
RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^(.*)$ index.php

При​ ​помощи​ ​директивы​ ​“RewriteEngine”​ ​и​ ​ее​ ​опции​ ​“On”​ ​производится​ ​включение модуля​ ​перезаписи​ ​URL. Используя​ ​директиву​ ​“RewriteCond”​ ​мы​ ​задаем​ ​некоторые​ ​условия,​ ​которые​ ​проверяют на​ ​истинность​ ​URL,​ ​и​ ​если​ ​условия​ ​истинны,​ ​то​ ​применяется​ ​директива​ ​перезаписи “RewriteRule”. В​ ​данном​ ​случае​ ​перезаписи​ ​будут​ ​подвергаться​ ​все​ ​URL,​ ​которые​ ​не​ ​ведут​ ​на реальные​ ​файлы​ ​или​ ​каталоги.​ ​Перезапись​ ​осуществляется​ ​на​ ​единую​ ​точку​ ​входа “index.php”.

Теперь​ ​можно​ ​приступить​ ​к​ ​разбору​ ​единой​ ​точки​ ​входа​ ​в​ ​приложение

1 <?php
2 // Включаем режим строгой типизации
3 declare(strict_types=1);
4 // Подключаем файл реализующий автозагрузку
5 require_once __DIR__ . '/System/autoload.php';
6 // Запускаем приложение
7 System\App::run();

В​ ​третьей​ ​строчке​ ​мы​ ​используем​ ​конструкцию​ ​“declare”​ ​которая​ ​устанавливает поддержку​ ​строгой​ ​типизации​ ​в​ ​рамках​ ​всего​ ​проекта. В​ ​шестой​ ​строчке​ ​подключается​ ​файл,​ ​в​ ​котором​ ​описана​ ​логика​ ​автозагрузки​ ​по стандарту​ ​PSR-0,​ ​выглядит​ ​это​ ​следующим​ ​образом

1 <?php
2 /**
3 * Example Implementation of PSR-0
4 *
5 * @param $className
6 */
7 function autoload($className)
8 {
9      $className = ltrim($className, '\\');
10     $fileName = '';
11     $namespace = '';
12     if ($lastNsPos = strrpos($className, '\\')) {
13         $namespace = substr($className, 0, $lastNsPos);
14         $className = substr($className, $lastNsPos + 1);
15         $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) .
16         DIRECTORY_SEPARATOR;
17 }
18 $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
19 require $fileName;
20 }
21 spl_autoload_register('autoload');

Подробнее​ ​о​ ​автозагрузке​ ​по​ ​стандарту​ ​PSR-0​ ​читайте​ ​статью,​ ​представленную​ ​в дополнительных​ ​материалах​ ​к​ конспекту.

На​ ​9​ ​строке​ ​точке​ ​входа​ ​index.php​ ​производится​ ​непосредственно​ ​запуск​ ​самого приложения.​ ​Давайте​ ​посмотрим,​ ​что​ ​делает​ ​статический​ ​метод​ ​“run”​ ​класса приложения​ ​“App”

1 <?php
2
3 namespace System;
4
5 /**
6 * Главный класс приложения
7 *
8 * @author farza
9 * @return void
10 */
11 class App
12 {
13     /**
14     * Запуск приложения
15     *
16     * @author farZa
17     * @throws \ErrorException
18     */
19     public static function run()
20     {
21         // Получаем URL запроса
22         $path = $_SERVER['REQUEST_URI'];
23         // Разбиваем URL на части
24         $pathParts = explode('/', $path);
25         // Получаем имя контроллера
26         $controller = $pathParts[1];
27         // Получаем имя действия
28         $action = $pathParts[2];
29         // Формируем пространство имен для контроллера
30         $controller = 'Controllers\\' . $controller . 'Controller';
31         // Формируем наименование действия
32         $action = 'action' . ucfirst($action);
33
34         // Если класса не существует, выбрасывем исключение
35         if (!class_exists($controller)) {
36             throw new \ErrorException('Controller does not exist');
37         }
38
39         // Создаем экземпляр класса контроллера
40         $objController = new $controller;
41
42         // Если действия у контроллера не существует, выбрасываем исключение
43         if (!method_exists($objController, $action)) {
44             throw new \ErrorException('action does not exist');
45         }
46
47         // Вызываем действие контроллера
48         $objController->$action();
49     }
50 }

Вся​ ​реализация​ ​маршрутизации​ ​приложения​ ​реализована​ ​в​ ​методе​ ​run.

Маршрутизатор​ ​приложения​ ​-​ ​это​ ​компонент​ ​который​ ​распознает​ ​входящие​ ​запросы. Маршрутизатор​ ​анализирует​ ​HTTP​ ​методы​ ​и​ ​URL​ ​полученного​ ​запроса,​ ​и
сопоставляет​ ​их​ ​с​ ​подходящим​ ​методом​ ​контроллера​ ​для​ ​передачи​ ​ему​ ​управления. Если​ ​компонент​ ​не​ ​сможет​ ​найти​ ​запрошенное​ ​пользователем​ ​действие​ ​контроллера,
приложение​ ​выдаст​ ​ошибку.

В​ ​нашем​ ​микро​ ​фреймворке​ ​реализован​ ​простейший​ ​маршрутизатор,​ ​который компактно​ ​умещается​ ​в​ ​методе​ ​run.​ ​Давайте​ ​разберемся​ ​как​ ​он​ ​работает​ ​на​ ​простом примере.​ ​Предположим​ ​пользователь​ ​запрашивает​ ​адрес​ ​вида​ ​“/home/main”.​ ​По​ ​этому адресу​ ​он​ ​предполагает​ ​увидеть​ ​главную​ ​страницу​ ​портала

http://example.com/home/main

Наш​ ​мини​ ​-​ ​компонент​ ​роутинга​ ​способен​ ​разобрать​ ​данный​ ​url.​ ​Он​ ​настроен​ ​таким образом,​ ​что​ ​в​ ​качестве​ ​контроллера​ ​(класс)​ ​будет​ ​понимать​ ​первую​ ​часть​ ​url​ ​запроса, т.е.​ ​“home”,​ ​а​ ​вторую​ ​часть​ ​url​ ​запроса,​ ​т.е.​ ​“main”​ ​будет​ ​понимать​ ​как​ ​действие​ ​(метод) контроллера.

Еще​ ​раз,​ ​маршрутизатор​ ​данного​ ​приложения​ ​настроен​ ​так,​ ​что​ ​в​ ​качестве контроллера​ ​он​ ​понимает​ ​первую​ ​часть​ ​url​,​ ​а​ ​в​ ​качестве действия​ ​контроллера​ ​понимает​ ​вторую​ ​часть​ ​этого​ ​же​ ​url​.

Таким​ ​образом​ ​будет​ ​создан​ ​экземпляр​ ​класса​ ​контроллера​ ​“home”​ ​и​ ​будет​ ​вызвано его​ ​действие​ ​“main”.

Теперь​ ​давайте​ ​разберем​ ​код​ ​маршрутизатора,​ ​приведенный​ ​выше.

  • На​ ​22​ ​строке​ ​мы​ ​получаем​ ​url​ ​запроса,​ ​то​ ​есть​ ​“/home/main”
  • На​ ​24​ ​строке​ ​разбиваем​ ​url​ ​на​ ​части
  • С​ ​26-28​ ​строках​ ​создаем​ ​переменные​ ​и​ ​помещаем​ ​туда​ ​наименование контроллера​ ​и​ ​наименование​ ​действия
  • В​ ​рамках​ ​нашего​ ​приложения​ ​мы​ ​для​ ​себя​ ​приняли​ ​некоторую​ ​спецификацию повествующую​ ​о​ ​том,​ ​что​ ​все​ ​контроллеры​ ​должны​ ​находиться​ ​в​ ​пространстве имен​ ​“Control ers”,​ ​поэтому​ ​на​ ​30​ ​строке​ ​мы​ ​конкатенируем​ ​префикс​ ​к​ ​названию контроллера,​ ​создавая​ ​тем​ ​самым​ ​наименование​ ​класса​ ​контроллера. Правилом​ ​хорошего​ ​тона​ ​является​ ​добавление​ ​суффикса​ ​к​ ​наименованию класса​ ​контроллера.​ ​Чтобы​ ​удовлетворить​ ​данное​ ​правило,​ ​в​ ​30​ ​строке производится​ ​конкатенация​ ​суффикса​ ​к​ ​наименованию​ ​контроллера.
  • Действием​ ​контроллера​ ​называют​ ​метод​ ​класса​ ​контроллера.​ ​Как​ ​правило, наименования​ ​действий​ ​начинается​ ​с​ ​ключевого​ ​слова​ ​“action”.​ ​Конечно​ ​же можно​ ​не​ ​соблюдать​ ​данное​ ​правило,​ ​но​ ​в​ ​данном​ ​конкретном​ ​примере,​ ​на​ ​32 строке​ ​кода,​ ​конкатенация​ ​префикса​ ​“action”​ ​с​ ​наименованием​ ​действия​ ​была произведена.
  • На​ ​35-37​ ​строках​ ​кода​ ​производится​ ​проверка​ ​существования​ ​класса контроллера.​ ​Если​ ​класс​ ​не​ ​был​ ​обнаружен,​ ​то​ ​выбрасывается​ ​исключение
  • На​ ​40​ ​строке​ ​создается​ ​экземпляр​ ​класса​ ​контроллера,​ ​а​ ​затем​ ​на​ ​43-45 строках​ ​производится​ ​проверка​ ​наличия​ ​запрашиваемого​ ​действия​ ​у созданного​ ​экземпляра​ ​класса​ ​контроллера.​ ​В​ ​случае​ ​отсутствия​ ​действия, выбрасывается​ ​исключение.
  • На​ ​последней​ ​48​ ​строке​ ​производится​ ​вызов​ ​действия​ ​контроллера.

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

Предположим​ ​что​ ​пользователь​ ​отправил​ ​запрос​ ​вида​ ​“/home/main”.​ ​Маршрутизатор сразу​ ​понимает,​ ​что​ ​необходимо​ ​создать​ ​экземпляр​ ​класса​ ​“homeController”, находящийся​ ​в​ ​пространстве​ ​имен​ ​“Controllers”​ ​и​ ​вызвать​ ​у​ ​него​ ​действие​ ​“actionMain”. В​ ​данном​ ​примере​ ​такой​ ​контроллер​ ​уже​ ​был​ ​создан​ ​и​ ​действие​ ​“main”​ ​тоже​ ​уже существует

1 <?php
2
3 namespace Controllers;
4
5 use System\View;
6 use Models\News;
7
8 /**
9 * Главный контроллер приложения
10 *
11 * @author farza
12 */
13 class homeController
14 {
15     /**
16     * Действие отвечающее за отображение главной
17     * страницы портала
18     *
19     * @author farZa
20     */
21     public function actionMain()
22     {
23         // Рендер главной страницы портала
24         View::render('index');
25     }
26 }

Единственное,​ ​что​ ​делает​ ​действие​ ​“main”​ ​контроллера​ ​“home”​ ​-​ ​отображает представление.​ ​В​ ​данном​ ​проекте​ ​было​ ​принято,​ ​что​ ​все​ ​представления​ ​будут храниться​ ​в​ ​директории​ ​“Views”​ ​и​ ​представлять​ ​из​ ​себя​ ​обыкновенные​ ​файлы формата​ ​“.php”,​ ​в​ ​которых​ ​будет​ ​присутствовать​ ​HTML​ ​разметка​ ​с​ ​возможными вставками​ ​PHP​ ​кода.​ ​Никаких​ ​шаблонизаторов​ ​в​ ​рамках​ ​системы​ ​не​ ​предусмотрено.

В​ ​двадцать​ ​четвертой​ ​строке​ ​мы​ ​говорим,​ ​что​ ​необходимо​ ​отобразить​ ​представление с​ ​наименованием​ ​“index”.​ ​За​ ​отображение​ ​представлений​ ​отвечает​ ​метод​ ​“render” класса​ ​“View”.​​ Давайте​ ​рассмотрим​ ​его​ ​логическую​ ​структуру

1 <?php
2
3 namespace System;
4
5 /**
6 * Главный класс реализующий функционал отображения
7 * представлений
8 *
9 * @author farza
10 */
11 class View
12 {
13     /**
14     * @author farZa
15     * @param string $path
16     * @param array $data
17     * @throws \ErrorException
18     */
19     public static function render(string $path, array $data = [])
20     {
21         // Получаем путь, где лежат все представления
22         $fullPath = __DIR__ . '/../Views/' . $path . '.php';
23
24         // Если представление не было найдено, выбрасываем исключение
25         if (!file_exists($fullPath)) {
26             throw new \ErrorException('view cannot be found');
27         }
28
29         // Если данные были переданы, то из элементов массива
30         // создаются переменные, которые будут доступны в представлении
31         if (!empty($data)) {
32             foreach ($data as $key => $value) {
33                 $$key = $value;
34             }
35         }
36
37         // Отображаем представление
38         include($fullPath);
39     }
40 }

Метод​ ​“render”​ ​принимает​ ​2​ ​параметра:

  1. path​ ​-​ ​наименование​ ​представления
  2. data​ ​-​ ​необязательный​ ​параметр,​ ​представляющий​ ​из​ ​себя​ ​массив​ ​данных, элементы​ ​которого​ ​станут​ ​доступны​ ​в​ ​отображаемом​ ​представлении

Разбор

  • В​ ​22​ ​строчке​ ​кода​ ​сохраняется​ ​путь​ ​до​ ​представления,​ ​а​ ​затем​ ​в​ ​25-27​ ​строчках производится​ ​проверка​ ​наличия​ ​представления​ ​в​ ​директории​ ​“Views”.​ ​В​ ​случае отсутствия​ ​представления,​ ​выбрасывается​ ​исключение.
  • Если​ ​в​ ​представление​ ​данные​ ​были​ ​переданы,​ ​то​ ​из​ ​элементов​ ​массива создаются​ ​переменные,​ ​которые​ ​станут​ ​доступны​ ​в​ ​подключаемом представлении,​ ​вся​ ​эта​ ​логика​ ​описана​ ​в​ ​31-35​ ​строчках
  • В​ ​38​ ​строке​ ​производится​ ​непосредственно​ ​подключение​ ​файла представления.

В​ ​рамках​ ​проекта​ ​представление​ ​уже​ ​было​ ​создано​ ​и​ ​находится​ ​в​ ​директории​ ​“Views”:

1 <h2>Главная страница</h2>
2
3 <div class="main-page">
4     <p>Добро пожаловать на главную страницу нашего первого
5     <strong>MVC</strong> фреймворка</p>
6
7     <a href="/home/news">Новости портала</a>
8 </div>

Данное​ ​представление​ ​содержит​ ​примитивную​ ​HTML​ ​разметку​ ​без​ ​каких​ ​либо​ ​вставок PHP​ ​кода.

Если​ ​мы​ ​перейдем​ ​по​ ​адресу​ ​“/home/main”,​ ​то​ ​увидим​ ​главную​ ​страницу​ ​приложения

images-011_800x600.jpg

Единственное,​ ​что​ ​осталось​ ​за​ ​бортом​ ​данной​ ​лекции,​ ​и​ ​что​ ​просто​ ​необходимо рассмотреть​ ​в​ ​рамках​ ​обсуждаемой​ ​концепции​ ​MVC​ ​и​ ​нашего​ ​микро-фреймворка​ ​-​ ​это “Модель”.

Давайте​ ​создадим​ ​страницу​ ​новостей.​ ​В​ ​качестве​ ​задачи​ ​поставим​ ​следующее условие:​ ​Необходимо​ ​получить​ ​список​ ​новостей​ ​из​ ​базы​ ​данных​ ​и​ ​отобразить​ ​их​ ​на странице.

В​ ​условии​ ​был​ ​упомянут​ ​факт​ ​о​ ​работе​ ​с​ ​базой​ ​данных,​ ​а​ ​значит​ ​будет​ ​некоторая бизнес​ ​логика,​ ​отвечающая​ ​за​ ​данную​ ​работу,​ ​а,​ ​если​ ​быть​ ​точнее,​ ​за​ ​получение списка​ ​новостей​ ​из​ ​базы​ ​данных.​ ​Эту​ ​бизнес​ ​логику​ ​в​ ​рамках​ ​концепции​ ​MVC​ ​мы обязаны​ ​положить​ ​в​ ​модель.​ ​В​ ​рамках​ ​данного​ ​проекта​ ​уже​ ​создана​ ​таблица​ ​“news”​ ​в базе​ ​данных​ ​и​ ​модель​ ​“News”​ ​расположенная​ ​в​ ​пространстве​ ​имен​ ​“Models”.

images-012_800x600.jpg

images-013_800x600.jpg

1 <?php
2
3 namespace Models;
4
5 /**
6 * Модель "Новости" содержащая бизнес логику
7 * относящуюся к сущности "Новости"
8 *
9 * @author farza
10 */
11 class News
12 {
13     /**
14     * Метод, отвечающий за получение всех данных
15     * о новостях портала
16     *
17     * @author farZa
18     * @return array
19     */
20     public function displayAll()
21     {
22         // Строка соединения с базой данных
23         $dsn = 'mysql:host=127.0.0.1;dbname=db_name;';
24         // Создаем экземпляр класса для работы с БД
25         $pdo = new \PDO($dsn, 'admin', 'admin');
26
27         // SQL запрос на получение всех новостей
28         $sql = 'SELECT * FROM news';
29
30         // Возвращаем полученные из БД данные
31         return $pdo->query($sql)->fetchAll(\PDO::FETCH_ASSOC);
32     }
33 }

Модель​ ​“News”​ ​содержит​ ​метод​ ​“displayAll”,​ ​который​ ​инкапсулирует​ ​бизнес​ ​логику получения​ ​всех​ ​новостей​ ​в​ ​одном​ ​месте

  • В​ ​23​ ​строке​ ​мы​ ​готовим​ ​так​ ​называемый​ ​“connection​ ​string"
  • В​ ​25​ ​строке​ ​создаем​ ​экземпляр​ ​класса​ ​PDO​ ​для​ ​работы​ ​с​ ​базой​ ​данныx
  • В​ ​28​ ​строке​ ​готовим​ ​SQL​ ​запрос​ ​на​ ​получение​ ​всех​ ​новостей
  • В​ ​31​ ​строке​ ​возвращаем​ ​список​ ​всех​ ​полученных​ ​новостей​ ​в​ ​виде ассоциативного​ ​массива

Итак,​ ​бизнес​ ​логика​ ​готова,​ ​осталось​ ​лишь​ ​создать​ ​действие​ ​контроллера,​ ​которое воспользуется​ ​методом​ ​модели​ ​с​ ​целью​ ​получения​ ​всех​ ​новостей​ ​и​ ​направит​ ​их​ ​в представление,​ ​которое​ ​отобразит​ ​новости​ ​в​ ​подобающем​ ​виде

1 <?php
2
3 namespace Controllers;
4
5 use System\View;
6 use Models\News;
7
8 /**
9 * Главный контроллер приложения
10 *
11 * @author farza
12 */
13 class homeController
14 {
15     /**
16     * Действие отвечающее за отображение главной
17     * страницы портала
18     *
19     * @author farZa
20     */
21     public function actionMain()
22     {
23         // Рендер главной страницы портала
24         View::render('index');
25     }
26
27     /**
28     * Действие отвечающее за отображение всех
29     * новостей
30     *
31     * @author farZa
32     */
33     public function actionNews()
34     {
35         // Создаем модель
36         $model = new News();
37
38         // Получаем данные используя модель
39         $data = $model->displayAll();
40
41         // Передаем данные представлению для их отображения
42         View::render('news', [
43             'data' => $data,
44         ]);
45     }
46 }

  • В​ ​33​ ​строке​ ​было​ ​создано​ ​действие​ ​с​ ​наименованием​ ​“news"
  • В​ ​36​ ​строке​ ​создается​ ​экземпляр​ ​класса​ ​модели
  • В​ ​39​ ​строке​ ​производится​ ​получение​ ​всех​ ​новостей
  • В​ ​42-44​ ​строках​ ​данные​ ​о​ ​новостях​ ​передаются​ ​в​ ​представление​ ​“news"

Представление​ ​“news”​ ​выглядит​ ​следующим​ ​образом

1  <?php
2  /**
3  * @var array $data - массив новостей
4  */
5  ?>
6
7  <h2>Новости</h2>
8
9  <div class="news-block">
10     <?php foreach ($data as $item): ?>
11         <div class="news-item" style="border: 1px solid black">
12             <div class="title">
13                 <span><?= $item['title']; ?></span>
14             </div>
15
16             <div class="description">
17                 <span><?= $item['description']; ?></span>
18             </div>
19         </div>
20
21         <br/>
22     <?php endforeach; ?>
23 </div>

Если​ ​перейти​ ​на​ ​страницу​ ​“home/news”​ ​то​ ​можно​ ​наблюдать​ ​следующий​ ​результат

images-017_800x600.jpg

Распространенные​ ​ошибки

Так​ ​как​ ​MVC​ ​не​ ​имеет​ ​строгой​ ​реализации,​ ​то​ ​реализован​ ​он​ ​может​ ​быть​ ​по-разному. Нет​ ​общепринятого​ ​определения,​ ​где​ ​должна​ ​располагаться​ ​бизнес-логика.​ ​Она​ ​может  находиться​ ​как​ ​в​ ​контроллере,​ ​так​ ​и​ ​в​ ​модели.​ ​От​ ​этого​ ​сообщество​ ​разделилось​ ​на два​ ​лагеря.​ ​Первый​ ​поддерживает​ ​принцип​ ​“Fat​ ​control er​ ​&​ ​Slim​ ​model”,​ ​а​ ​второй​ ​“Slim control er​ ​&​ ​Fat​ ​model”.​ ​Верно​ ​будет​ ​сказать,​ ​что​ ​эта​ ​тема​ ​является​ ​самой​ ​горячей дискуссией,​ ​хотя​ ​по​ ​определению​ ​контроллер​ ​должен​ ​лишь​ ​принимать​ ​и анализировать​ ​запросы,​ ​а​ ​также​ ​делать​ ​выбор​ ​следующего​ ​действия​ ​системы, (например,​ ​передача​ ​запроса​ ​другим​ ​элементам​ ​системы).

Также​ ​важно​ ​сказать,​ ​что​ ​от​ ​недостатка​ ​опыта​ ​начинающие​ ​программисты​ ​очень​ ​часто трактуют​ ​архитектурную​ ​модель​ ​MVC​ ​как​ ​пассивную​ ​модель.​ ​Пассивная​ ​модель выполняет​ ​функцию​ ​для​ ​доступа​ ​и​ ​работы​ ​с​ ​данными,​ ​а​ ​контроллер​ ​содержит​ ​всю бизнес​ ​логику.​ ​В​ ​результате​ ​-​ ​код​ ​моделей​ ​по​ ​факту​ ​является​ ​средством​ ​получения данных​ ​из​ ​СУБД,​ ​а​ ​контроллер​ ​-​ ​обыкновенный​ ​класс,​ ​наполненным​ ​бизнес-логикой. В​ ​рамках​ ​концепции​ ​MVC​ ​это​ ​является​ ​самой​ ​распространенной​ ​ошибкой,​ ​которую
следует​ ​избегать.​ ​В​ ​объектно-ориентированном​ ​программировании​ ​используется активная​ ​модель​ ​MVC,​ ​где​ ​модель​ ​-​ ​это​ ​не​ ​только​ ​совокупность​ ​кода​ ​доступа​ ​к​ ​данным и​ ​СУБД,​ ​но​ ​и​ ​вся​ ​бизнес-логика.

Применение

Применение​ ​данной​ ​концепции​ ​нашли​ ​множество​ ​фреймворков​ ​реализованных​ ​при помощи​ ​языка​ ​PHP.​ ​Такие​ ​популярные​ ​фреймворки​ ​как​ ​Zend​ ​Framework,​ ​Symfony, Laravel,​ ​Yi ​​и​ ​множество​ ​других​ ​полностью​ ​построены​ ​на​ ​данной​ ​моделе.

Дополнительные​ ​задания

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

  • В​ ​данный​ ​момент​ ​все​ ​представления​ ​хранятся​ ​в​ ​директории​ ​“Views”.​ ​Было​ ​бы неплохо,​ ​если​ ​бы​ ​в​ ​данной​ ​директории​ ​находились​ ​поддиректории, наименование​ ​которых​ ​бы​ ​соответствовало​ ​наименованию​ ​контроллеров,​ ​а​ ​в этих​ ​директориях​ ​хранились​ ​представления​ ​относящиеся​ ​исключительно​ ​к указанному​ ​контроллеру.​ ​Например​ ​есть​ ​контроллер​ ​“home”,​ ​а​ ​в​ ​директории “Views”​ ​должна​ ​быть​ ​под​ ​директория​ ​“home”,​ ​в​ ​которой​ ​бы​ ​хранились​ ​все представления​ ​относящиеся​ ​к​ ​вышеупомянутому​ ​контроллеру.​ ​В​ ​действиях контроллера​ ​при​ ​вызове​ ​метода​ ​“render”,​ ​система​ ​должна​ ​понимать​ ​в​ ​какой директории​ ​находится​ ​подключаемое​ ​представление.
  • Целесообразно​ ​рассмотреть​ ​вариант​ ​создания​ ​класса,​ ​который​ ​бы​ ​содержал логику​ ​относящуюся​ ​к​ ​маршрутизации
  • В​ ​данный​ ​момент​ ​представления​ ​не​ ​содержат​ ​основную​ ​часть​ ​HTML​ ​разметки, такую​ ​как​ ​“DOCTYPE”,​ ​“<html>”,​ ​“<body>”​ ​и​ ​т.д.,​ ​но​ ​в​ ​каждом​ ​представлении повторять​ ​обязательный​ ​каркас​ ​HTML​ ​страницы​ ​не​ ​является​ ​целесообразным. Было​ ​бы​ ​очень​ ​здорово,​ ​если​ ​бы​ ​был​ ​отдельный​ ​файл,​ ​называемый​ ​шаблоном (layout),​ ​который​ ​подключал​ ​в​ ​изменяемой​ ​части​ ​шаблона​ ​само​ ​представление
  • В​ ​моделях​ ​используется​ ​логика​ ​для​ ​работы​ ​с​ ​базой​ ​данных​ ​на​ ​довольно​ ​низком уровне,​ ​средствами​ ​PDO.​ ​Напишите​ ​класс,​ ​который​ ​бы​ ​инкапсулировал некоторую​ ​логическую​ ​часть​ ​работы​ ​с​ ​базой​ ​данных​ ​и​ ​предоставлял​ ​интерфейс для​ ​простого​ ​использования​ ​и​ ​манипулрования​ ​данными.​ ​Под​ ​интерфейсом можно​ ​понимать​ ​публичные​ ​методы​ ​типа​ ​“findAl ()”,​ ​поиск​ ​всех​ ​записей, “findOne”​ ​-​ ​поиск​ ​одной​ ​записи

Ссылки

Информация

Автор конспекта


Дата создания: 03.01.2019
Категория: Веб-разработка