Базовый контроллер и его методы

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

Зачем это нужно? Да, хотя бы, затем, чтобы не прописывать одни и те же методы в каждом контроллере. Написать один общий, а потом менять только нужные части. Это гораздо правильнее и удобнее.

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

Итак, приступаем.

Базовый контроллер

Создаём новый файл Controller.php рядом c Core.php:


<?php

class Controller {
    /** @var Core $core */
    public $core;


    /**
     * Конструктор класса, требует передачи Core
     *
     * @param Core $core
     */
    function __construct(Core $core) {
        $this->core = $core;
    }


    /**
     * Основной рабочий метод
     *
     * @return string
     */
    public function run() {
        return "Hello World!";
    }

}

Как видите, это пока точная копия контроллеров из прошлого занятия. Фокус в том, что им теперь эти методы не нужны. Переписываем их так:


<?php

if (!class_exists('Controller')) {
    require_once dirname(dirname(__FILE__)) . '/Controller.php';
}

class Controllers_Test extends Controller {

    /**
     * Основной рабочий метод
     *
     * @return string
     */
    public function run() {
        return "Мы выводим страницу <b>Test<b>";
    }

}

Вначале идёт проверка наличия класса базового контроллера и, если его нет, подключение из директории выше. Дальше мы объявляем свой класс с добавлением специальной конструкции extends, что говорит PHP о расширении Controller. И с этого момента наш рабочий контроллер наследует базовый.

Так что мы убираем у него свойство $core и метод __construct(), потому что они уже прописаны в родителе. Однако оставляем метод run(), потому что именно им и будут отличаться наследники Controller.

Логика понятна? Есть родитель и есть потомки, которые обладают всеми качествами родителя и могут любые из них менять. Конечно, если только те не объявлены как private.

Код сейчас выглядит вот так. Теперь мы можем добавить какие-то общие методы в Controller.php.

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

Во-первых, нам нужен метод инициализации, чтобы определять, будет ли вообще работать наш контроллер при переданных параметрах. Например, он будет проверять запрошенный URL, и есть тот не заканчивается на / - делать редирект на верный адрес.

Да, редирект это наше во-вторых. Меняем Controller.php


    public function initialize(array $params = array()) {

        return true;
    }

    public function redirect($url = '/') {
        header("Location: {$url}");
        exit();
    }

Просто пока голые методы. Теперь запуск initialize() у каждого контроллера нужно прописать в Core::handleRequest():


        // Имена контроллеров у нас с большой буквы
        $name = ucfirst(array_shift($request));
        // ... 
        // И запускаем
        $controller = new $class($this);
        $initialize = $controller->initialize($request);
        if ($initialize === true) {
            $response = $controller->run();
        }
        elseif (is_string($initialize)) {
            $response = $initialize;
        }
        else {
            $response = 'Возникла неведомая ошибка при загрузке страницы';
        }

        echo $response;

Видите? У каждого контроллера будет запущен, в первую очередь, метод initialize() и в зависимости от того, что он вернёт, дальше или запускается run(), или выводится ошибка. Причем, контроллер может сам вернуть текст ошибки.

Ну а теперь нам осталось только расширить initialize в методе Controllers_Test:


    public function initialize(array $params = array()) {
        if (empty($params)) {
            $this->redirect('/test/');
        }
        return true;
    }

Мы проверяем массив $params, который нам передаёт Core. Причем, выше я немного изменил его обработку в handleRequest(), чтобы первая часть url откусывалась методом array_pop() от начала массива.

Если URL был передан с косой на конце, то после explode('/', $_REQUEST) у нас получится массив ["имя страницы", ""] - второй параметр пуст. Затем мы откусываем первый параметр и выходит массив с один пустым значением: [""].

Если ни одного значения в массиве нет (даже пустого) - то он был передан без завершающей косой, и мы делаем в таком случае редирект на верный адрес через $this->redirect().

Если же всё ок, то мы возвращаем true, handleRequest продолжает работу, запускает run() и там выводится наша фраза.

Проверяем - http://s1889.bez.modhost.pro/test.

А вот в Controllers_Home мы сделаем другую проверку. Нам не нужно, чтобы страница открывалась как /home/ - ведь это же корень сайта. Так что, проверяем переменную $_REQUEST['q'] и если она не пуста, то делаем редирект в корень сайта.


    public function initialize(array $params = array()) {
        if (!empty($_REQUEST['q'])) {
            $this->redirect('/');
        }
        return true;
    }

Теперь любой левый адрес на сайте, для которого не нашлось контроллера будет отфутболен на Home, а тот проверит, по какому адресу он был открыт. И если это не корень сайта - то редирект.

Выходит, что при открытии несуществующей страницы вас отредиректит в корень сайта. Неплохо, правда?

Проверяем - http://s1889.bez.modhost.pro/wrong\_page/

Заключение

Вот мы и познакомились с наследованием и расширением PHP классов. У нас есть базовый контроллер, который содержит базовые методы для расширения и использования в дочерних.

Заодно у нас как-то сама собой написалась логика редиректа в корень сайта при запросе несуществующей страницы.

На данный момент код нашего "движка" выглядит вот так.

Думаю, на следующем уроке мы уже начнём выводить какой-то стандартный HTML на наших страницах.

← Предыдущая заметка
Основы ООП и контроллеры страниц
Следующая заметка →
Подключаем шаблонизатор Fenom
Комментарии (0)
bezumkin
Василий Наумкин
04.07.2022 23:34
Что-то странное у тебя произошло: миграция есть, и вроде как выполнена, но таблицы при этом отсутств...
inetlover
Александр Наумов
03.07.2022 20:36
Василий, спасибо! Все понятно!
bezumkin
Василий Наумкин
02.07.2022 20:28
Спасибо, поправил!
bezumkin
Василий Наумкин
30.06.2022 03:58
Есть ли возможность формировать &quot;friendly URL aliases&quot;, используя аналог translit MODx? ...
bezumkin
Василий Наумкин
27.06.2022 03:32
Спасибо за исправления, очень выручаешь =) Но учитывая количество не описаных в заметке дополнительн...
bezumkin
Василий Наумкин
27.06.2022 03:10
что будет использоваться для вывода многоуровневого меню Посмотри как работают комментарии на этом ...
bezumkin
Василий Наумкин
25.06.2022 11:56
Поправил, спасибо!
bezumkin
Василий Наумкин
21.06.2022 01:58
onLoad(data) { this.total = data.total }, и onLoad({total}) { this.total = total }, В нашем случ...
bezumkin
Василий Наумкин
20.06.2022 14:01
Прекрасно тебя понимаю, я когда сам в этом разбирался - голова дымилась. Но зато теперь прямо-таки п...
bezumkin
Василий Наумкин
20.06.2022 09:30
Не надо, оно по умолчанию так - я просто чуть более подробно написал.