Написал классы для работы с деревьями. На данный момент реализована работа с: - Adjacency List - Redundant Adjacency List (Closure Table) - Nested Sets Полезные ссылки: http://phpclub.ru/faq/Tree http://gsbelarus.com/gs/modules.php?nam ... le&sid=314 http://www.php.ru/forum/viewtopic.php?t=20735 http://www.getinfo.ru/article610.html http://demiurg.livejournal.com/53125.html Где лежит код: http://code.google.com/p/voltcore/sourc ... sses/Trees Код: Основной класс DBTree PHP: <?php /** * Абстрактный класс работы с деревьями. От него наследуют классы для работы с конкретным способом хранения дерева. * * <p>Предполагается, что существуют две таблицы. Первая - таблица имен. В ней хранятся идентификаторы, имена и порядок сортировки узлов дерева. * Вторая - собственно таблица с деревом.</p> * <p>Если используется возможность идентифицировать узел по имени в таблице имен, то имя в таблице имен должно быть уникальным.</p> * <p>Сортировка:</p> * <p>Элементам присваивается номер по-порядку в пределах одного родителя.</p> * * @TODO Больше возможностей (выбор пути, выбор уровня) * * @author Костин Алексей Васильевич aka Volt(220) * @copyright Copyright (c) 2010, Костин Алексей Васильевич * @license [url=http://www.gnu.org/licenses/gpl-3.0.html]http://www.gnu.org/licenses/gpl-3.0.html[/url] GNU Public License * @package classes * @subpackage Trees * @abstract */ abstract class DBTree{ /** * Имен не передано. * @var int */ const NO_NAME=0; /** * Передано имя потомка. * @var int */ const CHILD_NAME=1; /** * Передано имя родителя. * @var int */ const PARENT_NAME=2; /** * Передано оба имени. * @var int */ const BOTH_NAME=3; /** * Увеличивать порядковые номера. * @var int */ const MOVE_DOWN=0; /** * Уменьшать порядковые номера. * @var int */ const MOVE_UP=1; /** * Таблица, в которой лежит дерево. * @var string */ protected $table; /** * Имя таблицы, в которой содержатся имена узлов. * @var string */ protected $nameTable; /** * Имя поля с идентификаторами. * @var string */ protected $idField; /** * Имя поля, в котором содержатся имена узлов. * @var string */ protected $nameField; /** * Имя поля, в котором содержатся идентификаторы узлов таблицы имен. * @var string */ protected $idNameField; /** * Имя поля, по которому происходит сортировка. * @var string */ protected $orderField; /** * Объект для работы с БД * @var SQLDB */ protected $DB; /** * Конструктор. * * @param string $tab Таблица, в которой лежит дерево. * @param string $idName Имя поля с идентификаторами. * @param string $idParName Имя поля с идентификаторами родителей. * @param string $nameTab Имя таблицы, в которой содержатся имена узлов. * @param string $nameField Имя поля, в котором содержатся имена узлов. * @param string $orderField Имя поля, по которому происходит сортировка. * @param string $orderType Тип сортировки. * @param string $DBCon Объект для работы с БД. */ public function __construct($tab, $idName, $nameTab=null, $idNameField='id', $nameField=null, $orderField=null, $DBCon=null){ $this->DB=$DBCon ? $DBCon : SQLDBFactory::getDB(); $this->table=$this->DB->escapeKeys($tab); $this->idField=$this->DB->escapeKeys($idName); $this->nameTable=$this->DB->escapeKeys($nameTab); $this->idNameField=$this->DB->escapeKeys($idNameField); $this->nameField=$this->DB->escapeKeys($nameField); $this->orderField=$orderField ? $this->DB->escapeKeys($orderField) : null; } /** * Возвращает запрос для выбора идентифкаторов всех непосредственных потомков родителя $idParent. * * @param mixed $idParent Идентификатор родителя. * @return string Запрос для выбора потомков. */ abstract protected function getChildsQuery($idParent); /** * Возвращает следующий порядковый номер для родителя $idParent. * * @param mixed $idParent Идентификатор родителя. * @return int Следующий порядковый номер. */ abstract protected function getFamilyNextNum($idParent); /** * Возвращает запросы для вставки нового листа в дерево. * * @param int $idChild Идентификатор того, у кого меняем родителя. * @param int $idParent Идентификатор нового родителя. * @return array Запросы для вставки нового листа в дерево. */ abstract protected function getAddInsert($idChild, $idParent); /** * Возвращает запрос для нахождения непосредственного родителя. * * @param int $idChild Идентификатор того, у кого меняем родителя. * @return string Запрос для нахождения непосредственного родителя. */ abstract protected function getSelectParent($idChild); /** * Выполняет запросы для смены родителя у узла. * * @param int $idChild Идентификатор того, у кого меняем родителя. * @param int $idParent Идентификатор нового родителя. * @param int $orderNum Номер вставляемого узла по-порядку для сортировки. * @throws SqlException При ошибке работы с базой. */ abstract protected function doChangePar($idChild, $idParent); /** * Выполняет запросы для удаления поддерева. * * @param int $idChild Идентификатор того, у кого меняем родителя. * @throws SqlException При ошибке работы с базой. */ abstract protected function doDeleteSubTree($idChild); /** * Вытаскивает дерево из БД и создает соответствующий массив. * * @param array $extraFields дополнительные поля из таблицы с именами. * Ключи массива - псевдонимы полей, которые станут ключами результирующего массива. * Значения полей массива - имена полей для выборки или подзапрос типа (select smth from table where ...). * @param mixed $id Идентификатор корня поддерева. Если не указан, то возвращается все дерево. * @return array Массив с деревом. * Индексами этого массива является порядковый номер узла в уровне, начиная с 0, без пропусков. * Узел – это массив, в которм содержатся следующие элементы: * id – идентификатор узла дерева * name – имя узла дерева * поля переданные в $extraFields со значениями из БД * tree – список дочерних узлов для этого узла. Если у этого узла нет дочерних узлов, то здесь содержится пустой массив. * @throws SqlException При ошибке работы с базой. * @throws FormatException Если указаны не все поля. */ public abstract function getTree($extraFields=null, $subTreeRoot=1); /** * Определяет передано ли имя ребенка или его идентификатор. * * @param int $haveNames Определяет, какие параметры считать именами. * @return bool true - если указано, что передано имя ребенка, false - в противном случае. */ protected function haveChildName($haveNames){ return $haveNames & DBTree::CHILD_NAME; } /** * Определяет передано ли имя родителя или его идентификатор. * * @param int $haveNames Определяет, какие параметры считать именами. * @return bool true - если указано, что передано имя родителя, false - в противном случае. */ protected function haveParentName($haveNames){ return $haveNames & DBTree::PARENT_NAME; } /** * Возвращает идентификатор по имени. * * Если в $haveNames указано, что передано имя, то метод вернет идентификатор записи по данному имени, * иначе метод вернет переданное имя. * * @param mixed $name Имя, по которому нужно определить идентификатор. * @param int $haveNames Определяет, какие параметры считать именами. * @param bool $child Кого искать ребенока(true) или родителя(false). * @return mixed Идентификатор записи. * @throws SqlException При ошибке работы с базой. */ protected function getIdByName($name, $haveNames, $child=true){ $id=$name; $dbId=$this->DB->escapeString($name); $sql="select id from $this->nameTable where $this->nameField=$dbId"; if ($child){ if ($this->haveChildName($haveNames)){ $id=$this->DB->getVal($sql); } }else{ if ($this->haveParentName($haveNames)){ $id=$this->DB->getVal($sql); } } if (is_null($id) || $id===false) throw new SqlException("Идентификатор не найден","Нет данных",$sql); return $id; } /** * Возвращает номер по-порядку для узла с идентификатором $id. * * @param mixed $idParent Идентификатор узла. * @return int Номер узла по-порядку. * @throws SqlException При ошибке работы с базой. */ public function getOrderNum($id){ $orderNum=$this->DB->getVal("select $this->orderField from $this->nameTable where $this->idNameField=$id"); return $orderNum+0; } /** * Подготовливает таблицу для вставки нового элемента или изменения существующего порядка. * * @param mixed $idParent Идентификатор родителя, того элемента для которого подготавливается новый порядок * @param int $orderNum Новый желаемый порядковый номер потомка. Если не передан, то вычисляется. * @return int Новый порядковый номер потомка. * @throws SqlException При ошибке работы с базой. */ protected function prepareForNewOrder($idParent, $orderNum=null){ $sorder=$orderNum+0; if ($sorder>0){ $this->familyMove($idParent, $sorder); }else{ $sorder=$this->getFamilyNextNum($idParent); } return $sorder; } /** * Изменяет порядковый номер $count элементов с позиции $startNum на $positions позиций у родителя $idParent. * * @param mixed $idParent Идентификатор родителя * @param int $startNum С какой позиции начинать. * @param int $endNum На какой позиции закончить. * @param int $direction Куда двигать записи. * @param $positions На сколько позиций смещать. * @throws SqlException При ошибке работы с базой. */ protected function familyMove($idParent, $startNum, $direction=DBTree::MOVE_DOWN, $endNum=null, $positions=1){ $startNum+=0; $endNum+=0; if ($startNum==$endNum) return; $set=''; $where=$this->idNameField." in (".$this->getChildsQuery($idParent).")"; if ($direction==DBTree::MOVE_DOWN){ $symbol="+"; }else{ $symbol="-"; } $where.=" and $this->orderField>=$startNum"; if ($endNum){ $where.=" and $this->orderField<=$endNum"; } $set="$this->orderField=".$this->orderField.$symbol.$positions; $this->DB->update("update $this->nameTable set $set where $where"); } /** * Вставляет запись о ребенке в таблицу имен. * * @param string $idChild Имя ребенка. * @param mixed $idParent Идентификатор родителя. * @param int $orderNum Номер ребенка по порядку. * @return string Идентификатор ребенка. * @throws SqlException При ошибке работы с базой. */ protected function insertChild($idChild,$idParent=null, $orderNum=null){ if ($this->orderField){ $sorder=$this->prepareForNewOrder($idParent, $orderNum); $idChild=$this->DB->insert("insert into $this->nameTable($this->nameField, $this->orderField) values($idChild, $sorder)"); }else{ $idChild=$this->DB->insert("insert into $this->nameTable($this->nameField) values($idChild)"); } return $idChild; } /** * Возврщает строку для выборки дополнительных полей. * * @param array $extraFields дополнительные поля из таблицы с именами. * Ключи массива - псевдонимы полей, которые станут ключами результирующего массива. * Значения полей массива - имена полей для выборки или подзапрос типа (select smth from table where ...). * @return string Строка для выборки дополнительных полей. */ protected function extraFieldsToQueryString($extraFields){ if(!$extraFields || !is_array($extraFields)) return ''; $rez=""; foreach($extraFields as $name=>$field){ if (strPos($field, "(")===0){ $rez .= ", $field as $name"; }else{ $rez .= ", c.$field as $name"; } } return $rez; } /** * Добавляет новый лист в дерево. * * @param mixed $id Идентификатор потомка или уникальная строка для вставки в таблицу имен. * @param mixed $parId Идентификатор родителя или уникальная строка для поиска в таблице имен. * @param int $haveNames Определяет, какие параметры считать именами. * @param int $orderNum Номер нового узла по-порядку для сортировки. * @throws SqlException При ошибке работы с базой. * @throws FormatException Если указаны не все поля. */ public function add($id, $parId, $haveNames=DBTree::NO_NAME, $orderNum=null){ if ($haveNames!=DBTree::NO_NAME && (!$this->nameTable || !$this->nameField)) throw new FormatException("Недостаточно данных.","Указаны не все данные"); $DB=$this->DB; try{ $DB->startTran(); $idChild=$DB->escapeString($id); $idParent=$this->getIdByName($parId, $haveNames, false); if ($this->haveChildName($haveNames)){ $idChild=$this->insertChild($idChild,$idParent,$orderNum); } $insert=$this->getAddInsert($idChild, $idParent); foreach($insert as $query){ $DB->insert($query); } $DB->commit(); }catch(SqlException $e){ $DB->rollback(); throw $e; } } /** * Устанавливает номер узла по-порядку для сортировки. * * @param mixed $id Идентификатор потомка или уникальная строка из таблицы имен. * @param int $orderNum Номер нового узла по-порядку для сортировки. * @param int $haveNames Определяет, какие параметры считать именами. * @throws SqlException При ошибке работы с базой. * @throws FormatException Если указаны не все поля. */ public function setOrderNum($id, $orderNum, $haveNames=DBTree::NO_NAME){ if (!$this->nameTable || !$this->nameField || !$this->orderField) throw new FormatException("Недостаточно данных.","Указаны не все данные"); $idChild=$this->getIdByName($id, $haveNames); $idParent=$this->getParent($idChild); $newOrder=$orderNum+0; if ($newOrder<1){ $newOrder=$this->getFamilyNextNum($idParent)-1; } $oldOrder=$this->getOrderNum($idChild); if ($oldOrder==$newOrder) return; $DB=$this->DB; try{ $DB->startTran(); if ($oldOrder<$newOrder){ $this->familyMove($idParent, $oldOrder, DBTree::MOVE_UP, $newOrder); }else{ $this->familyMove($idParent, $newOrder, DBTree::MOVE_DOWN, $oldOrder); } $DB->update("update $this->nameTable set $this->orderField=$newOrder where $this->idNameField=$idChild"); $DB->commit(); }catch(SqlException $e){ $DB->rollback(); throw $e; } } /** * Изменяет порядковый номер узла. * * @param mixed $id Идентификатор узла. * @param int $positions На сколько позиций перемещать. * @param int $direction Куда перемещать. * @param int $haveNames Определяет, какие параметры считать именами. */ public function moveNode($id, $positions=1, $direction=DBTree::MOVE_DOWN, $haveNames=DBTree::NO_NAME){ if (!$this->nameTable || !$this->nameField || !$this->orderField) throw new FormatException("Недостаточно данных.","Указаны не все данные"); $idChild=$this->getIdByName($id, $haveNames); $oldNum=$this->getOrderNum($idChild); if ($direction==DBTree::MOVE_UP){ $newNum=$oldNum-$positions; $newNum=max($newNum,1); }else{ $newNum=$oldNum+$positions; } $this->setOrderNum($idChild, $newNum); } /** * Меняет родителя у узла. * * @param mixed $id Идентификатор того, у кого меняем родителя. * @param mixed $parId Идентификатор нового родителя. * @param int $haveNames Определяет, какие параметры считать именами. * @param int $orderNum Номер узла по-порядку для сортировки. * @throws SqlException При ошибке работы с базой. * @throws FormatException Если указаны не все поля. */ public function changePar($id, $parId, $haveNames=DBTree::NO_NAME, $orderNum=null){ if ($haveNames!=DBTree::NO_NAME && (!$this->nameTable || !$this->nameField)) throw new FormatException("Недостаточно данных.","Указаны не все данные"); if ($this->orderField && (!$this->nameTable || !$this->nameField || !$this->orderField)) throw new FormatException("Недостаточно данных.","Указаны не все данные"); $DB=$this->DB; try{ $idChild=$this->getIdByName($id, $haveNames); $idParent=$this->getIdByName($parId, $haveNames, false); $oldParent=$this->getParent($idChild); if ($idChild==$idParent){ throw new FormatException("Нельзя замкнуть узел на себя.","Неверные данные"); } if ($oldParent==$idParent){ if ($orderNum){ $this->setOrderNum($idChild, $orderNum); } return; } $DB->startTran(); if ($this->orderField){ $oldNum=$this->getOrderNum($idChild); $this->familyMove($oldParent, $oldNum+1, DBTree::MOVE_UP); $sorder=$this->prepareForNewOrder($idParent, $orderNum); $this->DB->update("update $this->nameTable set $this->orderField=$sorder where $this->idNameField=$idChild"); } $this->doChangePar($idChild, $idParent); $DB->commit(); }catch(SqlException $e){ $DB->rollback(); throw $e; } } /** * Удаляет поддерево. * * @param mixed $id Идентификатор вершины поддерева или уникальная строка в таблице имен. * @param int $haveNames Определяет, считать ли $id именем. * @throws SqlException При ошибке работы с базой. * @throws FormatException Если указаны не все поля. */ public function deleteSubTree($id, $haveNames=DBTree::NO_NAME){ if (!$this->nameTable || !$this->nameField) throw new FormatException("Недостаточно.","Указаны не все данные"); $DB=$this->DB; try{ $idChild=$this->getIdByName($id,$haveNames); if ($idChild==1) throw new FormatException('Нельзя удалить корень дерева','Неверные данные'); if ($this->orderField){ $oldParent=$this->getParent($idChild); $oldNum=$this->getOrderNum($idChild); $this->familyMove($oldParent, $oldNum+1, DBTree::MOVE_UP); } $DB->startTran(); $this->doDeleteSubTree($idChild); $DB->commit(); }catch(SqlException $e){ $DB->rollback(); throw $e; } } /** * Возвращает непосредственного родителя узла. * * @param mixed $id Идентификатор потомка. * @param int $haveNames Определяет, считать ли $id именем. * @return string Идентификатор предка. * @throws SqlException При ошибке работы с базой. */ public function getParent($id, $haveNames=DBTree::NO_NAME){ if (!$this->nameTable || !$this->nameField) throw new FormatException("Недостаточно данных.","Указаны не все данные"); $idChild=$this->getIdByName($id, $haveNames); if($idChild==1) return 1; $select=$this->getSelectParent($idChild); $pid=$this->DB->getVal($select); if (is_null($pid) || $pid===false) throw new SqlException("Идентификатор не найден","Нет данных",$select); return $pid; } } Adjacency List - ALTree: PHP: <?php /** * Класс для работы с деревом. Дерево хранится по принципу Adjacency List. * * Данная реализация принципа предполагает: * 1)Существует корневой элемент с id=1. * 2)Корневой элемент ссылается на себя как на родителя. * * @package classes * @subpackage Trees */ class ALTree extends DBTree{ /** * Имя поля с идентификаторами родителей. * @var string */ private $idParField; /** * @param string $idParName Имя поля с идентификаторами родителей. */ public function __construct($tab, $idName, $idParName, $nameTab=null, $idNameField='id', $nameField=null, $orderField=null, $DBCon=null){ parent::__construct($tab, $idName, $nameTab, $idNameField, $nameField, $orderField, $DBCon); $this->idParField=$this->DB->escapeKeys($idParName); } protected function getChildsQuery($idParent){ return "select $this->idField from $this->table where $this->idParField=$idParent"; } protected function getFamilyNextNum($idParent){ $sorder=$this->orderField; $table=$this->nameTable; $tree=$this->table; $id=$this->idNameField; $idChild=$this->idField; $idPar=$this->idParField; $num=$this->DB->getVal("select max($sorder) from $table join $tree on $table.$id=$tree.$idChild where $idPar=$idParent"); return $num+1; } protected function getAddInsert($idChild, $idParent){ return array("insert into $this->table($this->idField, $this->idParField) values ($idChild, $idParent)"); } protected function getSelectParent($idChild){ return "select $this->idParField from $this->table where $this->idField=$idChild"; } protected function doChangePar($idChild, $idParent){ $this->DB->update("update $this->table set $this->idParField=$idParent where $this->idField=$idChild"); } protected function doDeleteSubTree($idChild){ $DB=$this->DB; $numDeleted=0; $numDeleted=$DB->delete("delete from $this->table where $this->idField=$idChild or $this->idParField=$idChild"); while ($numDeleted>0){ $ids=implode(",",$DB->getColumn("select $this->idNameField from $this->nameTable where $this->idNameField not in (select $this->idField from $this->table)")); $numDeleted=$DB->delete("delete from $this->table where $this->idParField in ($ids)"); } $DB->delete("delete from $this->nameTable where $this->idNameField not in (select $this->idField from $this->table)"); } public function getTree($extraFields=null, $subTreeRoot=1){ if (!$this->nameTable || !$this->nameField) throw new FormatException("Недостаточно данных для создания дерева.","Указаны не все данные"); $sFiled= $this->orderField ? "c.$this->orderField," : ''; $extra= $this->extraFieldsToQueryString($extraFields); //Переприсваивание для создания более читаемого запроса $tree=$this->table; $f=$this->idField; $id=$this->idNameField; $pid=$this->idParField; $name=$this->nameField; $tab=$this->nameTable; //Выбор $sql="select c.$id as cid, c.$name as cname, t.$pid as pid $extra from $tree as t join $tab as c on t.$f=c.$id order by $sFiled c.$name"; $DB=$this->DB; $DB->select($sql); //Запись в массив $path=array(); while($row=$DB->fetchAssoc()){ if(!isset($path[$row["cid"]])){ $path[$row["cid"]]=array("name"=>$row["cname"], "id"=>$row["cid"], "tree"=>array()); if ($extra){ foreach($extraFields as $k=>$v){ $path[$row["cid"]][$k]=$row[$k]; } } } else{ $path[$row["cid"]]["name"]=$row["cname"]; $path[$row["cid"]]["id"]=$row["cid"]; if ($extra){ foreach($extraFields as $k=>$v){ $path[$row["cid"]][$k]=$row[$k]; } } } if ($row["pid"]!=$row["cid"]){ $path[$row["pid"]]["tree"][]=&$path[$row["cid"]]; } } $tree=array(); if ($path && isset($path[$subTreeRoot])){ $tree[0]=$path[$subTreeRoot]; } return $tree; } } Redundant Adjacency List - FHTree: PHP: <?php /** * Класс для работы с деревом. Дерево хранится по принципу Full Hierarchy (Redundant Adjacency List по другим источникам). * * Данная реализация принципа предполагает: * 1)Существует корневой элемент с id = 1. * 2)В таблице есть три колонки: идентификатор узла, идентификатор родителя, уровень. * 3)У узла создается запись для каждого родителя с указанием уровня (первый родитель - 1, родитель родителя - 2 и т.д.). * 4)Каждый узел, кроме корня, имеет как минимум 2 записи - ссылка на себя с уровнем 0 и ссылка на корневой элемент с id = 1. * * @package classes * @subpackage Trees */ class FHTree extends DBTree{ /** * Имя поля с идентификаторами родителей. * @var string */ private $idParField; /** * Имя поля уровня. * @var string */ private $levelField; /** * @param string $idParName Имя поля с идентификаторами родителей. * @param string $levelName Имя поля уровня. */ public function __construct($tab, $idName, $idParName, $levelName, $nameTab=null, $idNameField='id', $nameField=null, $orderField=null, $DBCon=null){ parent::__construct($tab, $idName, $nameTab,$idNameField, $nameField, $orderField, $DBCon); $this->idParField=$this->DB->escapeKeys($idParName); $this->levelField=$this->DB->escapeKeys($levelName); } protected function getChildsQuery($idParent){ return "select $this->idField from $this->table where $this->idParField=$idParent and $this->levelField=1"; } protected function getFamilyNextNum($idParent){ $sorder=$this->orderField; $table=$this->nameTable; $tree=$this->table; $id=$this->idNameField; $idChild=$this->idField; $idPar=$this->idParField; $level=$this->levelField; $num=$this->DB->getVal("select max($sorder) from $table join $tree on $table.$id=$tree.$idChild where $idPar=$idParent and $level=1"); return $num+1; } protected function getAddInsert($idChild, $idParent){ return array( "insert into $this->table($this->idField, $this->idParField, $this->levelField) select $idChild, $this->idParField, $this->levelField+1 from $this->table where $this->idField=$idParent", "insert into $this->table($this->idField, $this->idParField, $this->levelField) values($idChild, $idChild, 0)"); } protected function getSelectParent($idChild){ return "select $this->idParField from $this->table where $this->idField=$idChild and $this->levelField=1"; } protected function doChangePar($idChild, $idParent){ $table=$this->table; $f=$this->idField; $pid=$this->idParField; $level=$this->levelField; $DB=$this->DB; $allChilds="select $f from $table where $pid=$idChild"; $allParents="select $pid from $table where $f=$idChild and $pid<>$idChild"; $childs="(".implode(",",$DB->getColumn($allChilds)).")"; $parents="(".implode(",",$DB->getColumn($allParents)).")"; $delete="delete from $table where $f in $childs and $pid in $parents"; $insert="insert into $table($f, $pid, $level) SELECT down.$f, up.$pid, down.$level + up.$level + 1 FROM $table as up join $table as down on up.$f = $idParent and down.$pid=$idChild"; $DB->delete($delete); $DB->insert($insert); } /** * Выполняет запросы для удаления поддерева. * * @param int $idChild Идентификатор того, у кого меняем родителя. * @throws SqlException При ошибке работы с базой. */ protected function doDeleteSubTree($idChild){ $DB=$this->DB; $allChilds="select $this->idField from $this->table where $this->idParField=$idChild"; $childs="(".implode(",",$DB->getColumn($allChilds)).")"; $deleteName="delete from $this->nameTable where $this->idNameField in $childs"; $delete="delete from $this->table where $this->idField in $childs"; $DB->delete($delete); $DB->delete($deleteName); } public function getTree($extraFields=null, $subTreeRoot=1){ if (!$this->nameTable || !$this->nameField) throw new FormatException("Недостаточно данных для создания дерева.","Указаны не все данные"); $sFiled= $this->orderField ? "c.$this->orderField," : ''; $extra= $this->extraFieldsToQueryString($extraFields); //Переприсваивание для создания более читаемого запроса $tree=$this->table; $f=$this->idField; $id=$this->idNameField; $pid=$this->idParField; $level=$this->levelField; $name=$this->nameField; $tab=$this->nameTable; //Выбор $sql=" select c.$id as cid, c.$name as cname, par.$id as pid $extra from $tree as t join $tab as c on t.$f=c.$id left outer join $tree as t2 on t.$f=t2.$f and t2.$level=1 left outer join $tab as par on par.$id=t2.$pid where t.$pid=$subTreeRoot order by t.$level, $sFiled c.$name"; $this->DB->select($sql); //Запись в массив $path=array(); while($row=$this->DB->fetchAssoc()){ if(!isset($path[$row["cid"]])){ $path[$row["cid"]]=array("name"=>$row["cname"], "id"=>$row["cid"], "tree"=>array()); if ($extra){ foreach($extraFields as $k=>$v){ $path[$row["cid"]][$k]=$row[$k]; } } } if($row["pid"]){ $path[$row["pid"]]["tree"][]=&$path[$row["cid"]]; } } $tree=array(); if ($path){ $tree[0]=reset($path); } return $tree; } } Создание таблиц(MySQL): ALTree с сортировкой [sql]create table ALSortTreeData ( id int PRIMARY KEY AUTO_INCREMENT, content varchar(20) unique, sorder int not null ) ENGINE=InnoDB; create table ALSortTree ( cid int, pid int, FOREIGN KEY (cid) REFERENCES ALSortTreeData(id) on delete no action on update no action, FOREIGN KEY (pid) REFERENCES ALSortTreeData(id) on delete no action on update no action ) ENGINE=InnoDB; insert into ALSortTreeData(content, sorder) values('0', 0); insert into ALSortTree(cid, pid) values(1,1);[/sql] ALTree без сортировки [sql] create table ALTreeData ( id int PRIMARY KEY AUTO_INCREMENT, content varchar(20) unique ) ENGINE=InnoDB; create table ALTree ( cid int, pid int, FOREIGN KEY (cid) REFERENCES ALTreeData(id) on delete no action on update no action, FOREIGN KEY (pid) REFERENCES ALTreeData(id) on delete no action on update no action ) ENGINE=InnoDB; insert into ALTreeData(content) values('0'); insert into ALTree(cid, pid) values(1,1); [/sql] FHTree с сортировкой [sql]create table FHSortTreeData ( id int PRIMARY KEY AUTO_INCREMENT, content varchar(20) unique, sorder int not null ) ENGINE=InnoDB; create table FHSortTree ( cid int, pid int, level int not null, FOREIGN KEY (cid) REFERENCES FHSortTreeData(id) on delete no action on update no action, FOREIGN KEY (pid) REFERENCES FHSortTreeData(id) on delete no action on update no action ) ENGINE=InnoDB; insert into FHTreeData(content) values('0'); insert into FHTree(cid, pid, level) values(1,1,0); [/sql] FHTree без сортировки [sql]create table FHTreeData ( id int PRIMARY KEY AUTO_INCREMENT, content varchar(20) unique ) ENGINE=InnoDB; create table FHTree ( cid int, pid int, level int not null, FOREIGN KEY (cid) REFERENCES FHTreeData(id) on delete no action on update no action, FOREIGN KEY (pid) REFERENCES FHTreeData(id) on delete no action on update no action ) ENGINE=InnoDB; insert into FHSortTreeData(content, sorder) values('0', 0); insert into FHSortTree(cid, pid, level) values(1,1,0);[/sql] Для MSSQL аналогично, за двумя отличиями: [sql] id int PRIMARY KEY identity(1,1) -- вместо id int PRIMARY KEY AUTO_INCREMENT [/sql] и [sql] cid int REFERENCES ALTreeData(id) on delete no action on update no action -- вместо FOREIGN KEY (cid) REFERENCES FHTreeData(id) on delete no action on update no action -- MySQL у меня такую запись не скушал, хотя вроде должен. [/sql] Применение: PHP: <?php /*Без сортировки*/ //Создание $tree= new FHTree("FHTree", "cid", "pid", "level", "FHTreeData", 'id', "content", null, $DBCon); //Добавление $tree->add(3,2, DBTree::NO_NAME); //Соответствующее записи уже должны быть в таблице FHTreeData. $tree->add(4,'1', DBTree::PARENT_NAME); $tree->add('1.2.1',4,DBTree::CHILD_NAME); //В таблице FHTreeData появиться новая запись. $tree->add('1.2.2','1.2', DBTree::BOTH_NAME); //Перемещение поддерева к новому родителю $tree->changePar('6.1','5', DBTree::BOTH_NAME); //Удаление поддерева $tree->deleteSubTree(8); /*С сортировкой*/ $sortTree= new FHTree("FHSortTree", "cid", "pid", "level", "FHSortTreeData", 'id', "content", "sorder", $DBCon); //Добавление с указанием порядкового номера $sortTree->add('1.2','1',DBTree::BOTH_NAME,1); //Изменение порядка $sortTree->setOrderNum('1.1',1, DBTree::BOTH_NAME); //Перестановка в начало $sortTree->setOrderNum('1',0, DBTree::BOTH_NAME); //Перестановка в конец $sortTree->setOrderNum('6',3, DBTree::BOTH_NAME); //Перестановка на конкретное место $sortTree->moveNode(19,3,DBTree::MOVE_UP); //Перестановка на 3 вверх $sortTree->moveNode(19,2); //Перестановка на 2 вниз //Перемещение поддерева к новому родителю с указанием порядкового номера $sortTree->changePar('3','6.1', DBTree::BOTH_NAME,1); //UPD //Выбор дерева $tree->getTree(); //Выбор дерева с дополнительными полями $tree->getTree(array("price"=>"price", "itemColor"=>"color", "profit"=>"(select sum(price*count) from sales where id_item=c.id)")); //Выбор поддерева $tree->getTree(null, 8);
может быть ввести новый класс - нода? Вообще библиотека для работы с деревьями - очень правильная затея
[vs] Что значит пересортировать? Для изменения порядкового номера узла дерева - да, придется создавать экземпляр. Koc Интерфейс работы с БД: PHP: <?php /** * Абстрактный класс работы с базой данных. От него наследуют классы для работы с конкретной СУБД. * * @author Костин Алексей Васильевич aka Volt(220) * @copyright Copyright (c) 2010, Костин Алексей Васильевич * @license [url=http://www.gnu.org/licenses/gpl-3.0.html]http://www.gnu.org/licenses/gpl-3.0.html[/url] GNU Public License * @package classes * @subpackage DBClasses * @abstract */ abstract class SQLDB { /** * Возвращает код последней ошибки * * @return string Код последней ошибки */ abstract public function getErrorCode(); /** * Возвращает последнее сообщение об ошибке * * @return string Сообщение об ошибке */ abstract public function getErrorMsg(); /** * Возвращает количество строк, обработанных последним запросом. * * @access protected * @return int количество строк, обработанных последним запросом */ abstract public function affectRows(); /** * Начинает транзакцию * * Отключает autocommit и посылает серверу команду начать транзакцию. */ abstract public function startTran(); /** * Подтверждает транзакцию * * Подтверждает транзакцию и включает autocommit. */ abstract public function commit(); /** * Откатывает транзакцию * * Производит откат транзакции и включает autocommit. */ abstract public function rollback(); /** * Возвращает очередную строку из результата запроса в виде ассоциативного массива * * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res * @return array Ассоциативный массив, содержащий значения из очередной строки результата */ abstract public function fetchAssoc($res=null); /** * Возвращает очередную строку из результата запроса в виде объекта * * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res * @return array Объект, содержащий значения из очередной строки результата */ abstract public function fetchObj($res=null); /** * Возвращает очередную строку из результата запроса в виде пронумерованного массива * * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res * @return array Пронумерованный массив, содержащий значения из очередной строки результата */ abstract public function fetchRow($res=null); /** * Возвращает единственное значение из результата запроса * * Этот метод следует использовать, когда ожидется выбор единственного значения * * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res * @return mixed Значение из результата запроса */ abstract public function fetchField($res=null); /** * Обрабатывает спецсимволы в строке для безопасного ее использования в запросе * * @param mixed $str Строка, в которой надо экранировать спецсимволы. * @return mixed Строка с экранированными спецсимволы. */ abstract public function escape($str); ///////////////////// //Магические методы// ///////////////////// /** * Запрещение клонирования * @throws Exception Клонирование запрещено */ public function __clone(){ throw new Exeption('Клонирование запрещено!'); } /** * Преобразует объект в строковое представление. * @return string Строковое представление объекта */ public function __toString(){} /** * Деструктор. * * Закрывает соединение. Удаляет себя из списка фабрики. */ public function __destruct(){} //////////////////// //Сеттеры, геттеры// //////////////////// /** * Устанавливает значение {@link factId} * @param string $id Новый идентификатор */ public function setId($id){} /** * Возвращает значение {@link factId} * @return string Текущий идентификатор */ public function getId(){} /** * Конфигурирует объект в соответствии с массивом конфигурации * @throws FormatException * @param array $config Массив для установки конфигурации. */ protected function setConfig($config){} /** * Возвращает текущую конфигурацию объекта * @return array массив с конфигурационными переменными */ public function getConfig(){} /** * Возвращает последний запрос. * @return string Последний запрос */ public function getLastQuery(){} /////////// //Запросы// /////////// /** * Выполняет запрос select. * * Выполняет запрос $sql. Запрос должен быть запросом типа select. * Для предотвращения несанкционированных действий * перед выполнением запрос проверяется на наличие оператора union. * Если оператор union будет найден, то будет выброшено исключение. * Если в запросе необходимо использовать оператор union, * то количество таких операторов нужно указать в параметре $numUnion. * * @throws SqlException, FormatException * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора. * Если $arr это строка, то она трактуется как sql-запрос для выполнения. * @param string $tab Таблица, в которой выполняется обновление. * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * @param int $numUnion Количество операторов union в запросе. * @return resource Результат запроса */ public function select($arr, $tab=null, $where=null, $numUnion=0){} /** * Выполняет вставку строки в таблицу. * * @throws SqlException * @param mixed $arr Если $arr массив, то ключи массива трактуются как поля таблицы, * а соответствующие значения как значения этих полей. Значения проходят обработку {@link escapeString} * Если $arr это строка, то она трактуется как sql-запрос для выполнения. * @param string $tab Таблица, в которую нужно вставить значения. * @return mixed id только что вставленной записи */ public function insert($arr, $tab=null){} /** * Выполняет обновление данных в таблице. * * @throws SqlException, FormatException * @param mixed $arr Если $arr массив, то ключи массива трактуются как поля таблицы, * а соответствующие значения как значения этих полей. * Если $arr это строка, то она трактуется как sql-запрос для выполнения. * @param string $tab Таблица, в которой выполняется обновление. * @param mixed $where Условие, при котором выполняется обновление, или id записи для обновления, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * @return int Количество обновленных строк. */ public function update($arr, $tab=null, $where=null){} /** * Выполняет удаление из таблицы. * * @throws SqlException, FormatException * @param string $tab Таблица из которой удалять или sql-запрос. * @param mixed $where Условие, при котором выполняется удаление, или id записи для удаления, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * Если $where ничего из вышеперечисленного, то $tab считается sql-запросом. * @return int Количество удаленных строк. */ public function delete($tab, $where=null){} /** * Определяет id по уникальному значению. * * @param string $tab Имя таблицы. * @param array $where Массив для определения id. Ключи - имена полей, значения - значения полей. * @return mixed Идентификатор. */ public function findOrInsert($tab, $where){} /** * Находит id по уникальному значению. * * @param string $tab Имя таблицы. * @param array $where Массив для определения id. Ключи - имена полей, значения - значения полей. * @return mixed Идентификатор. */ public function findId($tab, $where){} //////////////////// //Запросы значений// //////////////////// /** * Возвращает первую строку из результата запроса $sql в виде ассоциативного массива. * * @throws SqlException, FormatException * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора. * Если $arr это строка, то она трактуется как sql-запрос для выполнения. * @param string $tab Таблица, в которой выполняется обновление. * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * @param int $numUnion Количество операторов union в запросе. * @return array Ассоциативный массив, содержащий значения из первой строки результата запроса */ public function getAssoc($arr, $tab=null, $where=null, $numUnion=0){} /** * Возвращает первую строку из результата запроса $sql в виде пронумированного массива. * * @throws SqlException, FormatException * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора. * Если $arr это строка, то она трактуется как sql-запрос для выполнения. * @param string $tab Таблица, в которой выполняется обновление. * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * @param int $numUnion Количество операторов union в запросе. * @return array Пронумированный массив, содержащий значения из первой строки результата запроса */ public function getRow($arr, $tab=null, $where=null, $numUnion=0){} /** * Возвращает первый столбец из результата запроса $sql в виде пронумированного массива. * * @throws SqlException, FormatException * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора. * Если $arr это строка, то она трактуется как sql-запрос для выполнения. * @param string $tab Таблица, в которой выполняется обновление. * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * @param int $numUnion Количество операторов union в запросе. * @return array Пронумированный массив, содержащий значения из первого столбца результата запроса */ public function getColumn($arr, $tab=null, $where=null, $numUnion=0){} /** * Возвращает первую строку из результата запроса $sql в виде объекта * * @throws SqlException, FormatException * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора. * Если $arr это строка, то она трактуется как sql-запрос для выполнения. * @param string $tab Таблица, в которой выполняется обновление. * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * @param int $numUnion Количество операторов union в запросе. * @return object Объект, содержащий значения из первой строки результата запроса */ public function getObj($arr, $tab=null, $where=null, $numUnion=0){} /** * Возвращает единственное значение из результата запроса $sql. * * @throws SqlException, FormatException * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора. * Если $arr это строка, то она трактуется как sql-запрос для выполнения. * @param string $tab Таблица, в которой выполняется обновление. * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * @param int $numUnion Количество операторов union в запросе. * @return mixed Значение из первой строки результата запроса */ public function getVal($arr, $tab=null, $where=null, $numUnion=0){} /** * Возвращает весь результат запроса в виде двумерного массива. * * @throws SqlException, FormatException * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора. * Если $arr это строка, то она трактуется как sql-запрос для выполнения. * @param string $tab Таблица, в которой выполняется обновление. * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * @param int $numUnion Количество операторов union в запросе. * @return array Двумерный массив с результатом запроса. */ public function getTable($arr, $tab=null, $where=null, $numUnion=0){} ////////////////////// //Подгтовка запросов// ////////////////////// /** * Создает и проверяет запрос select. * * @see select * @throws SqlException, FormatException * @param mixed $arr Запрос для проверки или массив со значениями. * @param string $tab Таблица, в которой обновляются значения. * @param mixed $where Условие, по которому обновляются значения, или id записи для поиска. * @param int $numUnion Количество операторов uniond в запросе. * @return string Проверенный запрос */ public function getSelect($arr, $tab=null, $where=null, $numUnion=0){} /** * Формирует и проверяет запрос insert. * @see insert * @throws SqlException, FormatException * @param mixed $arr Запрос для проверки или массив со значениями * @param string $tab Таблица для вставки значения * @return string Проверенный запрос */ public function getInsert($arr, $tab=null){} /** * Формирует и проверяет запрос update. * @see update * @throws SqlException, FormatException * @param mixed $arr Запрос для проверки или массив со значениями * @param string $tab Таблица, в которой обновляются значения * @param mixed $where Условие, по которому обновляются значения, или id записи для обновления * @return string Проверенный запрос */ public function getUpdate($arr, $tab=null, $where=null){} /** * Формирует и проверяет запрос delete. * @see delete * @throws SqlException, FormatException * @param string $tab Запрос для проверки или таблица, в которой обновляются значения * @param mixed $where Условие, при котором удаляется запись, или id записи для удаления * @return string Проверенный запрос */ public function getDelete($tab, $where=null){} /** * Формирует условие where. * * @param mixed $where Условие, при котором выполняется действие, или id записи, * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей * @return string Сформированное условие. */ public function getWhere($where){} /** * Подготовливает выполнение sql-запроса * * Функция запоминает sql-запрос в свойстве класса. * Логирует и выводит его на экран, если это указано в настройках $vf["db"]["sqlLog"] и $vf["db"]["sqlShow"]. * Производит перекодировку запроса, если нужно. * Выбрасывает исключение в случае ошибки на sql-сервере. * * @param string sql-запрос для выполнения */ public function exec($sql, $save=true){} /** * Функция создает строку присвоения/сравнения из массива. * * Функция создает строку присвоения/сравнения из массива. * Ключи массива трактуются как поля таблицы, а соответствующие значения, как те значения которые нужно присвоить/сравнить. * * @param array $arr Массив из которого нужно сделать строку присвоения/сравнения * @param string $delim Строка, которая вставляется между ассоциированными парами. * @return string Требуемая строка */ public function getAssignmentString($arr, $delim){} /** * Обрабатывает спецсимволы в строке для безопасного ее использования в запросе * * @param mixed $str Строка, в которой надо экранировать спецсимволы или число или null. * @return mixed Строка с экранированными спецсимволы, или число или строка "null". */ public function escapeString($str){ if (is_array($str)){ foreach($str as $key=>$val){ $str[$key]=$this->escapeString($val); } return $str; } if (is_null($str)) return "NULL"; if (is_string($str)) return "'".$this->escape($str)."'"; return $str; } /** * Обрамляет имя поля в специальные символы. * * В большинстве БД для использования некоторых символов (например, пробелов) в имени поля нужно все поле обрамить спецсимволами. * Именно это и делает данная функция * * @param string $str Имя поля * @return string имя поля заключенное в спецсимволы */ public function escapeKeys($key){} /** * Возвращает результат запроса в виде двумерного массива. * * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res * @return array Двумерный массив с результатом запроса. */ public function fetchTable($rez=null){} } На счет ноды: Мне кажется это перспективная идея в плане хранения дерева. Т.е. уйти от массива массивов к ноде.
крутотень! еще б нотацию PEAR/ZF в именовании классов. Код (Text): throw new FormatException("Недостаточно данных.","Указаны не все данные"); spl: InvalidArgumentException?
PHP: public function __construct($tab, $idName, $idParName, $levelName, $nameTab=null, $idNameField='id', $nameField=null, $orderField=null, $DBCon=null){ названия полей можно б массивом передавать, а к $DBCon добавить тайпхинтинг SQLDB
Да, вполне. Можно. Для некой единости библиотек - да. Для всеобщего обозрения/использования - не обязательно. По идее можно передать и объект не SQLDB класса, который реализует нужные методы.
И примерно вот так это дерево можно вывести: 1)Шаблонизатор а) http://www.php.ru/forum/viewtopic.php?p=211516#211516 б) PHP: <?php /** * Шаблон вывода дерева. * * @author Костин Алексей Васильевич aka Volt(220) * @copyright Copyright (c) 2010, Костин Алексей Васильевич * @license [url=http://www.gnu.org/licenses/gpl-3.0.html]http://www.gnu.org/licenses/gpl-3.0.html[/url] GNU Public License * @package classes * @subpackage templates */ class TreeTpl extends Template{ const UL=0; const EXTJS=1; const EXTJSADV=2; /** * Конструктор. * * @param array $tree Дерево. * @param int $tplType Тип шаблона (список, extjs дерево, extjs дерево в таблице). * @param boolean $cache Нужно ли кэширование шаблона. * @param string $dir Дирректория для кэша шаблона. */ public function __construct($tree, $tplType=TreeTpl::UL, $cache=null, $dir=null){ switch ($tplType) { case TreeTpl::UL: parent::__construct(VCROOT."/Templates/tree.tpl", $cache, $dir); break; case TreeTpl::EXTJS: parent::__construct(VCROOT."/Templates/extJSTree.tpl", $cache, $dir); break; case TreeTpl::EXTJSADV:parent::__construct(VCROOT."/Templates/extJSAdvTree.tpl", $cache, $dir); break; default: parent::__construct(VCROOT."/Templates/tree.tpl", $cache, $dir); } $this->tree=$tree; } } 2)Шаблоны: Просто дерево PHP: <ul> <?php foreach($tree as $child) { ?> <li> <?php echo $child['name'] ?> <?php if($child['tree'] && is_array($child['tree'])) { ?> <?php echo new TreeTpl($child['tree']);?> <?php } ?> </li> <?php } ?> </ul> Просто ExtJS дерево PHP: <?php if (!empty($tree)) {?> children: [{ <?php $first=true; ?> <?php foreach($tree as $child) { ?> <?php if ($first) $first=false; else { ?> ,{ <?php } ?> text: "<?php echo $child['name']?>", expanded: false, <?php if($child['tree'] && is_array($child['tree'])) { ?> <?php echo new TreeTpl($child['tree'], TreeTpl::EXTJS);?> <?php }else {?> leaf: true <?php } ?> } <?php } ?> ] <?php }else { ?> leaf: true <?php } ?> Сложное ExtJS дерево PHP: <?php if (!empty($tree)) {?> children: [{ <?php $first=true; ?> <?php foreach($tree as $child) { ?> <?php if ($first) $first=false; else {?> ,{ <?php } ?> text: "<?php echo $child['name']?>", <?php foreach($child as $key=>$data) {?> <?php if($key!="tree" && $key!="name") {?> <?php echo $key;?>: "<?php echo $data;?>", <?php } ?> <?php } ?> expanded: false, <?php if(isset($child['tree']) && is_array($child['tree'])) { ?> <?php echo new TreeTpl($child['tree'], TreeTpl::EXTJSADV);?> <?php }else {?> leaf: true <?php } ?> } <?php } ?> ] <?php }else { ?> leaf: true <?php } ?> Остальной код для ExtJS дерева [js]Ext.onReady(function(){ if(Ext.get("tree")) { var arrCh={<?php echo $tree; ?>}; var treeRoot = new Ext.tree.AsyncTreeNode({ children:arrCh.children[0].children }); var tree = new Ext.tree.TreePanel({ id: 'exTree', renderTo:'tree', title: "", trackMouseOver:false, singleExpand:true, autoHeight:true, border:false, root: treeRoot, rootVisible:false, lines:false, enableDD:false, loader: new Ext.tree.TreeLoader({ preloadChildren: true, baseAttrs:{ singleClickExpand:true }, }), frame:false }); } }); [/js] Так же сложное ExtJS дерево можно использовать для вот этого http://dev.sencha.com/deploy/dev/exampl ... egrid.html
Volt(220) нифига не понимаю, пойду забурюсь в ман... мне вот как раз примерно вот это и нужно... засада
Попытался применить эту версию на практике и сразу возникла проблема - идентификатор корня не равен 1. Решил. В итоге в конструктор передается массив имен как и советовал Кос. Последняя версия: http://code.google.com/p/voltcore/sourc ... sses/Trees Тесты, из которых можно вытащить примеры создания и манипулирования: http://code.google.com/p/voltcore/sourc ... sses/Tests
Volt(220) а если у меня допустим максимальная вложенность 3 и из данных нужно хранить только названия узлов можно использовать как вот тут первый вариант или он совсем негодный? http://gsbelarus.com/gs/modules.php?nam ... le&sid=314
Первый это где тупо ссылка на родителя? Собственно это и есть Adjacency List. Если объединить мои две таблицы ALTree и ALTreeData, то получиться одна таблица. Насколько я понял большинство так и хранит. Возможно даже конструкция типа: Код (Text): $tree= new ALTree("ALTree", "id", "pid", "ALTree", 'id', "content", null, $DBCon); будет с ней работать. Я начал изучение деревьев с http://phpclub.ru/faq/Tree . И там говориться что стоит разделять дерево и данные. Или в чем вопрос?
Сделал Nested Sets. Нужно будет прокомментировать ключевые моменты. Явно требуется вводить класс TreeNode.
tenshi Не, не... Есть задача - работать с деревьями, которые хранятся в базе. Вот я и спрашиваю: как реализовать данную задачу, с данным интерфейсом проще?
abstract public function fetchAssoc($res=null); abstract public function fetchObj($res=null); вот зачем фетчить во всех вариантах? лучше выбрать что-нибудь одно. и так далее.....
Затем что в разных ситуациях нужны разные данные. Так, например, ExtJS настроен на получение массивов объектов. Мне удобно работать с ассоциативными массивами. И пару раз мне попадался вариант, когда лучше разобрать в нумерованный массив. Ну и да этот топик в основном посвящен деревьям, потому я ожидал отзывов именно по ..Tree классам, хотя я не против конструктивной критики и по другим моим решениям.
$object= (object)$array; $values= array_values( $array ); чтобы ими воспользоваться нужно сначала реализовать этот громоздкий интерфейс. зачем? фактически их основная задача - сформировать запрос по параметрам - для этого достаточно одной лишь функции экранирования данных.