Чат PHP-разработчиков
Логические задачи с собеседований
View в MVC

View в MVC

Сегодня мы сделаем компонент View, то самое “V” в архитектуре MVC. View – это представление, то есть та часть программы, которая формирует то, что видит пользователь.

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

Итак, давайте рассмотрим простейший пример и создадим для начала только шаблон. Путь до него будет следующим: templates/main/main.php

Шаблон main.php

Давайте запишем в него HTML-код для нашей будущей странички
templates/main/main.php

Код доступен только после покупки курса ООП в PHP.

Теперь давайте откроем наш контроллер MainController и изменим его метод main()
src/MyProject/Controllers/MainController.php

Код доступен только после покупки курса ООП в PHP.

Теперь откроем http://myproject.loc/ и полюбуемся результатом:

Результат вывода шаблона

Для начала давайте немного облегчим шаблон и вынесем стили в отдельный файл. Для этого в папке www создадим файл styles.css.
www/styles.css

Код доступен только после покупки курса ООП в PHP.

Теперь подключим этот файл со стилями в шаблоне:

Код доступен только после покупки курса ООП в PHP.

И снова убедимся, что всё работает.
Отображение при подключении внешних стилей

Давайте теперь попробуем передавать в шаблон переменные. Вместо явно заданных статей сделаем переменную со статьями:
src/MyProject/Controllers/MainController.php

Код доступен только после покупки курса ООП в PHP.

А теперь выведем эти статьи в шаблоне:
templates/main/main.php

Код доступен только после покупки курса ООП в PHP.

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

Создадим класс View.php по пути src/MyProject/View/View.php
Класс View

В конструкторе этого класса мы будем принимать путь до папки с шаблонами:
src/MyProject/View/View.php

Код доступен только после покупки курса ООП в PHP.

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

Код доступен только после покупки курса ООП в PHP.

Функция extract извлекает массив в переменные. То есть она делает следующее: в неё передаётся массив ['key1' => 1, 'key2' => 2], а после её вызова у нас имеются переменные $key1 = 1 и $key2 = 2.

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

Пришло время опробовать этот код в нашем контроллере. Создадим новый объект View в конструкторе контроллера, а затем внутри экшена вызовем renderHtml().
src/MyProject/Controllers/MainController.php

Код доступен только после покупки курса ООП в PHP.

Теперь мы можем снова открыть сайт, и убедиться, что всё прекрасно работает.

Буфер вывода

В тот момент, когда мы подключаем файл c HTML-кодом, либо пишем в PHP-коде echo, либо совершаем какой-либо другой вывод данных, эти данные начинают сразу передаваться в поток вывода. И если что-то пойдёт не так, мы не сможем вернуть этот вывод и вывести вместо него какую-нибудь ошибку. Но в PHP есть возможность весь этот поток вывода положить во временный буфер вывода. Выглядит его использование следующим образом:
src/MyProject/View/View.php

Код доступен только после покупки курса ООП в PHP.

Если вы сейчас попробуете запустить наш скрипт, то увидите пустую страницу. Дело в том, что все данные, которые должны были быть переданы в поток вывода, оказались в переменной $buffer.
Для того, чтобы передать эти данные в поток вывода, достаточно только вывести переменную $buffer.
src/MyProject/View/View.php

Код доступен только после покупки курса ООП в PHP.

Откройте страничку снова, и убедитесь, что всё вернулось на свои места.

Так в чём же профит? А профит в том, что мы можем обрабатывать ошибки, возникшие в процессе работы с шаблоном. Пока мы с вами не знакомы с понятием «Исключения», давайте предположим, что у нас при подключении шаблона произошла какая-то ошибка. Тогда мы могли бы обработать эту ошибку и не выводить пользователю неправильно отрисованный шаблон. Мы могли бы сделать что-то типа такого:
src/MyProject/View/View.php

Код доступен только после покупки курса ООП в PHP.

Чуть позже мы вернёмся к обработке возможных ошибок, когда познакомимся с исключениями. А пока оставим этот код в таком состоянии:
src/MyProject/View/View.php

Код доступен только после покупки курса ООП в PHP.

Реиспользование шаблонов

Давайте в наш контроллер вернём экшн из прошлых уроков, который выводил приветствие.
src/MyProject/Controllers/MainController.php

Код доступен только после покупки курса ООП в PHP.

Давайте изменим его, чтобы он работал через шаблон.

Код доступен только после покупки курса ООП в PHP.

Ну и создадим сам шаблон для него.
templates/main/hello.php

Код доступен только после покупки курса ООП в PHP.

Давайте теперь перейдём по адресу http://myproject.loc/hello/username и увидим, что всё прекрасно сработало:

Рендеринг другого шаблона

А теперь внимательно присмотритесь к нашим двум получившимся шаблонам. Согласитесь, у них всё абсолютно одинаковое, кроме текста, который мы выводим на странице. Давайте это исправим! Всё, что выше нашего контента – вынесем в один файл, всё что ниже – в другой. А в самих наших шаблонах будем эти два файла подключать.

Итак, выносим верхнюю часть (так называемую шапку сайта - хедер) в новый файл templates/header.php
templates/header.php

Код доступен только после покупки курса ООП в PHP.

Затем выносим нижнюю часть (называемую футером или подвалом) в файл templates/footer.php
templates/footer.php

Код доступен только после покупки курса ООП в PHP.

После чего редактируем наши шаблоны:
templates/main/main.php

Код доступен только после покупки курса ООП в PHP.

templates/main/hello.php

Код доступен только после покупки курса ООП в PHP.

Должна получиться вот такая структура в шаблонах:
Структура папки с шаблонами

После этого заходим на странички http://myproject.loc/hello/username и http://myproject.loc/ и радуемся результату :)

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

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

  1. Добавляем экшн в контроллер (либо создаём ещё и новый контроллер);
  2. Добавляем для него роут в routes.php;
  3. Описываем логику внутри экшена и в конце вызываем у компонента view метод renderHtml();
  4. Создаём шаблон для вывода результата.

Вот и весь View.

Читайте также
Комментарии


tomsonst
tomsonst
// 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'],
        ];
        $title = '';
        $this->view->renderHtml('main/main.php', ['articles' => $articles], $title);
    }

    public function sayHello(string $name)
    {
        $title = 'Страница приветствия';
        $this->view->renderHtml('main/hello.php', ['name' => $name], $title);
    }
}

// View.php 

<?php

namespace MyProject\View;
class View
{
    private $templatesPath;

    public function __construct(string $templatesPath)
    {
        $this->templatesPath = $templatesPath;
    }

    public function renderHtml(string $templateName, array $vars = [], string $title)
    {
        extract($vars);

        ob_start();
        include $this->templatesPath . '/' . $templateName;
        $buffer = ob_get_contents();
        ob_end_clean();

        echo $buffer;
    }
}

// header.php

    <?php 
    if (!empty($title)) {
        $title;
    } else {
    $title = 'Мой блог';
} ?>
    <title><?= $title ?></title>
ivashkevich
ivashkevich

А зачем вам еще один параметр? Можно же title положить в vars, просто еще одно значение.
Выводить можно проще:

<title><?= $title ?? 'Мой блог' ?></title>
Sparky
Sparky

Переменную $title передаю из MainController в renderHtml (в массив $vars). Все работает, но не получается вывести <title> при пустом значении $title. Вывожу так:
<title><?= $title ?? "Мой блог" ?></title>

Если делать проверку if (empty($title)), то выводит что надо. Почему <?= $title ?? "Мой блог" ?> не работает?

ivashkevich
ivashkevich

Оператор ?? работает только с null-ом. Значит там не null, попробуй через var_dump узнать, что в переменной лежит. Скорее всего у тебя там пустая строка =)

Sparky
Sparky

О, да, так и есть. Не знал про Null :)

computerix
computerix
//routes
<?php
return [
    '~^hello/(.*)$~' => [\MyProject\Controllers\MainController::class, 'sayHello'],
    '~^bye/(.*)$~' => [\MyProject\Controllers\MainController::class, 'sayBye'],
    '~^$~' => [\MyProject\Controllers\MainController::class, 'main'],
    '~(.*)$~' => [\MyProject\Controllers\MainController::class, 'title'],
];

//MainController
<?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)
    {
        $this->view->renderHtml('main/hello.php', ['name' => $name]);
    }
    public function sayBye(string $name)
    {
        $this->view->renderHtml('main/bye.php', ['name' => $name]);
    }
    public function title(string $title)
    {
        $articles = [
            ['name' => 'Статья 1', 'text' => 'Текст статьи 1'],
            ['name' => 'Статья 2', 'text' => 'Текст статьи 2'],
        ];
       $this->view->renderHtml('main/main.php', ['title' => $title, 'articles' => $articles]);
    }
}

//main
<? if (empty($title)) {$title = 'Мой блог';} ?>
<title><?= $title ?></title>
<?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'; ?>

//hello
<title>Страница приветствия</title>
<?php include __DIR__ . '/../header.php'; ?>
Привет, <?= $name ?>!!!
<?php include __DIR__ . '/../footer.php'; ?>
ivashkevich
ivashkevich

Вся домашка заключалась в следующем:

  • в шаблоне header.php добавить:
    <title><?= $title ?? 'Мой блог' ?></title>
  • в экшене sayHello() сделать следующее:
    public function sayHello(string $name)
    {
    $this->view->renderHtml('main/hello.php', ['name' => $name, 'title' => 'Страница приветствия']);
    }

Возможно, я плохо сформулировал домашнее задание, если это так, напишите мне в ВК, подумаем, как сделать лучше.

Если же вам просто захотелось поэкспериментировать - то всё в порядке =)

computerix
computerix

Все оказалось проще, чем я понял) Я ещё подумал, что сложноватая домашка, но зато разобрался хорошо, пока делал)

ArtemijeKA
ArtemijeKA

Я написал в /src/MVCExample/Views/View.php

public function renderHtml(string $templateName, array $vars)

Обязательно ли писать:

array $vars = []
ivashkevich
ivashkevich

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

ArtemijeKA
ArtemijeKA

Понял, спасибо.

ArtemijeKA
ArtemijeKA

Учитель, почему у меня не подключаются стили если я перехожу на /hello/Name?
code github

PS
У меня структура локального сайта построена как учебник с папками по этому дирректория с фронтальным контроллером такая:

http://php_book/mvc/www/index.php
ivashkevich
ivashkevich

Открой консоль разработчика, посмотри вкладку networks. Там будет видно, откуда браузер пытается грузить стили.

ArtemijeKA
ArtemijeKA

Я так понял у нас localhost подключен к www?
И тогда подключая стили через / корневой слеш мы не ошибаемся и попадаем сначала в www дирректорию.

ivashkevich
ivashkevich

www - должна быть корневой директорией сайта. И если в браузере пишем /что-то там, то веб сервер будет искать именно в папке www. Как и index.php в этой папке является точкой входа в приложение.

SBTesla
SBTesla
//header.php
<title><?= $title ?? 'Мой блог-жек'?></title>

//routes.php
return[
      '~^hello/(.*)$~' => [\MyProject\Controllers\MainController::class, 'sayHello'],
      '~^$~' => [MyProject\Controllers\MainController::class, 'main'],
      '~^bye/(.*)$~' => [MyProject\Controllers\MainController::class, 'sayBye'],
     '~(.*)$~'=>[MyProject\Controllers\MainController::class, 'sayHello']
      ];

//MainController.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' => ' Статья 1 ', 'text' => ' Текст статьи 2'],
               ];
               include __DIR__ . '/../../../templates/main/main.php';
          }
          public function sayHello(string $name)
          {
               $this->view->renderHtml('main/hello.php', ['name' => $name, 'title' => 'Страница приветствия']);
          }

          public function sayBye(string $name)
          {
               echo 'Досвидания, ' . $name;
          }

     }
ArtemijeKA
ArtemijeKA
// MainController.php
public function sayHello(string $name)
    {
        $this->view->renderHtml('main/hello.php', ['name' => $name, 'title' => 'Страница приветствия']);
    }
// header.php
<title><?=$title ?? 'Мой блог'?></title>
AxLT
AxLT

Почему так работает:

<?= $title ?? 'Мой блог' ?>

а так нет?

<?php $title ?? 'Мой блог' ?>
ivashkevich
ivashkevich

<?=... - это сокращение для <?php echo...

demyanovpaul@yandex.ru
demyanovpaul@yandex.ru

Сделайте чтобы title для каждой страницы можно было задавать через переменную для шаблона. В случае, когда title не передан, выводите заголовок по умолчанию - "Мой блог". Для страницы /hello/username сделайте title "Страница приветствия".

//header.php
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title><?= $title ?? 'Мой блог'; ?></title>
    <link rel="stylesheet" href="/styles.css">
</head>
<body>

<table class="layout">
    <tr>
        <td colspan="2" class="header">
            Мой блог
        </td>
    </tr>
    <tr>
        <td>
//MainController.php
public function sayHello(string $name)
    {
        $this->view->renderHtml('main/hello.php', ['name' => $name, 'title' => 'Страница приветствия']);
    }
ivashkevich
ivashkevich

То что нужно!

Todd
Todd

MainController

 public function sayHello(string $name) {
        $this->view->renderHtml('main/hello.php', ['name' => $name, 'title' => 'Страница приветствия']);
    }

header

<title><?= $title ?? 'my blog' ?></title>
ivashkevich
ivashkevich

Отлично! Не забывайте переносить для функций открывающую фигурную скобку на отдельную строку.