View в MVC
Сегодня мы сделаем компонент View, то самое “V” в архитектуре MVC. View – это представление, то есть та часть программы, которая формирует то, что видит пользователь.
В случае приложения на языке PHP, в подавляющем большинстве случаев представление занимается формированием HTML-кода. Вообще, это довольно простая часть кода, которой даётся только имя шаблона и список переменных, которые в этот шаблон нужно подставить.
Итак, давайте рассмотрим простейший пример и создадим для начала только шаблон. Путь до него будет следующим: templates/main/main.php
Давайте запишем в него HTML-код для нашей будущей странички
templates/main/main.php
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Мой блог</title>
<style>
.layout {
width: 100%;
max-width: 1024px;
margin: auto;
background-color: white;
border-collapse: collapse;
}
.layout tr td {
padding: 20px;
vertical-align: top;
border: solid 1px gray;
}
.header {
font-size: 30px;
}
.footer {
text-align: center;
}
.sidebarHeader {
font-size: 20px;
}
.sidebar ul {
padding-left: 20px;
}
a, a:visited {
color: darkgreen;
}
</style>
</head>
<body>
<table class="layout">
<tr>
<td colspan="2" class="header">
Мой блог
</td>
</tr>
<tr>
<td>
<h2>Статья 1</h2>
<p>Всем привет, это текст первой статьи</p>
<hr>
<h2>Статья 2</h2>
<p>Всем привет, это текст второй статьи</p>
</td>
<td width="300px" class="sidebar">
<div class="sidebarHeader">Меню</div>
<ul>
<li><a href="/">Главная страница</a></li>
<li><a href="/about-me">Обо мне</a></li>
</ul>
</td>
</tr>
<tr>
<td class="footer" colspan="2">Все права защищены (c) Мой блог</td>
</tr>
</table>
</body>
</html>
Теперь давайте откроем наш контроллер MainController и изменим его метод main()
src/MyProject/Controllers/MainController.php
<?php
namespace MyProject\Controllers;
class MainController
{
public function main()
{
include __DIR__ . '/../../../templates/main/main.php';
}
}
Теперь откроем http://myproject.loc/ и полюбуемся результатом:
Для начала давайте немного облегчим шаблон и вынесем стили в отдельный файл. Для этого в папке www создадим файл styles.css.
www/styles.css
.layout {
width: 100%;
max-width: 1024px;
margin: auto;
background-color: white;
border-collapse: collapse;
}
.layout tr td {
padding: 20px;
vertical-align: top;
border: solid 1px gray;
}
.header {
font-size: 30px;
}
.footer {
text-align: center;
}
.sidebarHeader {
font-size: 20px;
}
.sidebar ul {
padding-left: 20px;
}
a, a:visited {
color: darkgreen;
}
Теперь подключим этот файл со стилями в шаблоне:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Мой блог</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<table class="layout">
<tr>
<td colspan="2" class="header">
Мой блог
</td>
</tr>
<tr>
<td>
<h2>Статья 1</h2>
<p>Всем привет, это текст первой статьи</p>
<hr>
<h2>Статья 2</h2>
<p>Всем привет, это текст второй статьи</p>
</td>
<td width="300px" class="sidebar">
<div class="sidebarHeader">Меню</div>
<ul>
<li><a href="/">Главная страница</a></li>
<li><a href="/about-me">Обо мне</a></li>
</ul>
</td>
</tr>
<tr>
<td class="footer" colspan="2">Все права защищены (c) Мой блог</td>
</tr>
</table>
</body>
</html>
И снова убедимся, что всё работает.
Давайте теперь попробуем передавать в шаблон переменные. Вместо явно заданных статей сделаем переменную со статьями:
src/MyProject/Controllers/MainController.php
<?php
namespace MyProject\Controllers;
class MainController
{
public function main()
{
$articles = [
['name' => 'Статья 1', 'text' => 'Текст статьи 1'],
['name' => 'Статья 2', 'text' => 'Текст статьи 2'],
];
include __DIR__ . '/../../../templates/main/main.php';
}
}
А теперь выведем эти статьи в шаблоне:
templates/main/main.php
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Мой блог</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<table class="layout">
<tr>
<td colspan="2" class="header">
Мой блог
</td>
</tr>
<tr>
<td>
<?php foreach ($articles as $article): ?>
<h2><?= $article['name'] ?></h2>
<p><?= $article['text'] ?></p>
<hr>
<?php endforeach; ?>
</td>
<td width="300px" class="sidebar">
<div class="sidebarHeader">Меню</div>
<ul>
<li><a href="/">Главная страница</a></li>
<li><a href="/about-me">Обо мне</a></li>
</ul>
</td>
</tr>
<tr>
<td class="footer" colspan="2">Все права защищены (c) Мой блог</td>
</tr>
</table>
</body>
</html>
Если нам понадобится в другом контроллере или другом экшне добавить логику для работы с шаблонами, нам снова придется перечислять список переменных, а затем писать include с указанием полного пути для шаблона. Звучит не очень хорошо. Поэтому мы просто вынесем логику с подключением нужного шаблона в отдельный класс.
Создадим класс View.php по пути src/MyProject/View/View.php
В конструкторе этого класса мы будем принимать путь до папки с шаблонами:
src/MyProject/View/View.php
<?php
namespace MyProject\View;
class View
{
private $templatesPath;
public function __construct(string $templatesPath)
{
$this->templatesPath = $templatesPath;
}
}
Помимо этого давайте добавим метод, в который будем передавать имя конкретного шаблона и массив с переменными.
<?php
namespace MyProject\View;
class View
{
private $templatesPath;
public function __construct(string $templatesPath)
{
$this->templatesPath = $templatesPath;
}
public function renderHtml(string $templateName, array $vars = [])
{
extract($vars);
include $this->templatesPath . '/' . $templateName;
}
}
Функция extract извлекает массив в переменные. То есть она делает следующее: в неё передаётся массив ['key1' => 1, 'key2' => 2], а после её вызова у нас имеются переменные $key1 = 1 и $key2 = 2.
После этого мы просто подключаем файл с нужным шаблоном, получив путь до него, склеив пути до папки с шаблонами и именем конкретного шаблона.
Пришло время опробовать этот код в нашем контроллере. Создадим новый объект View в конструкторе контроллера, а затем внутри экшена вызовем renderHtml().
src/MyProject/Controllers/MainController.php
<?php
namespace MyProject\Controllers;
use MyProject\View\View;
class MainController
{
private $view;
public function __construct()
{
$this->view = new View(__DIR__ . '/../../../templates');
}
public function main()
{
$articles = [
['name' => 'Статья 1', 'text' => 'Текст статьи 1'],
['name' => 'Статья 2', 'text' => 'Текст статьи 2'],
];
$this->view->renderHtml('main/main.php', ['articles' => $articles]);
}
}
Теперь мы можем снова открыть сайт, и убедиться, что всё прекрасно работает.
Буфер вывода
В тот момент, когда мы подключаем файл c HTML-кодом, либо пишем в PHP-коде echo, либо совершаем какой-либо другой вывод данных, эти данные начинают сразу передаваться в поток вывода. И если что-то пойдёт не так, мы не сможем вернуть этот вывод и вывести вместо него какую-нибудь ошибку. Но в PHP есть возможность весь этот поток вывода положить во временный буфер вывода. Выглядит его использование следующим образом:
src/MyProject/View/View.php
public function renderHtml(string $templateName, array $vars = [])
{
extract($vars);
ob_start();
include $this->templatesPath . '/' . $templateName;
$buffer = ob_get_contents();
ob_end_clean();
}
Если вы сейчас попробуете запустить наш скрипт, то увидите пустую страницу. Дело в том, что все данные, которые должны были быть переданы в поток вывода, оказались в переменной $buffer.
Для того, чтобы передать эти данные в поток вывода, достаточно только вывести переменную $buffer.
src/MyProject/View/View.php
public function renderHtml(string $templateName, array $vars = [])
{
extract($vars);
ob_start();
include $this->templatesPath . '/' . $templateName;
$buffer = ob_get_contents();
ob_end_clean();
echo $buffer;
}
Откройте страничку снова, и убедитесь, что всё вернулось на свои места.
Так в чём же профит? А профит в том, что мы можем обрабатывать ошибки, возникшие в процессе работы с шаблоном. Пока мы с вами не знакомы с понятием «Исключения», давайте предположим, что у нас при подключении шаблона произошла какая-то ошибка. Тогда мы могли бы обработать эту ошибку и не выводить пользователю неправильно отрисованный шаблон. Мы могли бы сделать что-то типа такого:
src/MyProject/View/View.php
public function renderHtml(string $templateName, array $vars = [])
{
extract($vars);
ob_start();
include $this->templatesPath . '/' . $templateName;
$buffer = ob_get_contents();
ob_end_clean();
$error = 'В шаблоне была ошибка!';
if (empty($error)) {
echo $buffer;
} else {
echo $error;
}
}
Чуть позже мы вернёмся к обработке возможных ошибок, когда познакомимся с исключениями. А пока оставим этот код в таком состоянии:
src/MyProject/View/View.php
public function renderHtml(string $templateName, array $vars = [])
{
extract($vars);
ob_start();
include $this->templatesPath . '/' . $templateName;
$buffer = ob_get_contents();
ob_end_clean();
echo $buffer;
}
Реиспользование шаблонов
Давайте в наш контроллер вернём экшн из прошлых уроков, который выводил приветствие.
src/MyProject/Controllers/MainController.php
<?php
namespace MyProject\Controllers;
use MyProject\View\View;
class MainController
{
private $view;
public function __construct()
{
$this->view = new View(__DIR__ . '/../../../templates');
}
public function main()
{
$articles = [
['name' => 'Статья 1', 'text' => 'Текст статьи 1'],
['name' => 'Статья 2', 'text' => 'Текст статьи 2'],
];
$this->view->renderHtml('main/main.php', ['articles' => $articles]);
}
public function sayHello(string $name)
{
echo 'Привет, ' . $name;
}
}
Давайте изменим его, чтобы он работал через шаблон.
public function sayHello(string $name)
{
$this->view->renderHtml('main/hello.php', ['name' => $name]);
}
Ну и создадим сам шаблон для него.
templates/main/hello.php
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Мой блог</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<table class="layout">
<tr>
<td colspan="2" class="header">
Мой блог
</td>
</tr>
<tr>
<td>
Привет, <?= $name ?>!!!
</td>
<td width="300px" class="sidebar">
<div class="sidebarHeader">Меню</div>
<ul>
<li><a href="/">Главная страница</a></li>
<li><a href="/about-me">Обо мне</a></li>
</ul>
</td>
</tr>
<tr>
<td class="footer" colspan="2">Все права защищены (c) Мой блог</td>
</tr>
</table>
</body>
</html>
Давайте теперь перейдём по адресу http://myproject.loc/hello/username и увидим, что всё прекрасно сработало:
А теперь внимательно присмотритесь к нашим двум получившимся шаблонам. Согласитесь, у них всё абсолютно одинаковое, кроме текста, который мы выводим на странице. Давайте это исправим! Всё, что выше нашего контента – вынесем в один файл, всё что ниже – в другой. А в самих наших шаблонах будем эти два файла подключать.
Итак, выносим верхнюю часть (так называемую шапку сайта - хедер) в новый файл templates/header.php
templates/header.php
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Мой блог</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<table class="layout">
<tr>
<td colspan="2" class="header">
Мой блог
</td>
</tr>
<tr>
<td>
Затем выносим нижнюю часть (называемую футером или подвалом) в файл templates/footer.php
templates/footer.php
</td>
<td width="300px" class="sidebar">
<div class="sidebarHeader">Меню</div>
<ul>
<li><a href="/">Главная страница</a></li>
<li><a href="/about-me">Обо мне</a></li>
</ul>
</td>
</tr>
<tr>
<td class="footer" colspan="2">Все права защищены (c) Мой блог</td>
</tr>
</table>
</body>
</html>
После чего редактируем наши шаблоны:
templates/main/main.php
<?php include __DIR__ . '/../header.php'; ?>
<?php foreach ($articles as $article): ?>
<h2><?= $article['name'] ?></h2>
<p><?= $article['text'] ?></p>
<hr>
<?php endforeach; ?>
<?php include __DIR__ . '/../footer.php'; ?>
templates/main/hello.php
<?php include __DIR__ . '/../header.php'; ?>
Привет, <?= $name ?>!!!
<?php include __DIR__ . '/../footer.php'; ?>
Должна получиться вот такая структура в шаблонах:
После этого заходим на странички http://myproject.loc/hello/username и http://myproject.loc/ и радуемся результату :)
Согласитесь, теперь можно очень просто добавлять новые странички на сайте, а также изменять шапку и футер только в одном месте.
Давайте повторим последовательность шагов, которые необходимо сделать для добавления новой странички:
- Добавляем экшн в контроллер (либо создаём ещё и новый контроллер);
- Добавляем для него роут в routes.php;
- Описываем логику внутри экшена и в конце вызываем у компонента view метод renderHtml();
- Создаём шаблон для вывода результата.
Вот и весь View.
Код с результатом на гитхабе.
Комментарии