Реализация MVC - Контроллеры

 

Доброго всем времени суток. Сегодня мы продолжаем собирать ядро CMS RS-MINI. У нас по плану реализация схемы проектирования MVC, а точнее одной из составляющих этой схемы — контроллеры. Само собой сегодня мы не получим готовую реализацию контроллеров. В нашу задачу на сегодня это написать базу, которая будет наследоваться в будущем.

Что будет входить в базовый класса всех контроллеров в системе? Ну я бы поделил методы класса на два типа

  • обязательные
  • и не очень

В обязательные будут входить те методы, которые будут вызваться при сборке этих самых контроллеров (сборка будет происходить в классе application.class.php) в системе. Так же те, которые позволяют получить данные назначенные в этой самой сборке.

В категорию «не очень обязательных» я включаю те методы которые будут лишь помогать при написание контроллеров для модулей (эдакий инструментарий)

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

Обязательные методы

Давайте перечислю эти методы и зачем они нам понадобятся

  • __construct() — само собой понятно что без него никуда. Нам понадобится передать некоторые важные параметры которые мы получим до того как нам понадобится делать объект того или иного контроллера. В общем-то все эти параметры мы уже знаете откуда берутся. Если Вы пролистаете чуть ниже и посмотрите список параметров этого метода, то увидите там два объекта (dom и request) которые мы объявляли в приложении, и массив с параметрами контроллера, которые мы получим из базы данных
  • getCaption() — данный метод позволит получить имя страницы. Так как его можно будет переназначить в теле класса контроллера (полиморфизм), нам будет позволено вернуть совершенно любое имя страницы.
  • validate() — этот метод призван сделать некоторые проверки еще до того как начнется вся свистопляска. Важно сейчас запомнить, что этот метод (и предыдущий) будет отрабатываться на каждой странице (если конечно он к этой странице подключен). Поясню что значит на каждой. К примеру, если мы сейчас находимся на странице /about/contact/, то конечной будет считаться страница /contact/. А /about/ будет считаться промежуточной. Так вот метод validate() будет отрабатываться на всех страницах (и /about/ и /contact/)
  • run() — этот метод сердце контроллера. В нем находится вся необходимая логика. В отличие от методов перечисленных выше, этот метод будет отрабатываться только на конечной странице.
  • setDynamicValue() — этот метод позволяет напичкать специальное статичное свойство псевдонимами динамических страниц. Помните во время написания класса page.class.php мы говорили о том, что у динамических страниц есть псевдоним который жестко прописан в базе данных (нужно же как-то назвать эту страницу) и псевдоним который передан через адресную строку. Вот как этот метод будет сохранять эти данные, для того что бы мы могли с ними работать при написания контроллера.
  • getDynamicValue() — ну тут думаю без комментариев. Этот метод поможет нам получить переданный псевдоним по жестко вбитому псевдониму.
  • getParam() — это метод позволит получать параметры контроллера, которые будут переданы в копию объекта из приложения

Методы getCaption(), validate() и run() подвержены полиморфизму. То есть эти методы для использования необходимо будет переназначить в дочернем классе. Если метод validate() или run() вернет false, то система выведет сообщение о 404-ой ошибке. Как Вы могли заметить (если уже смотрели код класса) у метода run() по умолчанию возвращается false, что говорит о том, что этот метод обязательно должен будет переназначен в дочернем классе.

Не очень обязательные методы

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

К таким методам относятся:

  • redirect() — позволяет производить редирект. Очень нужен в контроллерах обработчиках, например перенести пользователя к его кооментарию после вставки этого комментария в базу данных.
  • getNowDate() — метод практически бесполезен, но мне надоело при создание дат публикаций статей использовать функцию date() так как часто путаю символ минут (так и хочется написать m, а нужно i)
  • changeDate() — этот метод просто позволяет перевести дату из формата ГГГГ-ММ-ДД ЧЧ:ММ:СС в формат ДД Месяц ГГГГ г. ЧЧ:ММ. Не универсальный метод, но мне его пока хватало.
  • getTranslit() — этот метод полезнее тех что описаны выше. Позволяет перевести строку с русскими буквами в на забугорные аналоги. Предназначен для создания псевдонимов для страниц.
  • getPhoneByFormat() — метод позволяет перевести телефон в более правильный формат. Используется не так часто, но нам при реализации интернет-магазина он пригодится.
  • getCurrent() — этот метод предназначен для того чтобы определить находимся ли мы внутри какого-нибудь раздела или нет. Очень полезен когда нам нужно подсветить пункт рубрики в том случае если мы находимся в этой рубрике.

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

Реализация класса abstractcontroller.class.php

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

А теперь собственно код класса. (Я его очень хорошо за комментировал, так что все что написано выше можно и не читать Улыбаюсь) (файл называется abstractcontroller.class.php и лежит в папке /rs-mini/core/)

<?php

/*
 * @package RS-MINI
 * @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
 * @license GNU General Public License version 2; see LICENSE.txt
 *
 */
 
namespace core;

if(!defined('RS-MINI')) die();

/*
 * abstractcontroller v 1.0
 *
 * абстрактный класс для контроллеров
 *
 */

abstract class abstractcontroller
{
    /*
     * объект дома
     *
     * @var    - object
     * @access - protected
     */
    
    protected $dom;
    
    /*
     * объект класса по работе с внешними глобальными переменнами
     *
     * @var    - object
     * @access - protected
     */
    
    protected $request;

    /*
     * массив хранящий значения динамичных страниц
     *
     * @var    - array
     * @access - private
     */
    
    private static $dynamicValue;

    /*
     * массив хранящий параметры контроллера
     *
     * @var    - array
     * @access - private
     */
    
    private $paramList;
    
    /*
     * контсруктор
     * обратите внимания, что объекты класса (dom и request) по умолчанию
     * передаются по ссылке что гарантирует нам использование одного и
     * того же экземпляра класса (по всему проекту будет существовать одна
     * копия объекта)
     *
     * @access - public
     *
     * @param object dom       - объект класса dom
     * @param object request   - объект класса request
     * @param array  paramList - массив с параметрами контроллера
     */
    
    public function __construct($dom, $request, $paramList = array())
    {
        $this->dom        = $dom;
        $this->request    = $request;
        $this->paramList  = $paramList;
    }

    /*
     * метод возвращает заголовок страницы
     *
     * @access - public
     *
     * @param string caption - системный заголовок
     */

    public function getCaption($caption)
    {
        return $caption;
    }

    /*
     * метод позволит проверить некоторые данные на валидацию.
     * запускается для каждого контроллера (за исключением отключенных)
     *
     * @access - public
     * @return - true или false (404 ошибка)
     *
     */

    public function validate()
    {
        return TRUE;
    }
   
    /*
     * запуск контроллера. основной метод контроллера.
     * если не создать подобный метод (полиморфизм) в дочернем классе
     * контроллер выдаст 404-ую ошибку
     *
     * @access - public
     * @return - true или false (404 ошибка)
     *
     */

    public function run()
    {
        return FALSE;
    }
    
    /*
     * метод запоминает значение динамичной страницы.
     * отрабатывает при сборке контроллеров в приложении
     * системы. необходим, что бы псевдоним динамической страницы
     * можно было получить из любого подключенного контроллера
     *
     * @access - public
     * @return - значение дин страницы
     *
     * @param string name - имя параметра
     */

    public function setDynamicValue($name, $value)
    {
        self::$dynamicValue[$name] = $value;
    }

    /*
     * метод получает значение динамичной страницы по имени
     *
     * @access - protected
     * @return - значение дин страницы
     *
     * @param string name - имя параметра
     */

    protected function getDynamicValue($name)
    {
        return (isset(self::$dynamicValue[$name])) ? self::$dynamicValue[$name] : NULL;
    }

    /*
     * метод получает значение параметра по имени.
     * если значение содержит запятые, то оно (значение) вернется не
     * в виде строки, а в виде массива
     *
     * @access - protected
     * @return - значение параметра
     *
     * @param string name - имя параметра
     */

    protected function getParam($name)
    {
        if (!isset($this->paramList[$name]))
            return NULL;
            
        if (!preg_match('/[\,]+/', $this->paramList[$name]))
            return $this->paramList[$name];
        
        $params = explode(',', $this->paramList[$name]);
        
        foreach ($params as $key => $param)
            $params[$key] = trim($param);
            
        return $params;
    }
   
    /*
     * метод редиректит к опрделенному адресу
     *
     * @access - protected
     *
     * @param string  link - адрес редиректа
     */
   
    protected function redirect($link)
    {
        header('location: ' . $link);
        die();
    }

    /*
     * метод получает сейчашнию дату в формате ГГГГ-ММ-ДД ЧЧ:ММ:СС
     *
     * @access - protected
     *
     */

    protected function getNowDate()
    {
        return date('Y-m-d H:i:s');
    }
    
    /*
     * метод конвертирует дату из формата ГГГГ-ММ-ДД ЧЧ:ММ:СС в формат ДД Месяц ГГГГ г. ЧЧ:ММ
     *
     * @access - protected
     *
     * @param string  cdate  - дата и время в формате ГГГГ-ММ-ДД ЧЧ:ММ:СС (или ГГГГ-ММ-ДД)
     */
    
    protected function changeDate($cdate)
    {
        // массив с месяцами по русски
        $arrayMonth = array (
            '01'    => 'Января',
            '02'    => 'Февраля',
            '03'    => 'Марта',
            '04'    => 'Апреля',
            '05'    => 'Мая',
            '06'    => 'Июня',
            '07'    => 'Июля',
            '08'    => 'Августа',
            '09'    => 'Сентября',
            '10'    => 'Октября',
            '11'    => 'Ноября',
            '12'    => 'Декабря'
        );
        
        // если прислано значение ГГГГ-ММ-ДД ЧЧ:ММ:СС
        if ($this->validateDateTime($cdate)) {
            // бьем ГГГГ-ММ-ДД ЧЧ:ММ:СС на ГГГГ-ММ-ДД и ЧЧ:ММ:СС
            $dateArray = explode(" ", $cdate);
            // бьем ГГГГ-ММ-ДД на ГГГГ, ММ и ДД
            $dateArray = explode("-", $dateArray[0]);
            
            // бьем ГГГГ-ММ-ДД ЧЧ:ММ:СС на ГГГГ-ММ-ДД и ЧЧ:ММ:СС
            $timeArray = explode(" ", $cdate);
            // бьем ЧЧ:ММ:СС на ЧЧ, ММ и СС
            $timeArray = explode(":", $timeArray[1]);
        }
        // если прислано значение ГГГГ-ММ-ДД
        else
            // бьем ГГГГ-ММ-ДД на ГГГГ, ММ и ДД
            $dateArray = explode("-", $cdate);
        
        // формируем строку ДД Месяц ГГГГ г. ЧЧ:ММ или ДД Месяц ГГГГ г.
        return ""
            . $dateArray[2]
            . " "
            . $arrayMonth[$dateArray[1]]
            . " "
            . $dateArray[0]
            . " г."
            . ( ($this->validateDateTime($cdate)) ? " " . $timeArray[0] . ":" . $timeArray[1] : "")
        ;
    }
    
    /*
     * метод переводит кириллицу в латинец
     *
     * @access - protected
     *
     * @param string string - строка для изменения
     */
    
    protected function getTranslit($string)
    {   
        // массив с символами которые нужно заменить
        $what = array(
            "а","А","б","Б","в","В","г","Г","д","Д","е","Е","ё","Ё","ж","Ж","з","З",
            "и","И","й","Й","к","К","л","Л","м","М","н","Н","о","О","п","П","р","Р",
            "с","С","т","Т","у","У","ф","Ф","х","Х","ц","Ц","ч","Ч","ш","Ш","щ","Щ",
            "ы","Ы","э","Э","ю","Ю","я","Я","a","A","b","B","c","C","d","D","e","E",
            "f","F","g","G","h","H","i","I","j","J","k","K","l","L","m","M","n","N",
            "o","O","p","P","q","Q","r","R","s","S","t","T","u","U","v","V","w","W",
            "x","X","y","Y","z","Z","1","2","3","4","5","6","7","8","9","0","-"," ",
            ".","_"
        );
       
        // массив с символами на которые нужно заменить
        $forWhat = array(
            "a","a","b","b","v","v","g","g","d","d","e","e","jo","jo","zh","zh","z","z",
            "i","i","j","j","k","k","l","l","m","m","n","n","o","o","p","p","r","r",
            "s","s","t","t","u","u","f","f","h","h","c","c","ch","ch","sh","sh","shh","shh",
            "y","y","je","je","ju","ju","ja","ja","a","a","b","b","c","c","d","d","e","e",
            "f","f","g","g","h","h","i","i","j","j","k","k","l","l","m","m","n","n",
            "o","o","p","p","q","q","r","r","s","s","t","t","u","u","v","v","w","w",
            "x","x","y","y","z","z","1","2","3","4","5","6","7","8","9","0","-","-",
            "-","-"
        );
        
        // подгатавливаем паттерн из допустимых символов (массив $what)
        $pattern = '/[^' . preg_quote(implode('', $what)) . ']/su';
        
        // заменяем найденые в массиве $what символы в строке string и заменяем на символы из $forWhat
        // при этом строка string чистится от всех символов кроме букв (за исключением знаков ь и ъ),
        // цифр, точек, тире, нижних подчеркиваний и пробелов (пробелы по краям строки string тоже удаляются)
        return str_replace($what, $forWhat, preg_replace($pattern, '', trim($string)));
    }
    
    /*
     * метод получает телефон в междунородном формате
     *
     * @access - protected
     * @return - телефон в формате 8 (123) 456-78-90
     *
     * @param string phone - телефон
     */
    
    protected function getPhoneByFormat($phone)
    {
        // чистим от всего что НЕ число
        $phone = preg_replace('/[^0-9]/is', '', $phone);
        // если первая цифра 8 или 7, то удаляем ее
        $phone = preg_replace('/^(8|7)/is', '', $phone);
        
        // формируем телефон в формате 8 (123) 456-78-90
        $phone = ''
            . '8'
            . ' ('
            . $phone[0]
            . $phone[1]
            . $phone[2]
            . ') '
            . $phone[3]
            . $phone[4]
            . $phone[5]
            . '-'
            . $phone[6]
            . $phone[7]
            . '-'
            . $phone[8]
            . $phone[9]
        ;
        
        return $phone;
    }
    
    /*
     * метод по урлам определяет выбранный пункт
     *
     * @access - protected
     * @return - true или false
     *
     * @param string uri - путь до документа
     */
     
    protected function getCurrent($uri)
    {
        // получаем урл страницы
        $openUri  = $this->request->getHttpServer('REQUEST_URI')->toUri();

        // получаем массив из присланного урла
        $openUri = explode('/', $openUri);          // бьем строку на массив

        if (empty($openUri[(count($openUri) - 1)])) // если последний элемент массива пуст, удаляем его
            unset($openUri[(count($openUri) - 1)]);

        foreach ($openUri as $key => $alias)        // чистим алиасы от пробелов
            $openUri[$key] = trim($alias);

        // обрабатываем присланный урл
        $uri = explode('/', $uri);                  // бьем строку на массив

        if (empty($uri[(count($uri) - 1)]))         // если последний элемент массива пуст, удаляем его
            unset($uri[(count($uri) - 1)]);

        foreach ($uri as $key => $alias)            // чистим алиасы от пробелов
            $uri[$key] = trim($alias);

        $current = TRUE;
        
        // листаем псевдонимы присланного пути
        // пример. предположим мы находимся вот по такому пути /about/contact/ (переменная openUri)
        // а для проверки прислали вот такой путь /about/ (переменная uri). на глаз можно сказать,
        // что мы находимся разделе /about/, т.е. результат метода должен быть TRUE
        foreach ($uri as $key => $alias)
            // при первой итерации в переменной alias будет значение about
            // а в массиве $openUri[0] (ноль потому-что первая итерации, key равен нулю)
            // тоже значение about. стало быть условие не выполнится и в переменной current
            // так и останится с значением TRUE
            // второй итерации не будет, так как в переменной uri был только один элемент массива
            // в результате чего метод вернет TRUE как и предпологалось
            if (!isset($openUri[$key]) || $openUri[$key] != $alias)
                $current = FALSE;
                
        return $current;
    }
}

Заключение

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

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

Всего Вам наилучшего, на сегодня все!

Прикрепленные файлы

 

Возможно Вам будут интересны следующие заметки

Комментарии (1)

Ваше имя *
Сайт
Ваш E-mail *
Ваше сообщение *
 
Артем, 26 Июля 2015 г. 14:00 пишет:
Читатель
getTranslit в контроллере? Серьезно?)
Я очень рекомендую автору взглянуть на любой фреймворк (Kohana, Symfony 2)
Есть такие классы как Request, Response, Controller - логика распределена.
Есть хелперы (специально для чего-то вроде getTranslit..)

Удачи)