Обо мне

Простые MVC-приложения

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

Теория

Если коротко, то MVC (model view controller) это такой способ написания программы, когда код отвечающий за вывод данных, пишется в одном месте, а код который эти данные формирует, пишется в другом месте. В итоге получается так, что если вам надо подправить вывод вы сразу знаете в каком месте искать. Сейчас все популярные фреймворки используют такую архитектуру.

Также стоит упомянуть тот факт, что существует два лагеря: один пишет логику в контроллерах, второй в моделях. В тех фреймворках, которые знаю я (yii, laravel) логику пишут в контроллерах, а модели являются просто экземплярами ORM. У yii кстати в мануале написано, что писать логику надо в моделях, а потом они сами в примерах пишут её в контроллерах, довольно забавно.

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

Читайте также:  Hex питон что это

Ещё можно упомянуть, что есть два вида MVC Page Controller и Front Controller. Page Controller’ом пользуются редко, его подход заключается в использовании нескольких точек входа (запросы к сайту осуществляются к нескольким файлам), и внутри каждой точки входа свой код отображения. Мы будем писать Front Controller с одной точкой входа.

Практика

Начать надо с настройки сервера для переадресации на нашу единую точку входа. Если у нас apache, то в файле .htaccess пишем следующее

RewriteEngine on RewriteCond % !-f RewriteCond % !-d RewriteRule .* index.php [L] 

Дальше в папке нашего проекта создаём папку, которую можно назвать App например. В ней будет следующее содержимое.

Наш Service Locator. Файл App.php

 public static function bootstrap() < static::$router = new App\Router(); static::$kernel = new App\Kernel(); static::$db = new App\Db(); >public static function loadClass ($className) < $className = str_replace('\\', DIRECTORY_SEPARATOR, $className); require_once ROOTPATH.DIRECTORY_SEPARATOR.$className.'.php'; >public function handleException (Throwable $e) < if($e instanceof \App\Exceptions\InvalidRouteException) < echo static::$kernel->launchAction('Error', 'error404', [$e]); >else< echo static::$kernel->launchAction('Error', 'error500', [$e]); > > > 

Сервис локатор нужен чтобы хранить в нём компоненты нашего приложения. Поскольку у нас простое mvc приложение, то мы не используем паттерн registry (как например в yii сделано). А просто сохраняем компоненты приложения в статические свойства, чтобы обращаться к ним было проще. Ещё App регистрирует автозагрузчик классов и обработчик исключений.

Роутер. Файл Router.php

 $route = is_null($route) ? $_SERVER['REQUEST_URI'] : $route; $route = explode('/', $route); array_shift($route); $result[0] = array_shift($route); $result[1] = array_shift($route); $result[2] = $route; return $result; > > 

В простом mvc приложении роутер содержит всего один метод. Он парсит адрес из $_SERVER[‘REQUEST_URI’]. Я ещё не сказал, что все наши ссылки на страницы сайта должны быть вида www.ourwebsite.com/%controller%/%action%, где %controller% — имя файла контроллера, а %action% — имя метода контроллера, который будет вызван.

Файл Db.php

getPDOSettings(); $this->pdo = new \PDO($settings['dsn'], $settings['user'], $settings['pass'], null); > protected function getPDOSettings() < $config = include ROOTPATH.DIRECTORY_SEPARATOR.'Config'.DIRECTORY_SEPARATOR.'Db.php'; $result['dsn'] = ":host=;dbname=;charset="; $result['user'] = $config['user']; $result['pass'] = $config['pass']; return $result; > public function execute($query, array $params=null) < if(is_null($params))< $stmt = $this->pdo->query($query); return $stmt->fetchAll(); > $stmt = $this->pdo->prepare($query); $stmt->execute($params); return $stmt->fetchAll(); > > 

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

Файл Config/Db.php

 'mysql', 'host' => 'localhost', 'dbname' => 'gotlib', 'charset' => 'utf8', 'user' => 'root', 'pass' => '' ];

Наше ядро. Файл Kernel.php

resolve(); echo $this->launchAction($controllerName, $actionName, $params); > public function launchAction($controllerName, $actionName, $params) < $controllerName = empty($controllerName) ? $this->defaultControllerName : ucfirst($controllerName); if(!file_exists(ROOTPATH.DIRECTORY_SEPARATOR.'Controllers'.DIRECTORY_SEPARATOR.$controllerName.'.php')) < throw new \App\Exceptions\InvalidRouteException(); >require_once ROOTPATH.DIRECTORY_SEPARATOR.'Controllers'.DIRECTORY_SEPARATOR.$controllerName.'.php'; if(!class_exists("\\Controllers\\".ucfirst($controllerName))) < throw new \App\Exceptions\InvalidRouteException(); >$controllerName = "\\Controllers\\".ucfirst($controllerName); $controller = new $controllerName; $actionName = empty($actionName) ? $this->defaultActionName : $actionName; if (!method_exists($controller, $actionName)) < throw new \App\Exceptions\InvalidRouteException(); >return $controller->$actionName($params); > > 

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

Файл Controller.php

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

 public function render ($viewName, array $params = []) < $viewFile = ROOTPATH.DIRECTORY_SEPARATOR.'Views'.DIRECTORY_SEPARATOR.$viewName.'.php'; extract($params); ob_start(); require $viewFile; $body = ob_get_clean(); ob_end_clean(); if (defined(NO_LAYOUT))< return $body; >return $this->renderLayout($body); > >

Файл index.php

Не забываем создать индексный файл в корне:

Создаём контроллеры и виды

Работа с нашим приложением (можно даже сказать минифреймворком) теперь сводится к созданию видов и контроллеров. Пример контроллера следующий (в папке Controllers):

 my_photo

Привет

Меня зовут Глеб и я - веб-разработчик.

Мои контакты:
8-912-641-3462
goootlib@gmail.com

В папке Views/Layout создаём Layout.php:

         

Заключение

Если решите пользоваться кодом приложения, которое описал я, не забудьте создать контроллер Error с методами error404 и error500. Класс для работы с бд, описанный мной, подходит для написания запросов руками, вместо него можно подключить ORM и вас будут настоящие модели.

Источник

MVC для веб: проще некуда

В этой статье мы рассмотрим архитектурный паттерн MVC (Model, View, Controller) в применении к веб-разработке, «в чистом виде», без привлечения каких-то дополнительных, не относящихся к MVC структур и паттернов. Мы будем продвигаться от простого к сложному, поэтому пока не станем рассматривать, например, дальнейшее развитие MVC – паттерн HMVC (Hierarchical MVC). Хотя HMVC, несомненно, намного более интересен для разработки веб-приложений, но его применение не отменяет необходимости понимания «обычного» MVC.

Статья в Википедии (а именно туда, видимо, чаще всего попадают те, кто только начинает изучать MVC), изобилует неточностями и туманными формулировками, само определение, по сути, является неверным, а приведенная схема просто напросто не соответствует той, которая применяется в веб вообще и при разработке на PHP – в особенности.

Наиболее корректное определение паттерна MVC я обнаружил тут:

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

Уточним, что термин «компонент» в данном случае не имеет никакой связи с компонентами некоторых популярных CMS или фреймворков, а компоненты Битрикса, например вообще строятся из всех трёх составляющих MVC.
В приведённом определении под компонентом следует понимать некую отдельную часть кода, каждая из которых играет одну из ролей Контроллера, Модели или Представления, где Модель служит для извлечения и манипуляций данными приложения, Представление отвечает за видимое пользователю отображение этих данных (то есть, в применении к вебу, формирует отдаваемый сервером браузеру пользователя HTML/CSS), а Контроллер управляет всем этим оркестром.

Давайте рассмотрим классическую схему веб-приложения:

Рисунок 1

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

Получив Запрос 1, Контроллер его анализирует, и в зависимости от результатов обработки может выдать следующие варианты ответа (почему ответ имеет номер 4, станет понятно из следующих рисунков):

1. Сразу выдать ответ об ошибке (например, при запросе несуществующей страницы отдать только HTTP-заголовок «404 Not found»)

2. Если поступивший Запрос 1 признан корректным, то, в зависимости от того, является он запросом на просмотр или на модификацию данных, Контроллер вызывает соответствующий метод Модели, такой как Save или Load (Запрос 2 на Рис.2).

Рисунок 2

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

 // возвращает ассоциативный массив с данными о Товаре либо FALSE при неудаче bool Product_Save (array $data) < . >// возвращает TRUE при удачном сохранении данных $data, либо FALSE при неудаче ?> 

Итак, в зависимости от полученного от Модели Ответа 2 Контроллер решает, какое из Представлений вызвать для формирования итогового ответа на изначальный Запрос 1:

3.1. В случае неудачи – Представление для сообщения об ошибке
3.2. В случае успеха – Представление для отображения запрашиваемых данных либо сообщения об их успешном сохранении (если Запрос 1 был на изменение данных).

Рисунок 3

Вопрос о том, кто должен проверять на валидность и права доступа входные данные (Контроллер или Модель), является предметом достаточно многочисленных споров, поскольку паттерн MVC не описывает таких деталей. Это значит, что в этом вопросе выбор за вами (или за вас его сделали авторы вашего любимого фрейvворка или CMS).
Мы в своей практике придерживаемся такого подхода:
Контроллер проверяет входные данные на предмет «общей» (т.е. независящей от конкретного запроса) корректности, соответствие требованиям Модели к валидности сохраняемых данных проверяет соответствующий метод Модели, а права доступа – метод Access отдельного класса User.

Для вызова Представления в PHP иногда проектируется специальный класс (а то и несколько классов), например, View (это часто встречается в описаниях MVC в реализации того или иного фреймворка), однако это не является требованием MVC.
Также файлы Представлений часто называют шаблонами, а при использовании так называемых шаблонизаторов роль Представления играет сам шаблонизатор, а шаблоны (т.е. файлы, содержащие непосредственно HTML-разметку) в некоторых фреймворках называют layouts.

Не вполне понятен предыдущий абзац? Забейте, поскольку у нас каждое Представление – это просто отдельный PHP-файл, а сам PHP устроен так, что в случае, если мы используем для выполнения Запроса 3 инструкцию include, Ответ 3 и Ответ 4 (помните, что это ответ на Запрос 1?) отдаются браузеру автоматически, средствами самого PHP.

Давайте рассмотрим пример.

У нас есть два варианта Представления (шаблоны), в которых

будет означать HTML-код, предворяющий в формируемом веб-документе основной контент (т.е. содержит тег doctype, контейнер head, код шапки страницы, и т.п.), а – примерно то же, только для подвала страницы.

Листинг 1. Шаблон product.tpl.php отображает данные о Товаре (которые к моменту его вызова уже содержит объект $product):

  

Title;?>

Цена:Price;?>

Description;?>

Листинг 2. Шаблон error.tpl.php отображает сообщение об ошибке (которое содержится в переменной $error):

Листинг 3. Контроллер product.php, служащий для отобоажения Товара, будет выглядеть примерно так:

 if (!$id = . ) // проверка "общей" валидности Запроса 1 error(. ); // проверка прав доступа if (!$user->Access(. )) error(403); if (!$product = Product::Load($id)) // Запрос 2 и анализ Ответа 2 error('Тут скорее всего случилась ошибка БД'); include 'product.tpl.php'; // Запрос 3 и Ответы 3 и 4 ?> 

Те, кто любит красивый и оптимизированный код могут заметить, что блоки HTML.header и HTML.footer дублируются в обоих шаблонах (они же Представления) error.tpl.php и product.tpl.php, и наверняка захотят вынести их в Контроллер product.save.php:

…. и нарушить таким образом основное правило MVC – разделяйте Контроллер, Модель и Представление.

Тем не менее, дублирующийся код – однозначное Зло. Что же делать?
Мы должны перейти от MVC к HMVC!
Но это – тема отдельной статьи.

Источник

Оцените статью