Реализация прослойки между контроллером и шаблоном

 

Всем доброго времени суток. Сегодня мы с Вами попробуем реализовать один из самых важных классов в системе RS-MINI. Данная прослойка между контроллерами и представлением будет, по своей идеи, схожа с XML деревом. Но, как я уже писал ранее, это не XML. Зачем эта прослойка нужна вообще? А как Вы себе представляете передачу информации из контроллера в представление? Она должна где-то храниться прежде чем представление начнет свою работу.

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

По подобной схеме работает и RS-SITE на котором сейчас работает блог который Вы читаете. Для удобства во время разработки (а именно для того что бы не на память разбирать дом для вывода информации в шаблоне) используется визуальное отображение содержимого дома. Я сейчас Вам покажу пример хранения информации перед тем как она попадает в шаблон. Вот скрин уже собранного дома главной странице моего блога

пример дом дерево

Вы видите два корневых узла.

  • textList
  • meta

В первом узле есть дочерний узел text. Он содержит атрибуты document_id, document_created и т. д.

Во-втором корневом узле нет вложенного дочернего, но имеются атрибуты title, keywords, description и name.

Как Вы могли заметить первый дочерний узел первого корневого узла это приветствующий текст на главной странице. А второй корневой узел — мета теги страницы.

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

Пишем класс dom.class.php

Давайте для начала определимся с тем как мы будем пользоваться данным классом. По задумке он будет уметь:

  • добавлять новый узел
  • добавлять атрибут в созданный узел
  • получать один узел
  • получать атрибуты выбранного узла
  • получать набор дочерних узлов выбранного узла
  • получать кол-во дочерних узлов выбранного узла.
  • получать все узлы вложенные в выбранный узел (и дочерние и дочерние дочерних. Это как раз для реализации визуального отображения дома)

Давайте поразмышляем как будут юзаться данные возможности. Например создание нового узла должно происходить вот так:

$dom  = new dom();
$node = $dom->addNode('newNode');

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

Далее добавление атрибута. Предлагаю делать это вот так:

$dom  = new dom();
$node = $dom->addNode('newNode');

$node->addAttr('name', 'имя');

Тут все просто. В только что созданный узел можно будет с помощью метода addAttr() назначить имя атрибута и значение.

Далее получение узла. По моей задумке производится будет вот так:

// предоположим в переменной dom уже есть узел newNode
$node = $dom->getNode('newNode');

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

Далее получение атрибутов. Предлагаю делать это вот так:

// предоположим в переменной dom уже есть узел newNode. и в нем есть атрибут name
$node = $dom->getNode('newNode');
$attr = $node->getAttr('name');

просто указываем имя атрибута узла и метод getAttr() вытащит нам значение этого узла.

Как Вы уже поняли, в случаях когда у нас есть несколько дочерних узлов с одним именем (например узел пост на блоге. Постов ведь больше чем один) метод getNode() нам не поможет. Именно поэтому в классе должен быть метод получающий весь набор (вертать будет массив) нужных дочерних узлов. Вот так мы будем их получать:

// предоположим в переменной dom уже есть несколько узлов с именем newNode
$nodeList = $dom->getChildren('newNode');

метод getChildren() будет возвращать массив с узлами чье имя равно параметру переданному в метод.

Предпоследняя возможность это получение кол-во дочерних узлов. В основном метод необходим для того что бы заранее знать можно ли юзать метод getChildren(). Предлагаю кол-во получать вот так:

// предоположим в переменной dom уже есть несколько узлов с именем newNode
if ($dom->count('newNode'))
    $nodeList = $dom->getChildren('newNode');

Ну и последнее это получение всех дочерних узлов выбранного узла. И под всеми я имею ввиду и дочерние узлы и дочерние узлы дочерних узлов. То есть тут необходима рекурсия. Этот метод понадобится нам только один раз, но без него будет очень туго. Предлагаю особо не заморачиваться и получать список узлов вот так:

// предоположим в переменной dom лежит уже собранный дом
$domArr = $dom->getDom();

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

  • attr — массив с атрибутами узла
  • node — массив с списком дочерних узлов
  • name — имя узла

Базовая структура класса

Для начала создаю необходимые свойства класса и накидываю весь список необходимых методов (класс я назвал dom.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();


/*
 * dom v 1.0
 *
 * Класс хранит данные для передачи их представлению
 *
 */

class dom
{   
    
    /*
     * массив с атрибутами узла
     *
     * @var    - array
     * @access - public
     */
    
    public $attr = array();
    
    /*
     * массив с узлами
     *
     * @var    - array
     * @access - public
     */
    
    public $node = array();
    
    /*
     * имя узла
     *
     * @var    - string
     * @access - public
     */
    
    public $name = NULL;
    
    /*
     * добавляем новый узел. если в качестве параметра был передан объект dom то
     * в массив $this->node запишится этот объект. если в качестве параемтера передено строка (т.е. имя нового узла)
     * то на основе этого имени поднимется новая копия объекта класса dom
     *
     * @access - public
     * @return - возвращает созданный узел
     *
     * @param string/object $data - данные содержащие имя нового узла или объект дома
     */

    public function addNode($data)
    {

    }

    /*
     * прописываем новый атрибут в текущий объект класса dom
     *
     * @access - public
     * @return - сам себя
     *
     * @param string name  - имя атрибута
     * @param string value - значение атрибута
     */

    public function addAttr($name, $value)
    {

    }
    
    /*
     * получаем узел (объект) по имени из массива $this->node.
     *
     * @access - public
     * @return - узел или FALSE
     *
     * @param string name - имя узла
     */
     
    public function getNode($name)
    {

    }

    /*
     * получаем значение атрибута. Узел выберается с помощью метода getNode() или getChildren()
     *
     * @access - public
     * @return - значение атрибута или FALSE в случае неудачи
     *
     * @param string name - имя атрибута
     */
    
    public function getAttr($name)
    {
        
    }

    /*
     *  получаем весь дом целиком в виде массива
     *
     * @access - public
     * @return - массив с всеми узлами и их атрибутами
     *
     */

    public function getDom()
    {

    }
    
    /*
     * рекурсивно собираем узлы (вспомогательный метод метода getDom()) вертая массив вот такой структуры
     * [0]
     *     [name] => имя,
     *     [attr] => массив атрибутов,
     *     [node] =>
     *            [0]
     *                [name] => имя,
     *                [attr] => массив атрибутов,
     *                [node] => array()
     *            [1]
     *                [name] => имя,
     *                [attr] => массив атрибутов,
     *                [node] =>
     *                       [0]
     *                           [name] => имя,
     *                           [attr] => массив атрибутов,
     *                           [node] => array()
     *
     * @access  - private
     * @return  - массив с узлом
     *
     * @param array nodeList - список узлов (объектов dom)
     */
    
    private function collectNode($nodeList)
    {

    }

    /*
     * получаем массив с вложенными узлами (объекты) из текущего узла
     *
     * @access - public
     * @return - массив с узлами или FALSE
     *
     * @param string name - имя узла
     */
    
    public function getChildren($name)
    {

    }

    /*
     * проверяем есть ли узлы в выбранном узле
     *
     * @access - public
     * @return - число узлов если они есть, или FALSE если нет
     *
     * @param string name - имя узла
     */
     
    public function count($name)
    {

    }
    
    /*
     * метод устанавливает имя узла
     *
     * @access - private
     * @return - сам себя
     *
     * @param string name - имя узла
     */
    
    private function setName($name)
    {

    }
}

Теперь нужно написать самые простые методы. А это

  • addNode()
  • addAttr()
  • getAttr()
  • setName() — про этот метод мы не говорили, но нам же нужно как-то записывать имя узла

Метод addNode()

    /*
     * добавляем новый узел. если в качестве параметра был передан объект dom то
     * в массив $this->node запишится этот объект. если в качестве параемтера передено строка (т.е. имя нового узла)
     * то на основе этого имени поднимется новая копия объекта класса dom
     *
     * @access - public
     * @return - возвращает созданный узел
     *
     * @param string/object $data - данные содержащие имя нового узла или объект дома
     */

    public function addNode($data)
    {
        $node           = (!is_object($data)) ? new dom()             : $data;
        $this->node[]   = (!is_object($data)) ? $node->setName($data) : $node;
        
        return $node;
    }

как видите я ориентируясь на то что в параметре data,

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

Метод addAttr()

    /*
     * прописываем новый атрибут в текущий объект класса dom
     *
     * @access - public
     * @return - сам себя
     *
     * @param string name  - имя атрибута
     * @param string value - значение атрибута
     */

    public function addAttr($name, $value)
    {
        $this->attr[$name] = $value;

        return $this;
    }

тут тоже все просто. Именем атрибута является ключ массива а значением — значение элемента массива.

Метод getAttr()

    /*
     * получаем значение атрибута. Узел выберается с помощью метода getNode() или getChildren()
     *
     * @access - public
     * @return - значение атрибута или FALSE в случае неудачи
     *
     * @param string name - имя атрибута
     */
    
    public function getAttr($name)
    {
        return (isset($this->attr[$name])) ? $this->attr[$name] : FALSE;
    }

просто смотрим существует ли элемент массива $name и если есть возвращаем его значение. В противном случае вернем false.

Метод setName()

    /*
     * метод устанавливает имя узла
     *
     * @access - private
     * @return - сам себя
     *
     * @param string name - имя узла
     */
    
    private function setName($name)
    {
        $this->name = $name;
        
        return $this;
    }

Приватный метод позволяющий запомнить имя узла. Если Вы были внимательны то заметили, что он используется в методе addNode()

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

Метод getNode()

    /*
     * получаем узел (объект) по имени из массива $this->node.
     *
     * @access - public
     * @return - узел или FALSE
     *
     * @param string name - имя узла
     */
     
    public function getNode($name)
    {
        // листаем дочернии узлы
        foreach ($this->node as $node)
            // если имя из объекта совпадает с искомым именем
            if ($node->name == $name)
                return $node;
                
        // по умолчанию метод возвращает фалс
        return FALSE;
    }

В общем-то тут нет ничего сложного. Мы листаем дочерние узлы, до тех пор пока имя переданное через параметр не совпадет с именем дочернего узла. Как только это произошло, возвращаем объект данной итерации.

Если же поиск не даст результата, то метод вернет false

Метод getChildren()

    /*
     * получаем массив с вложенными узлами (объекты) из текущего узла
     *
     * @access - public
     * @return - массив с узлами или FALSE
     *
     * @param string name - имя узла
     */
    
    public function getChildren($name)
    {
        // массив с выбранными узлами
        $nodeList = array();
        
        // листаем дочернии узлы
        foreach ($this->node as $node)
            // если имя из объекта совпадает с искомым именем
            if ($node->name == $name)
                // заполняем выборку узлом
                $nodeList[] = $node;
        
        // если небыло выбрано ни одного узла, то фалсе. в противном случае список узлов
        return (empty($nodeList)) ? FALSE : $nodeList;
    }

Тут как и в случае с методом getNode(). Листаем дочерние узлы, но не ретурним сразу как увидели совпадение имен, а сохраняем в отдельный массив $nodeList. После чего метод возвращает этот массив или false если массив пустой.

Метод count()

    /*
     * проверяем есть ли узлы в выбранном узле
     *
     * @access - public
     * @return - число узлов если они есть, или FALSE если нет
     *
     * @param string name - имя узла
     */
     
    public function count($name)
    {
        // массив с выбранными узлами
        $nodeList = array();
        
        // листаем дочернии узлы
        foreach ($this->node as $node)
            // если имя из объекта совпадает с искомым именем
            if ($node->name == $name)
                // заполняем выборку узлом
                $nodeList[] = $node;
        
        // если небыло выбрано ни одного узла, то фалсе. в противном случае кол-во узлов
        return (empty($nodeList)) ? FALSE : count($nodeList);
    }

В общем-то это копия метода getChildren() за исключением того что данный метод вертает не массив в случае успеха, а кол-во элементов в массиве.

Ну и на по следок самые сложные методы.

Метод getDom() и collectNode()

Сложность заключается в том, что в методе collectNode() используется рекурсия, а она очень часто связываем мои извилины в узлы. Вот код этих двух методов:

    /*
     *  получаем весь дом целиком в виде массива
     *
     * @access - public
     * @return - массив с всеми узлами и их атрибутами
     *
     */

    public function getDom()
    {
        return $this->collectNode($this->node);
    }
    
    /*
     * рекурсивно собираем узлы (вспомогательный метод метода getDom()) вертая массив вот такой структуры
     * [0]
     *     [name] => имя,
     *     [attr] => массив атрибутов,
     *     [node] =>
     *            [0]
     *                [name] => имя,
     *                [attr] => массив атрибутов,
     *                [node] => array()
     *            [1]
     *                [name] => имя,
     *                [attr] => массив атрибутов,
     *                [node] =>
     *                       [0]
     *                           [name] => имя,
     *                           [attr] => массив атрибутов,
     *                           [node] => array()
     *
     * @access  - private
     * @return  - массив с узлом
     *
     * @param array nodeList - список узлов (объектов dom)
     */
    
    private function collectNode($nodeList)
    {
        $nodes = array();
        
        foreach ($nodeList as $key => $node)            
            $nodes[] = array(
                'name'  => $node->name,
                'attr'  => $node->attr,
                'node'  => (count($node->node) > 0) ? $this->collectNode($node->node) : array()
            );
        
        return $nodes;      
    }

Мы будем вызывать метод getDom() он в свою очередь вызовет метод collectNode(). В теле метода collectNode() собирается массив, структуру которого я нарисовал в комментарии. Смысл в том, что если у узла имеются дочерние узлы, то в элемент node массива $nodes, будет записан результат того же самого метода collectNode(). А результатом метода collectNode() является массив $nodes. Тем самым мы рекурсивно соберем дерево.

Подключаем класс к приложению

Давайте подключим написанный класс к приложению RS-MINI и попробуем его немного потестировать. Вот код класса application.class.php:

<?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();

/*
 * application v 1.0
 *
 * Класс стартует систему
 *
 */

class application
{

    /*
     * объект дома
     *
     * @var         - object
     * @access      - private
     */
    
    private static $dom;

    /*
     * главный метод. запуск системы
     *
     * @access  - public
     *
     */
     
    public static function run()
    {
        // объект прослойка между контроллерами и вьюшкой
        self::$dom = new \core\dom();
    }
}

Я создал свойство dom и развернул в нем объект класса dom. Свойство dom является статичным свойством. Поэтому обращению к нему, в теле метода run(), происходит через self:: а не через $this->.

Заключение

Давайте в теле метода run() напишем небольшой тест. Попробуем создать узел testNode в который положим дочерний узел subTestNode с атрибутом name. Вот код:

    /*
     * главный метод. запуск системы
     *
     * @access  - public
     *
     */
     
    public static function run()
    {
        // объект прослойка между контроллерами и вьюшкой
        self::$dom = new \core\dom();
        
        // тест
        // создаем узел testNode
        $testNode    = self::$dom->addNode('testNode');
        // в узле testNode создаем узел subTestNode
        $subTestNode = $testNode->addNode('subTestNode');
        // в узле subTestNode создаем атрибут name с значением "значение атрибута name"
        $subTestNode->addAttr('name', 'значение атрибута name');
        
        // получаем весь дом целиком
        ?><xmp><?
        var_dump(self::$dom->getDom());
        ?></xmp><?
    }

Результат я вывел с помощью метода getDom() (т. е. показать все дерево). Вот что получилось

результат теста класса dom.class.php

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

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

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

 

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

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

Ваше имя *
Сайт
Ваш E-mail *
Ваше сообщение *
 
jimi333, 26 Марта 2017 г. 15:40 пишет:
Читатель
Приветствую, подскажите, если я вместо перезаписывания функции count, создал свою, например countNode, то как мне к ней обратится из функции collectNode? $this->countNode($node->node) обращается , но считает некорректно.
Алексей, 26 Марта 2017 г. 15:48 пишет:
Автор
Доброго времени суток.
Вы из вот такой строчки:
'node' => (count($node->node) > 0) ? $this->collectNode($node->node) : array()
хотите сделать вот такую:
'node' => ($this->countNode($node->node) > 0) ? $this->collectNode($node->node) : array()
Ответ для пользователя: jimi333
jimi333, 26 Марта 2017 г. 15:53 пишет:
Читатель
Верно, но не получается
jimi333, 26 Марта 2017 г. 15:54 пишет:
Читатель
Вернее так, я хочу написать свою функцию подсчета, а не перезаписывать встроенную
Алексей, 26 Марта 2017 г. 16:35 пишет:
Автор
я хочу написать свою функцию подсчета, а не перезаписывать встроенную
Тут нет перезаписи встроенной функции. Если Вы про это:
public function count($name)
то это не функция, а метод класса dom. Этот метод не перезаписывается встроенную в php функцию count()
Ответ для пользователя: jimi333
jimi333, 26 Марта 2017 г. 16:56 пишет:
Читатель
А почему тогда если переименовать count, в любую другую, то выдает ошибку Call to undefined function
Ответ для пользователя: Алексей
Алексей, 26 Марта 2017 г. 17:17 пишет:
Автор
Если вместо
'node' => (count($node->node) > 0) ? $this->collectNode($node->node) : array()
написать
'node' => (что_то_другое($node->node) > 0) ? $this->collectNode($node->node) : array()
то само собой Вы получите ошибку, ведь функции что_то_другое() не существует (если конечно Вы сами ее не напишите)
Ответ для пользователя: jimi333
jimi333, 26 Марта 2017 г. 18:11 пишет:
Читатель
Так дело в том, что я переименовал функцию
public function count($name)
в
    public function countNode($name)
и соответственно вызываю
'node' => (countNode($node->node) > 0) ? $this->collectNode($node->node) : array()
и возникает ошибка
Ответ для пользователя: Алексей
Алексей, 26 Марта 2017 г. 18:31 пишет:
Автор
Так дело в том, что я переименовал функцию
это не функция это метод класса dom.

Когда я писал вот такую строчку:
'node' => (count($node->node) > 0) ? $this->collectNode($node->node) : array()
я использовал функцию count(), а не метод count() класса dom.
Ответ для пользователя: jimi333
jimi333, 26 Марта 2017 г. 18:54 пишет:
Читатель
ааа всё понял, спасибо
Ответ для пользователя: Алексей