Бэкенд / Создание контроллеров

Как я уже говорил, практически все современные веб приложения работают на базе архитектуры MVC, у нас из нее сейчас только более менее выделился пункт V то бишь View – представление, ну или шаблон если более понятно.

Если вы писали код примерно, как я, то у вас должна была бы получится двухуровневая структура в роутере

как вариант вы могли обойтись без вложенных if-ов но тогда вам бы приходилось дублировать код, ответственный за название галактики, общее описание объекта и картинку

в общем оба подхода одинаково плохи. Их неудобно читать и не приятно писать.

Мы же возьмем лучшее от этих двух подходов. Плоскую структуру из второго и иерархию из первого. Для этого мы воспользуемся возможности создавать классы в php.

Классы позволят нам утащить код из if-ов а также строить иерархию наследования. Ну типа у общей страницы андромеды название “Галактика Андромеды”, а у страница с картинкой то же название, но плюс там еще появляется информация о картинки.

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

И так, создаем папку controllers и в ней файл BaseController

Пишем в него

<?php
// класс абстрактный, чтобы нельзя было создать экземпляр
abstract class BaseController {
    // так как все вертится вокруг данных, то заведем функцию,
    // которая будет возвращать контекст с данными
    public function getContext(): array {
        return []; // по умолчанию пустой контекст
    }
    
    // с помощью функции get будет вызывать непосредственно рендеринг
    // так как рендерить необязательно twig шаблоны, а можно, например, всякий json
    // то метод сделаем абстрактным, ну типа кто наследуем BaseController
    // тот обязан переопределить этот метод
    abstract public function get();
}

Создаем еще один класс и назовем его TwigBaseController

<?php
require_once "BaseController.php"; // обязательно импортим BaseController

class TwigBaseController extends BaseController {
    public $title = ""; // название страницы
    public $template = ""; // шаблон страницы
    protected \Twig\Environment $twig; // ссылка на экземпляр twig, для рендернига
    
    // теперь пишем конструктор, 
    // передаем в него один параметр
    // собственно ссылка на экземпляр twig
    // это кстати Dependency Injection называется
    // это лучше чем создавать глобальный объект $twig 
    // и быстрее чем создавать персональный $twig обработчик для каждого класс 
    public function __construct($twig)
    {
        $this->twig = $twig; // пробрасываем его внутрь
    }
    
    // переопределяем функцию контекста
    public function getContext() : array
    {
        $context = parent::getContext(); // вызываем родительский метод
        $context['title'] = $this->title; // добавляем title в контекст

        return $context;
    }
    
    // функция гет, рендерит результат используя $template в качестве шаблона
    // и вызывает функцию getContext для формирования словаря контекста
    public function get() {
        echo $this->twig->render($this->template, $this->getContext());
    }
}

И вероятно, может возникнуть вопрос, а нафига столько кода, какие-то абстракции, и все такое?

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

Добавляем контроллер главной страницы

Создаем контроллер MainController под main страницу

и пишем в нем… а почти ничего и не пишем =О

<?php
require_once "TwigBaseController.php"; // импортим TwigBaseController

class MainController extends TwigBaseController {
    public $template = "main.twig";
    public $title = "Главная";
}

далее идем и правим в роутере:

<?php 
require_once "../vendor/autoload.php";
require_once "../controllers/MainController.php"; // добавим в самом верху ссылку на наш контроллер

// ...

$context = []; 

$controller = null; // создаем переменную под контроллер

if ($url == "/") {
    $controller = new MainController($twig); // создаем экземпляр контроллера для главной страницы
} elseif (preg_match("#^/andromeda#", $url)) {
    // пока не трогаем ...
} elseif (preg_match("#^/orion#", $url)) {
    // и это тоже ...
} 

/* УБИРАЕМ
$context['title'] = $title; 

echo $twig->render($template, $context);
*/

// проверяем если controller не пустой, то рендерим страницу
if ($controller) {
    $controller->get();
}

тестим:

работает так же, но в самом роутере, то есть внутри if-а всего одна строчка – создание экземпляра контроллера. Что можно считать своего рода достижением!)

Но мизерным, там ведь и раньше особо ничего не было…

Добавляем контроллер страницы Андромеды

Теперь попробуем создать контроллер AndromedaController под вывод инфы о андромеде.

Делаем по аналогии, по сути копипастим то, что было в роутере.

То есть указали title, указали шаблон, в getContext() указали специфичные для страницы ключи/значения

теперь идем обратно в index.php и там пишем:

<?php 
require_once "../vendor/autoload.php";
require_once "../controllers/MainController.php";
require_once "../controllers/AndromedaController.php"; // не забываем добавить импорт

// ...

$controller = null;

if ($url == "/") {
    $controller = new MainController($twig);
} elseif (preg_match("#^/andromeda#", $url)) {

    $controller = new AndromedaController($twig); // тут просто контроллер создаем
    
    // image и info пока не трогаем
    if (preg_match("#^/andromeda/image#", $url)) {
        // ...
    } elseif (preg_match("#^/andromeda/info#", $url)) {
        // ...
    }
} elseif (preg_match("#^/orion#", $url)) {
    // ...
} 

// ...

проверяем:

то есть работает так же! =О

Добавляем контроллер страницы изображения Андромеды

Сейчас самое интересное. У нас шаблон страницы Андромеды с картинкой, это по сути базовая страница с Андромедой, плюс дополнительные свойства.

Ну то есть в рамках twig у нас уже есть двухуровневая иерархия. Теперь такую же иерархию мы делаем на уровне контроллера, то есть создаем контроллер, который наследует уже не TwigBaseController, а AndromedaController. Оот так:

опять подправляем index.php

<?php 
require_once "../vendor/autoload.php";
require_once "../controllers/MainController.php";
require_once "../controllers/AndromedaController.php";
require_once "../controllers/AndromedaImageController.php"; // добавил

// ...

$controller = null;

if ($url == "/") {
    $controller = new MainController($twig);
} elseif (preg_match("#^/andromeda#", $url)) {

    $controller = new AndromedaController($twig);

    if (preg_match("#^/andromeda/image#", $url)) {
        $controller = new AndromedaImageController($twig); // теперь тут AndromedaImageController
    } elseif (preg_match("#^/andromeda/info#", $url)) {
        // ...
    }
} elseif (preg_match("#^/orion#", $url)) {
    // ...
} 

// ...

тестим:

работает! =)

Добавляем контроллер страницы информации об Андромеде

по аналогии делаем AndromedaInfoController

<?php
require_once "AndromedaController.php";

class AndromedaInfoController extends AndromedaController {
    public $template = "andromeda_info.twig";
    
    public function getContext(): array
    {
        $context = parent::getContext(); 

        // ...

        return $context;
    }
}

и подключаем в index.php:

<?php 
require_once "../vendor/autoload.php";
require_once "../controllers/MainController.php";
require_once "../controllers/AndromedaController.php";
require_once "../controllers/AndromedaImageController.php";
require_once "../controllers/AndromedaInfoController.php"; // добавил

// ...

$controller = null;

if ($url == "/") {
    $controller = new MainController($twig);
} elseif (preg_match("#^/andromeda#", $url)) {
    $controller = new AndromedaController($twig);
    if (preg_match("#^/andromeda/image#", $url)) {
        $controller = new AndromedaImageController($twig);
    } elseif (preg_match("#^/andromeda/info#", $url)) {
        $controller = new AndromedaInfoController($twig); // теперь и тут только AndromedaInfoController
    }
} elseif (preg_match("#^/orion#", $url)) {
   // ...
} 

// ...

И вот, если посмотреть, то вложенный if нам для андромеды теперь не нужен!

И мы вполне можем сделать структуру роутера плоской, по крайне мере для андромеды, вот так:

if ($url == "/") {
    $controller = new MainController($twig);
} elseif (preg_match("#^/andromeda/image#", $url)) { 
    $controller = new AndromedaImageController($twig);
} elseif (preg_match("#^/andromeda/info#", $url)) {
    $controller = new AndromedaInfoController($twig);
} elseif (preg_match("#^/andromeda#", $url)) {
    $controller = new AndromedaController($twig);
} elseif (preg_match("#^/orion#", $url)) {
    // ...
} 

Единственное, обратите внимание что я поменял порядок проверки на совпадение с url, так чтобы более точный шаблоны (с image или c info) шел раньше, чем более общий (типа #^/andromeda#)

Ну для второго объекта я думаю вы сами сделаете. Но прежде чем начнете,

Создаем 404 страницу

давайте еще сделаем 404 страницу, то есть это страница, когда ссылка на страницу не существует (ну в нашем случае если, например, контроллер не определен).

Создадим контроллер и называем его Controller404.php и пишем в него простой код:

<?php
require_once "TwigBaseController.php";

class Controller404 extends TwigBaseController {
    public $template = "404.twig"; 
    public $title = "Страница не найдена";
}

я там прописал шаблон 404.twig давайте его создадим в папке views:

и загоним в него что-то такое

{% extends "__layout.twig" %}

{% block content %}

<div class="text-center">
    <img src="/images/025_picture-47918.gif" alt="">
    <div>
        Страница не найдена! Шо делать!!!111 =О
    </div>
</div>

{% endblock %}

так, и подключим в index.php

<?php 
// ...
require_once "../controllers/AndromedaInfoController.php";
require_once "../controllers/Controller404.php"; // добавил

// ...
$context = []; 

$controller = new Controller404($twig); // теперь 404 будут нашем контроллером по умолчанию

if ($url == "/") {
// ...

так как для Ориона я еще контроллеры не делал, то по идее тыкнув на страницу Ориона, я как раз должен вызвать этот контроллер. Пробуем:

тут еще один момент, у нас 404 страница не совсем настоящая получается. И не потому что я не написал на ней 404, а потому что код ответа, который приходит вместе с этой страницей не 404, а 200.

Код ответа можно посмотреть следующим образом. Нажмите в браузере F12 или Ctrl+Shift+I найдите вкладку Network (или Сеть), и там найдите самый первый запрос:

вот то что в столбце Status – это и есть код возврата страницы. Чтобы сделать его настоящим 404, надо добавить в контроллер вызов специальной функции http_response_code:

<?php
require_once "TwigBaseController.php";

class Controller404 extends TwigBaseController {
    public $template = "404.twig";
    public $title = "Страница не найдена";

    public function get()
    {
        http_response_code(404); // с помощью http_response_code устанавливаем код возврата 404
        parent::get(); // вызываем базовый метод get(), который собственно уже отрендерит страницу
    }
}

запустим еще раз, и снова глянем что в столбце статус:

вот теперь другое дело!)

7

Доработать свое приложение, путем реализации контроллеров для всех страниц.