Всем доброго времени суток. Сегодня мы с Вами попробуем реализовать один из самых важных классов в системе RS-MINI. Данная прослойка между контроллерами и представлением будет, по своей идеи, схожа с XML деревом. Но, как я уже писал ранее, это не XML. Зачем эта прослойка нужна вообще? А как Вы себе представляете передачу информации из контроллера в представление? Она должна где-то храниться прежде чем представление начнет свою работу.
Идея контроллера, это в зависимости от ситуации получить нужный набор данных (в этом ему помогает модель) и засунуть полученную информацию в прослойку (далее по тексту дом). Когда дело дойдет до представлений (то есть шаблонов) у нас на руках будет объект класса в котором будет лежать вся нужная информация для отображения ее конечному пользователю.
По подобной схеме работает и RS-SITE на котором сейчас работает блог который Вы читаете. Для удобства во время разработки (а именно для того что бы не на память разбирать дом для вывода информации в шаблоне) используется визуальное отображение содержимого дома. Я сейчас Вам покажу пример хранения информации перед тем как она попадает в шаблон. Вот скрин уже собранного дома главной странице моего блога
Вы видите два корневых узла.
В первом узле есть дочерний узел text. Он содержит атрибуты document_id, document_created и т. д.
Во-втором корневом узле нет вложенного дочернего, но имеются атрибуты title, keywords, description и name.
Как Вы могли заметить первый дочерний узел первого корневого узла это приветствующий текст на главной странице. А второй корневой узел — мета теги страницы.
Шаблон главной страницы просто берет из этого дерева информацию для отображения. А контроллер, перед этим, собрал это дерево. Так что в нашу на сегодня задачу входит написать класс который позволит собрать подобное дерево.
Давайте для начала определимся с тем как мы будем пользоваться данным классом. По задумке он будет уметь:
Давайте поразмышляем как будут юзаться данные возможности. Например создание нового узла должно происходить вот так:
$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();
Исходя из выше написанного могу точно сказать, что в классе нам понадобится как минимум три свойства:
Для начала создаю необходимые свойства класса и накидываю весь список необходимых методов (класс я назвал 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)
{
}
}
Теперь нужно написать самые простые методы. А это
/*
* добавляем новый узел. если в качестве параметра был передан объект 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,
/*
* прописываем новый атрибут в текущий объект класса dom
*
* @access - public
* @return - сам себя
*
* @param string name - имя атрибута
* @param string value - значение атрибута
*/
public function addAttr($name, $value)
{
$this->attr[$name] = $value;
return $this;
}
тут тоже все просто. Именем атрибута является ключ массива а значением — значение элемента массива.
/*
* получаем значение атрибута. Узел выберается с помощью метода getNode() или getChildren()
*
* @access - public
* @return - значение атрибута или FALSE в случае неудачи
*
* @param string name - имя атрибута
*/
public function getAttr($name)
{
return (isset($this->attr[$name])) ? $this->attr[$name] : FALSE;
}
просто смотрим существует ли элемент массива $name и если есть возвращаем его значение. В противном случае вернем false.
/*
* метод устанавливает имя узла
*
* @access - private
* @return - сам себя
*
* @param string name - имя узла
*/
private function setName($name)
{
$this->name = $name;
return $this;
}
Приватный метод позволяющий запомнить имя узла. Если Вы были внимательны то заметили, что он используется в методе addNode()
Ну что же, простые методы закончились, теперь давайте приступим к более услажненным.
/*
* получаем узел (объект) по имени из массива $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
/*
* получаем массив с вложенными узлами (объекты) из текущего узла
*
* @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 если массив пустой.
/*
* проверяем есть ли узлы в выбранном узле
*
* @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() за исключением того что данный метод вертает не массив в случае успеха, а кол-во элементов в массиве.
Ну и на по следок самые сложные методы.
Сложность заключается в том, что в методе 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() (т. е. показать все дерево). Вот что получилось
Вот собственно и все. По началу боялся, что тексту выйдет куда больше. Хотя и так не мало получилось. Если у Вас есть какие-то вопросы, то пишите в комментарии. Результат работы Вы можете скачать в конце статьи.
Всего Вам наилучшего, на сегодня все!
Вы из вот такой строчки:
Когда я писал вот такую строчку: