За последние 24 часа нас посетили 18173 программиста и 1684 робота. Сейчас ищут 1094 программиста ...

Деревья.

Тема в разделе "Решения, алгоритмы", создана пользователем Volt(220), 22 сен 2010.

  1. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    Написал классы для работы с деревьями. На данный момент реализована работа с:
    - 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:
    1. <?php
    2.     /**
    3.      * Абстрактный класс работы с деревьями. От него наследуют классы для работы с конкретным способом хранения дерева.
    4.      *
    5.      * <p>Предполагается, что существуют две таблицы. Первая - таблица имен. В ней хранятся идентификаторы, имена и порядок сортировки узлов дерева.
    6.      * Вторая - собственно таблица с деревом.</p>
    7.      * <p>Если используется возможность идентифицировать узел по имени в таблице имен, то имя в таблице имен должно быть уникальным.</p>
    8.      * <p>Сортировка:</p>
    9.      * <p>Элементам присваивается номер по-порядку в пределах одного родителя.</p>
    10.      *
    11.      * @TODO Больше возможностей (выбор пути, выбор уровня)
    12.      *
    13.      * @author Костин Алексей Васильевич aka Volt(220)
    14.      * @copyright Copyright (c) 2010, Костин Алексей Васильевич
    15.      * @license [url=http://www.gnu.org/licenses/gpl-3.0.html]http://www.gnu.org/licenses/gpl-3.0.html[/url] GNU Public License
    16.      * @package classes
    17.      * @subpackage Trees
    18.      * @abstract
    19.      */
    20.     abstract class DBTree{
    21.  
    22.         /**
    23.          * Имен не передано.
    24.          * @var int
    25.          */
    26.         const NO_NAME=0;
    27.  
    28.         /**
    29.          * Передано имя потомка.
    30.          * @var int
    31.          */
    32.         const CHILD_NAME=1;
    33.        
    34.         /**
    35.          * Передано имя родителя.
    36.          * @var int
    37.          */
    38.         const PARENT_NAME=2;
    39.        
    40.         /**
    41.          * Передано оба имени.
    42.          * @var int
    43.          */
    44.         const BOTH_NAME=3;
    45.        
    46.         /**
    47.          * Увеличивать порядковые номера.
    48.          * @var int
    49.          */
    50.         const MOVE_DOWN=0;
    51.        
    52.         /**
    53.          * Уменьшать  порядковые номера.
    54.          * @var int
    55.          */
    56.         const MOVE_UP=1;
    57.        
    58.         /**
    59.          * Таблица, в которой лежит дерево.
    60.          * @var string
    61.          */
    62.         protected $table;
    63.  
    64.         /**
    65.          * Имя таблицы, в которой содержатся имена узлов.
    66.          * @var string
    67.          */
    68.         protected $nameTable;
    69.        
    70.         /**
    71.          * Имя поля с идентификаторами.
    72.          * @var string
    73.          */
    74.         protected $idField;
    75.        
    76.         /**
    77.          * Имя поля, в котором содержатся имена узлов.
    78.          * @var string
    79.          */
    80.         protected $nameField;
    81.  
    82.         /**
    83.          * Имя поля, в котором содержатся идентификаторы узлов таблицы имен.
    84.          * @var string
    85.          */
    86.         protected $idNameField;
    87.        
    88.         /**
    89.          * Имя поля, по которому происходит сортировка.
    90.          * @var string
    91.          */
    92.         protected $orderField;
    93.        
    94.         /**
    95.          * Объект для работы с БД
    96.          * @var SQLDB
    97.          */
    98.         protected $DB;
    99.    
    100.         /**
    101.          * Конструктор.
    102.          *
    103.          * @param string $tab Таблица, в которой лежит дерево.
    104.          * @param string $idName Имя поля с идентификаторами.
    105.          * @param string $idParName Имя поля с идентификаторами родителей.
    106.          * @param string $nameTab Имя таблицы, в которой содержатся имена узлов.
    107.          * @param string $nameField Имя поля, в котором содержатся имена узлов.
    108.          * @param string $orderField Имя поля, по которому происходит сортировка.
    109.          * @param string $orderType Тип сортировки.
    110.          * @param string $DBCon Объект для работы с БД.
    111.          */
    112.         public function __construct($tab, $idName, $nameTab=null, $idNameField='id', $nameField=null, $orderField=null, $DBCon=null){
    113.             $this->DB=$DBCon ? $DBCon : SQLDBFactory::getDB();
    114.             $this->table=$this->DB->escapeKeys($tab);
    115.             $this->idField=$this->DB->escapeKeys($idName);
    116.             $this->nameTable=$this->DB->escapeKeys($nameTab);
    117.             $this->idNameField=$this->DB->escapeKeys($idNameField);
    118.             $this->nameField=$this->DB->escapeKeys($nameField);
    119.             $this->orderField=$orderField ? $this->DB->escapeKeys($orderField) : null;
    120.         }
    121.                
    122.         /**
    123.          * Возвращает запрос для выбора идентифкаторов всех непосредственных потомков родителя $idParent.
    124.          *
    125.          * @param mixed $idParent Идентификатор родителя.
    126.          * @return string Запрос для выбора потомков.
    127.          */
    128.         abstract protected function getChildsQuery($idParent);
    129.        
    130.         /**
    131.          * Возвращает следующий порядковый номер для родителя $idParent.
    132.          *
    133.          * @param mixed $idParent Идентификатор родителя.
    134.          * @return int Следующий порядковый номер.
    135.          */
    136.         abstract protected function getFamilyNextNum($idParent);
    137.        
    138.         /**
    139.          * Возвращает запросы для вставки нового листа в дерево.
    140.          *
    141.          * @param int $idChild Идентификатор того, у кого меняем родителя.
    142.          * @param int $idParent Идентификатор нового родителя.
    143.          * @return array Запросы для вставки нового листа в дерево.
    144.          */
    145.         abstract protected function getAddInsert($idChild, $idParent);
    146.  
    147.         /**
    148.          * Возвращает запрос для нахождения непосредственного родителя.
    149.          *
    150.          * @param int $idChild Идентификатор того, у кого меняем родителя.
    151.          * @return string Запрос для нахождения непосредственного родителя.
    152.          */
    153.         abstract protected function getSelectParent($idChild);
    154.        
    155.         /**
    156.          * Выполняет запросы для смены родителя у узла.
    157.          *
    158.          * @param int $idChild Идентификатор того, у кого меняем родителя.
    159.          * @param int $idParent Идентификатор нового родителя.
    160.          * @param int $orderNum Номер вставляемого узла по-порядку для сортировки.
    161.          * @throws SqlException При ошибке работы с базой.
    162.          */
    163.         abstract protected function doChangePar($idChild, $idParent);
    164.  
    165.         /**
    166.          * Выполняет запросы для удаления поддерева.
    167.          *
    168.          * @param int $idChild Идентификатор того, у кого меняем родителя.
    169.          * @throws SqlException При ошибке работы с базой.
    170.          */
    171.         abstract protected function doDeleteSubTree($idChild);
    172.        
    173.         /**
    174.          * Вытаскивает дерево из БД и создает соответствующий массив.
    175.          *
    176.          * @param array $extraFields дополнительные поля из таблицы с именами.
    177.          *      Ключи массива - псевдонимы полей, которые станут ключами результирующего массива.
    178.          *      Значения полей массива - имена полей для выборки или подзапрос типа (select smth from table where ...).  
    179.          * @param mixed $id Идентификатор корня поддерева. Если не указан, то возвращается все дерево.
    180.          * @return array Массив с деревом.
    181.          *      Индексами этого массива является порядковый номер узла в уровне, начиная с 0, без пропусков.
    182.          *      Узел – это массив, в которм содержатся следующие элементы:
    183.          *          id – идентификатор узла дерева
    184.          *          name – имя узла дерева
    185.          *          поля переданные в $extraFields со значениями из БД
    186.          *          tree – список дочерних узлов для этого узла. Если у этого узла нет дочерних узлов, то здесь содержится пустой массив.
    187.          * @throws SqlException При ошибке работы с базой.
    188.          * @throws FormatException Если указаны не все поля.
    189.          */
    190.         public abstract function getTree($extraFields=null, $subTreeRoot=1);
    191.        
    192.         /**
    193.          * Определяет передано ли имя ребенка или его идентификатор.
    194.          *
    195.          * @param int $haveNames Определяет, какие параметры считать именами.
    196.          * @return bool true - если указано, что передано имя ребенка, false - в противном случае.
    197.          */
    198.         protected function haveChildName($haveNames){
    199.             return $haveNames & DBTree::CHILD_NAME;
    200.         }
    201.  
    202.         /**
    203.          * Определяет передано ли имя родителя или его идентификатор.
    204.          *
    205.          * @param int $haveNames Определяет, какие параметры считать именами.
    206.          * @return bool true - если указано, что передано имя родителя, false - в противном случае.
    207.          */
    208.         protected function haveParentName($haveNames){
    209.             return $haveNames & DBTree::PARENT_NAME;
    210.         }
    211.        
    212.         /**
    213.          * Возвращает идентификатор по имени.
    214.          *
    215.          * Если в $haveNames указано, что передано имя, то метод вернет идентификатор записи по данному имени,
    216.          * иначе метод вернет переданное имя.
    217.          *
    218.          * @param mixed $name Имя, по которому нужно определить идентификатор.
    219.          * @param int $haveNames Определяет, какие параметры считать именами.
    220.          * @param bool $child Кого искать ребенока(true) или родителя(false).
    221.          * @return mixed Идентификатор записи.
    222.          * @throws SqlException При ошибке работы с базой.
    223.          */
    224.         protected function getIdByName($name, $haveNames, $child=true){
    225.             $id=$name;
    226.             $dbId=$this->DB->escapeString($name);
    227.             $sql="select id from $this->nameTable where $this->nameField=$dbId";
    228.             if ($child){
    229.                 if ($this->haveChildName($haveNames)){
    230.                     $id=$this->DB->getVal($sql);
    231.                 }
    232.             }else{
    233.                 if ($this->haveParentName($haveNames)){
    234.                     $id=$this->DB->getVal($sql);
    235.                 }
    236.             }
    237.             if (is_null($id) || $id===false) throw new SqlException("Идентификатор не найден","Нет данных",$sql);
    238.             return $id;
    239.         }
    240.  
    241.         /**
    242.          * Возвращает номер по-порядку для узла с идентификатором $id.
    243.          *
    244.          * @param mixed $idParent Идентификатор узла.
    245.          * @return int Номер узла по-порядку.
    246.          * @throws SqlException При ошибке работы с базой.
    247.          */
    248.         public function getOrderNum($id){
    249.             $orderNum=$this->DB->getVal("select $this->orderField from $this->nameTable where $this->idNameField=$id");
    250.             return $orderNum+0;
    251.         }
    252.    
    253.         /**
    254.          * Подготовливает таблицу для вставки нового элемента или изменения существующего порядка.  
    255.          *
    256.          * @param mixed $idParent Идентификатор родителя, того элемента для которого подготавливается новый порядок
    257.          * @param int $orderNum Новый желаемый порядковый номер потомка. Если не передан, то вычисляется.
    258.          * @return int Новый порядковый номер потомка.
    259.          * @throws SqlException При ошибке работы с базой.
    260.          */
    261.         protected function prepareForNewOrder($idParent, $orderNum=null){
    262.             $sorder=$orderNum+0;
    263.             if ($sorder>0){
    264.                 $this->familyMove($idParent, $sorder);
    265.             }else{
    266.                 $sorder=$this->getFamilyNextNum($idParent);
    267.             }
    268.             return $sorder;
    269.         }
    270.        
    271.         /**
    272.          * Изменяет порядковый номер $count элементов с позиции $startNum на $positions позиций у родителя $idParent.
    273.          *
    274.          * @param mixed $idParent Идентификатор родителя
    275.          * @param int $startNum С какой позиции начинать.
    276.          * @param int $endNum На какой позиции закончить.
    277.          * @param int $direction Куда двигать записи.
    278.          * @param $positions На сколько позиций смещать.
    279.          * @throws SqlException При ошибке работы с базой.
    280.          */
    281.         protected function familyMove($idParent, $startNum, $direction=DBTree::MOVE_DOWN, $endNum=null, $positions=1){
    282.             $startNum+=0;
    283.             $endNum+=0;
    284.             if ($startNum==$endNum) return;
    285.             $set='';
    286.             $where=$this->idNameField." in (".$this->getChildsQuery($idParent).")";
    287.            
    288.             if ($direction==DBTree::MOVE_DOWN){
    289.                 $symbol="+";
    290.             }else{
    291.                 $symbol="-";
    292.             }
    293.            
    294.             $where.=" and $this->orderField>=$startNum";
    295.             if ($endNum){
    296.                 $where.=" and $this->orderField<=$endNum";
    297.             }
    298.             $set="$this->orderField=".$this->orderField.$symbol.$positions;
    299.            
    300.             $this->DB->update("update $this->nameTable  set $set where $where");
    301.         }
    302.        
    303.         /**
    304.          * Вставляет запись о ребенке в таблицу имен.
    305.          *
    306.          * @param string $idChild Имя ребенка.
    307.          * @param mixed $idParent Идентификатор родителя.
    308.          * @param int $orderNum Номер ребенка по порядку.
    309.          * @return string Идентификатор ребенка.
    310.          * @throws SqlException При ошибке работы с базой.
    311.          */
    312.         protected function insertChild($idChild,$idParent=null, $orderNum=null){
    313.             if ($this->orderField){
    314.                 $sorder=$this->prepareForNewOrder($idParent, $orderNum);
    315.                 $idChild=$this->DB->insert("insert into $this->nameTable($this->nameField, $this->orderField) values($idChild, $sorder)");
    316.             }else{
    317.                 $idChild=$this->DB->insert("insert into $this->nameTable($this->nameField) values($idChild)");
    318.             }
    319.             return $idChild;
    320.         }
    321.        
    322.         /**
    323.          * Возврщает строку для выборки дополнительных полей.
    324.          *
    325.          * @param array $extraFields дополнительные поля из таблицы с именами.
    326.          *      Ключи массива - псевдонимы полей, которые станут ключами результирующего массива.
    327.          *      Значения полей массива - имена полей для выборки или подзапрос типа (select smth from table where ...).  
    328.          * @return string Строка для выборки дополнительных полей.
    329.          */
    330.         protected function extraFieldsToQueryString($extraFields){
    331.             if(!$extraFields || !is_array($extraFields)) return '';
    332.             $rez="";
    333.             foreach($extraFields as $name=>$field){
    334.                 if (strPos($field, "(")===0){
    335.                     $rez .= ", $field as $name";
    336.                 }else{
    337.                     $rez .= ", c.$field as $name";
    338.                 }
    339.             }
    340.             return $rez;
    341.         }
    342.        
    343.         /**
    344.          * Добавляет новый лист в дерево.
    345.          *  
    346.          * @param mixed $id Идентификатор потомка или уникальная строка для вставки в таблицу имен.
    347.          * @param mixed $parId Идентификатор родителя или уникальная строка для поиска в таблице имен.
    348.          * @param int $haveNames Определяет, какие параметры считать именами.
    349.          * @param int $orderNum Номер нового узла по-порядку для сортировки.
    350.          * @throws SqlException При ошибке работы с базой.
    351.          * @throws FormatException Если указаны не все поля.
    352.          */
    353.         public function add($id, $parId, $haveNames=DBTree::NO_NAME, $orderNum=null){
    354.             if ($haveNames!=DBTree::NO_NAME && (!$this->nameTable || !$this->nameField)) throw new FormatException("Недостаточно данных.","Указаны не все данные");
    355.             $DB=$this->DB;
    356.             try{
    357.                 $DB->startTran();
    358.                
    359.                 $idChild=$DB->escapeString($id);
    360.                 $idParent=$this->getIdByName($parId, $haveNames, false);
    361.  
    362.                 if ($this->haveChildName($haveNames)){
    363.                     $idChild=$this->insertChild($idChild,$idParent,$orderNum);
    364.                 }
    365.  
    366.                 $insert=$this->getAddInsert($idChild, $idParent);
    367.                 foreach($insert as $query){
    368.                     $DB->insert($query);
    369.                 }
    370.                
    371.                 $DB->commit();
    372.             }catch(SqlException $e){
    373.                 $DB->rollback();
    374.                 throw $e;
    375.             }
    376.         }
    377.            
    378.         /**
    379.          * Устанавливает номер узла по-порядку для сортировки.
    380.          *
    381.          * @param mixed $id Идентификатор потомка или уникальная строка из таблицы имен.
    382.          * @param int $orderNum Номер нового узла по-порядку для сортировки.
    383.          * @param int $haveNames Определяет, какие параметры считать именами.
    384.          * @throws SqlException При ошибке работы с базой.
    385.          * @throws FormatException Если указаны не все поля.
    386.          */
    387.         public function setOrderNum($id, $orderNum, $haveNames=DBTree::NO_NAME){
    388.             if (!$this->nameTable || !$this->nameField || !$this->orderField) throw new FormatException("Недостаточно данных.","Указаны не все данные");
    389.            
    390.             $idChild=$this->getIdByName($id, $haveNames);
    391.             $idParent=$this->getParent($idChild);
    392.  
    393.             $newOrder=$orderNum+0;
    394.             if ($newOrder<1){
    395.                 $newOrder=$this->getFamilyNextNum($idParent)-1;
    396.             }
    397.            
    398.             $oldOrder=$this->getOrderNum($idChild);
    399.             if ($oldOrder==$newOrder) return;
    400.            
    401.             $DB=$this->DB;
    402.             try{
    403.                 $DB->startTran();
    404.                
    405.                 if ($oldOrder<$newOrder){
    406.                     $this->familyMove($idParent, $oldOrder, DBTree::MOVE_UP, $newOrder);
    407.                 }else{
    408.                     $this->familyMove($idParent, $newOrder, DBTree::MOVE_DOWN, $oldOrder);
    409.                 }
    410.                
    411.                 $DB->update("update $this->nameTable set $this->orderField=$newOrder where $this->idNameField=$idChild");
    412.  
    413.                 $DB->commit();
    414.             }catch(SqlException $e){
    415.                 $DB->rollback();
    416.                 throw $e;
    417.             }
    418.         }
    419.        
    420.         /**
    421.          * Изменяет порядковый номер узла.
    422.          *
    423.          * @param mixed $id Идентификатор узла.
    424.          * @param int $positions На сколько позиций перемещать.
    425.          * @param int $direction Куда перемещать.
    426.          * @param int $haveNames Определяет, какие параметры считать именами.
    427.          */
    428.         public function moveNode($id, $positions=1, $direction=DBTree::MOVE_DOWN, $haveNames=DBTree::NO_NAME){
    429.             if (!$this->nameTable || !$this->nameField || !$this->orderField) throw new FormatException("Недостаточно данных.","Указаны не все данные");
    430.            
    431.             $idChild=$this->getIdByName($id, $haveNames);
    432.             $oldNum=$this->getOrderNum($idChild);
    433.             if ($direction==DBTree::MOVE_UP){
    434.                 $newNum=$oldNum-$positions;
    435.                 $newNum=max($newNum,1);
    436.             }else{
    437.                 $newNum=$oldNum+$positions;
    438.             }
    439.             $this->setOrderNum($idChild, $newNum);
    440.         }
    441.        
    442.         /**
    443.          * Меняет родителя у узла.
    444.          *
    445.          * @param mixed $id Идентификатор того, у кого меняем родителя.
    446.          * @param mixed $parId Идентификатор нового родителя.
    447.          * @param int $haveNames Определяет, какие параметры считать именами.
    448.          * @param int $orderNum Номер узла по-порядку для сортировки.
    449.          * @throws SqlException При ошибке работы с базой.
    450.          * @throws FormatException Если указаны не все поля.
    451.          */
    452.         public function changePar($id, $parId, $haveNames=DBTree::NO_NAME, $orderNum=null){
    453.             if ($haveNames!=DBTree::NO_NAME && (!$this->nameTable || !$this->nameField)) throw new FormatException("Недостаточно данных.","Указаны не все данные");
    454.             if ($this->orderField && (!$this->nameTable || !$this->nameField || !$this->orderField)) throw new FormatException("Недостаточно данных.","Указаны не все данные");
    455.             $DB=$this->DB;
    456.             try{
    457.                 $idChild=$this->getIdByName($id, $haveNames);
    458.                 $idParent=$this->getIdByName($parId, $haveNames, false);
    459.                 $oldParent=$this->getParent($idChild);
    460.                
    461.                 if ($idChild==$idParent){
    462.                     throw new FormatException("Нельзя замкнуть узел на себя.","Неверные данные");
    463.                 }
    464.                 if ($oldParent==$idParent){
    465.                     if ($orderNum){
    466.                         $this->setOrderNum($idChild, $orderNum);
    467.                     }
    468.                     return;
    469.                 }
    470.                
    471.                 $DB->startTran();
    472.                 if ($this->orderField){
    473.                     $oldNum=$this->getOrderNum($idChild);
    474.                     $this->familyMove($oldParent, $oldNum+1, DBTree::MOVE_UP);
    475.                     $sorder=$this->prepareForNewOrder($idParent, $orderNum);
    476.                     $this->DB->update("update $this->nameTable set $this->orderField=$sorder where $this->idNameField=$idChild");
    477.                 }
    478.                
    479.                 $this->doChangePar($idChild, $idParent);
    480.                
    481.                
    482.                 $DB->commit();
    483.             }catch(SqlException $e){
    484.                 $DB->rollback();
    485.                 throw $e;
    486.             }
    487.         }
    488.  
    489.         /**
    490.          * Удаляет поддерево.
    491.          *
    492.          * @param mixed $id Идентификатор вершины поддерева или уникальная строка в таблице имен.
    493.          * @param int $haveNames Определяет, считать ли $id именем.
    494.          * @throws SqlException При ошибке работы с базой.
    495.          * @throws FormatException Если указаны не все поля.
    496.          */
    497.         public function deleteSubTree($id, $haveNames=DBTree::NO_NAME){
    498.             if (!$this->nameTable || !$this->nameField) throw new FormatException("Недостаточно.","Указаны не все данные");
    499.             $DB=$this->DB;
    500.             try{
    501.                 $idChild=$this->getIdByName($id,$haveNames);
    502.                 if ($idChild==1) throw new FormatException('Нельзя удалить корень дерева','Неверные данные');
    503.                
    504.                 if ($this->orderField){
    505.                     $oldParent=$this->getParent($idChild);
    506.                     $oldNum=$this->getOrderNum($idChild);
    507.                     $this->familyMove($oldParent, $oldNum+1, DBTree::MOVE_UP);
    508.                 }
    509.                
    510.                 $DB->startTran();
    511.                 $this->doDeleteSubTree($idChild);
    512.                
    513.                 $DB->commit();
    514.             }catch(SqlException $e){
    515.                 $DB->rollback();
    516.                 throw $e;
    517.             }
    518.         }
    519.        
    520.         /**
    521.          * Возвращает непосредственного родителя узла.
    522.          *
    523.          * @param mixed $id Идентификатор потомка.
    524.          * @param int $haveNames Определяет, считать ли $id именем.
    525.          * @return string Идентификатор предка.
    526.          * @throws SqlException При ошибке работы с базой.
    527.          */
    528.         public function getParent($id, $haveNames=DBTree::NO_NAME){
    529.             if (!$this->nameTable || !$this->nameField) throw new FormatException("Недостаточно данных.","Указаны не все данные");
    530.  
    531.             $idChild=$this->getIdByName($id, $haveNames);
    532.             if($idChild==1) return 1;
    533.            
    534.             $select=$this->getSelectParent($idChild);
    535.             $pid=$this->DB->getVal($select);
    536.  
    537.             if (is_null($pid) || $pid===false) throw new SqlException("Идентификатор не найден","Нет данных",$select);
    538.             return $pid;
    539.         }
    540.     }
    Adjacency List - ALTree:
    PHP:
    1. <?php
    2.     /**
    3.      * Класс для работы с деревом. Дерево хранится по принципу Adjacency List.
    4.      *
    5.      * Данная реализация принципа предполагает:
    6.      * 1)Существует корневой элемент с id=1.
    7.      * 2)Корневой элемент ссылается на себя как на родителя.
    8.      *
    9.      * @package classes
    10.      * @subpackage Trees
    11.      */
    12.     class ALTree extends DBTree{
    13.  
    14.         /**
    15.          * Имя поля с идентификаторами родителей.
    16.          * @var string
    17.          */
    18.         private $idParField;
    19.    
    20.         /**
    21.          * @param string $idParName Имя поля с идентификаторами родителей.
    22.          */
    23.         public function __construct($tab, $idName, $idParName, $nameTab=null, $idNameField='id', $nameField=null, $orderField=null, $DBCon=null){
    24.             parent::__construct($tab, $idName, $nameTab, $idNameField, $nameField, $orderField, $DBCon);
    25.             $this->idParField=$this->DB->escapeKeys($idParName);
    26.         }
    27.  
    28.         protected function getChildsQuery($idParent){
    29.             return "select $this->idField from $this->table where $this->idParField=$idParent";
    30.         }      
    31.  
    32.         protected function getFamilyNextNum($idParent){
    33.             $sorder=$this->orderField;
    34.             $table=$this->nameTable;
    35.             $tree=$this->table;
    36.             $id=$this->idNameField;
    37.             $idChild=$this->idField;
    38.             $idPar=$this->idParField;
    39.            
    40.             $num=$this->DB->getVal("select max($sorder) from $table join $tree on $table.$id=$tree.$idChild where $idPar=$idParent");
    41.             return $num+1;
    42.         }
    43.        
    44.         protected function getAddInsert($idChild, $idParent){
    45.             return array("insert into $this->table($this->idField, $this->idParField) values ($idChild, $idParent)");
    46.         }
    47.        
    48.         protected function getSelectParent($idChild){
    49.             return "select $this->idParField from $this->table where $this->idField=$idChild";
    50.         }
    51.                
    52.         protected function doChangePar($idChild, $idParent){
    53.             $this->DB->update("update $this->table set $this->idParField=$idParent where $this->idField=$idChild");
    54.         }
    55.    
    56.         protected function doDeleteSubTree($idChild){
    57.             $DB=$this->DB;
    58.             $numDeleted=0;
    59.             $numDeleted=$DB->delete("delete from $this->table where $this->idField=$idChild or $this->idParField=$idChild");
    60.             while ($numDeleted>0){
    61.                 $ids=implode(",",$DB->getColumn("select $this->idNameField from $this->nameTable where $this->idNameField not in (select $this->idField from $this->table)"));
    62.                 $numDeleted=$DB->delete("delete from $this->table where $this->idParField in ($ids)");
    63.             }
    64.             $DB->delete("delete from $this->nameTable where $this->idNameField not in (select $this->idField from $this->table)");
    65.         }
    66.        
    67.         public function getTree($extraFields=null, $subTreeRoot=1){
    68.             if (!$this->nameTable || !$this->nameField) throw new FormatException("Недостаточно данных для создания дерева.","Указаны не все данные");
    69.             $sFiled= $this->orderField ? "c.$this->orderField," : '';
    70.             $extra= $this->extraFieldsToQueryString($extraFields);
    71.                                    
    72.             //Переприсваивание для создания более читаемого запроса
    73.             $tree=$this->table;
    74.             $f=$this->idField;
    75.             $id=$this->idNameField;
    76.             $pid=$this->idParField;
    77.             $name=$this->nameField;
    78.             $tab=$this->nameTable;
    79.            
    80.             //Выбор
    81.             $sql="select c.$id as cid, c.$name as cname, t.$pid as pid $extra
    82.                 from $tree as t join $tab as c on t.$f=c.$id
    83.                 order by $sFiled c.$name";
    84.            
    85.             $DB=$this->DB;
    86.             $DB->select($sql);
    87.            
    88.             //Запись в массив
    89.             $path=array();
    90.             while($row=$DB->fetchAssoc()){
    91.                 if(!isset($path[$row["cid"]])){
    92.                     $path[$row["cid"]]=array("name"=>$row["cname"], "id"=>$row["cid"], "tree"=>array());
    93.                     if ($extra){
    94.                         foreach($extraFields as $k=>$v){
    95.                             $path[$row["cid"]][$k]=$row[$k];
    96.                         }
    97.                     }
    98.                                     }
    99.                 else{
    100.                     $path[$row["cid"]]["name"]=$row["cname"];
    101.                     $path[$row["cid"]]["id"]=$row["cid"];
    102.                     if ($extra){
    103.                         foreach($extraFields as $k=>$v){
    104.                             $path[$row["cid"]][$k]=$row[$k];
    105.                         }
    106.                     }
    107.                 }
    108.                 if ($row["pid"]!=$row["cid"]){
    109.                     $path[$row["pid"]]["tree"][]=&$path[$row["cid"]];
    110.                 }
    111.             }
    112.             $tree=array();
    113.             if ($path && isset($path[$subTreeRoot])){
    114.                 $tree[0]=$path[$subTreeRoot];
    115.             }
    116.             return $tree;
    117.         }
    118.     }
    Redundant Adjacency List - FHTree:
    PHP:
    1. <?php
    2.     /**
    3.      * Класс для работы с деревом. Дерево хранится по принципу Full Hierarchy (Redundant Adjacency List по другим источникам).
    4.      *
    5.      * Данная реализация принципа предполагает:
    6.      * 1)Существует корневой элемент с id = 1.
    7.      * 2)В таблице есть три колонки: идентификатор узла, идентификатор родителя, уровень.
    8.      * 3)У узла создается запись для каждого родителя с указанием уровня (первый родитель - 1, родитель родителя - 2 и т.д.).
    9.      * 4)Каждый узел, кроме корня, имеет как минимум 2 записи - ссылка на себя с уровнем 0 и ссылка на корневой элемент с id = 1.
    10.      *
    11.      * @package classes
    12.      * @subpackage Trees
    13.      */
    14.     class FHTree extends DBTree{
    15.  
    16.         /**
    17.          * Имя поля с идентификаторами родителей.
    18.          * @var string
    19.          */
    20.         private $idParField;
    21.    
    22.         /**
    23.          * Имя поля уровня.
    24.          * @var string
    25.          */
    26.         private $levelField;
    27.    
    28.         /**
    29.          * @param string $idParName Имя поля с идентификаторами родителей.
    30.          * @param string $levelName Имя поля уровня.
    31.          */
    32.         public function __construct($tab, $idName, $idParName, $levelName, $nameTab=null,  $idNameField='id', $nameField=null, $orderField=null, $DBCon=null){
    33.             parent::__construct($tab, $idName, $nameTab,$idNameField, $nameField, $orderField, $DBCon);
    34.             $this->idParField=$this->DB->escapeKeys($idParName);
    35.             $this->levelField=$this->DB->escapeKeys($levelName);
    36.         }
    37.  
    38.         protected function getChildsQuery($idParent){
    39.             return "select $this->idField from $this->table where $this->idParField=$idParent and $this->levelField=1";
    40.         }      
    41.  
    42.         protected function getFamilyNextNum($idParent){
    43.             $sorder=$this->orderField;
    44.             $table=$this->nameTable;
    45.             $tree=$this->table;
    46.             $id=$this->idNameField;
    47.             $idChild=$this->idField;
    48.             $idPar=$this->idParField;
    49.             $level=$this->levelField;
    50.            
    51.             $num=$this->DB->getVal("select max($sorder) from $table join $tree on $table.$id=$tree.$idChild where $idPar=$idParent and $level=1");
    52.             return $num+1;
    53.         }
    54.        
    55.         protected function getAddInsert($idChild, $idParent){
    56.             return array(  
    57.                 "insert into $this->table($this->idField, $this->idParField, $this->levelField)
    58.                     select $idChild, $this->idParField, $this->levelField+1 from $this->table where $this->idField=$idParent",
    59.                 "insert into $this->table($this->idField, $this->idParField, $this->levelField) values($idChild, $idChild, 0)");       
    60.         }
    61.    
    62.         protected function getSelectParent($idChild){
    63.             return "select $this->idParField from $this->table where $this->idField=$idChild and $this->levelField=1";
    64.         }
    65.        
    66.         protected function doChangePar($idChild, $idParent){
    67.             $table=$this->table;
    68.             $f=$this->idField;
    69.             $pid=$this->idParField;
    70.             $level=$this->levelField;
    71.             $DB=$this->DB;
    72.            
    73.             $allChilds="select $f from $table where $pid=$idChild";
    74.             $allParents="select $pid from $table where $f=$idChild and $pid<>$idChild";
    75.                
    76.             $childs="(".implode(",",$DB->getColumn($allChilds)).")";
    77.             $parents="(".implode(",",$DB->getColumn($allParents)).")";
    78.                
    79.             $delete="delete from $table where $f in $childs and $pid in $parents";
    80.        
    81.             $insert="insert into $table($f, $pid, $level)
    82.                 SELECT down.$f, up.$pid, down.$level + up.$level + 1
    83.                 FROM $table as up join $table as down on
    84.                 up.$f = $idParent and down.$pid=$idChild";
    85.        
    86.             $DB->delete($delete);
    87.             $DB->insert($insert);
    88.         }
    89.    
    90.         /**
    91.          * Выполняет запросы для удаления поддерева.
    92.          *
    93.          * @param int $idChild Идентификатор того, у кого меняем родителя.
    94.          * @throws SqlException При ошибке работы с базой.
    95.          */
    96.         protected function doDeleteSubTree($idChild){
    97.             $DB=$this->DB;
    98.            
    99.             $allChilds="select $this->idField from $this->table where $this->idParField=$idChild";
    100.             $childs="(".implode(",",$DB->getColumn($allChilds)).")";
    101.                
    102.             $deleteName="delete from $this->nameTable where $this->idNameField in $childs";
    103.             $delete="delete from $this->table where $this->idField in $childs";
    104.                
    105.             $DB->delete($delete);
    106.             $DB->delete($deleteName);
    107.         }
    108.        
    109.        
    110.    
    111.         public function getTree($extraFields=null, $subTreeRoot=1){
    112.             if (!$this->nameTable || !$this->nameField) throw new FormatException("Недостаточно данных для создания дерева.","Указаны не все данные");
    113.             $sFiled= $this->orderField ? "c.$this->orderField," : '';
    114.             $extra= $this->extraFieldsToQueryString($extraFields);
    115.             //Переприсваивание для создания более читаемого запроса
    116.             $tree=$this->table;
    117.             $f=$this->idField;
    118.             $id=$this->idNameField;
    119.             $pid=$this->idParField;
    120.             $level=$this->levelField;
    121.             $name=$this->nameField;
    122.             $tab=$this->nameTable;
    123.        
    124.             //Выбор
    125.             $sql="  select c.$id as cid, c.$name as cname, par.$id as pid $extra
    126.                     from
    127.                         $tree as t
    128.                         join $tab as c on t.$f=c.$id
    129.                         left outer join $tree as t2 on t.$f=t2.$f and t2.$level=1
    130.                         left outer join $tab as par on par.$id=t2.$pid
    131.                     where t.$pid=$subTreeRoot
    132.                     order by t.$level, $sFiled c.$name";
    133.  
    134.             $this->DB->select($sql);
    135.            
    136.             //Запись в массив
    137.             $path=array();
    138.             while($row=$this->DB->fetchAssoc()){
    139.                 if(!isset($path[$row["cid"]])){
    140.                     $path[$row["cid"]]=array("name"=>$row["cname"], "id"=>$row["cid"], "tree"=>array());
    141.                     if ($extra){
    142.                         foreach($extraFields as $k=>$v){
    143.                             $path[$row["cid"]][$k]=$row[$k];
    144.                         }
    145.                     }
    146.                 }
    147.                 if($row["pid"]){
    148.                     $path[$row["pid"]]["tree"][]=&$path[$row["cid"]];
    149.                 }
    150.             }
    151.             $tree=array();
    152.             if ($path){
    153.                 $tree[0]=reset($path);
    154.             }
    155.             return $tree;
    156.         }
    157.     }
    Создание таблиц(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:
    1. <?php
    2. /*Без сортировки*/
    3. //Создание
    4. $tree= new FHTree("FHTree", "cid", "pid", "level", "FHTreeData",  'id', "content", null, $DBCon);
    5. //Добавление
    6. $tree->add(3,2, DBTree::NO_NAME); //Соответствующее записи уже должны быть в таблице FHTreeData.
    7. $tree->add(4,'1', DBTree::PARENT_NAME);
    8. $tree->add('1.2.1',4,DBTree::CHILD_NAME); //В таблице FHTreeData появиться новая запись.
    9. $tree->add('1.2.2','1.2', DBTree::BOTH_NAME);
    10. //Перемещение поддерева к новому родителю
    11. $tree->changePar('6.1','5', DBTree::BOTH_NAME);
    12. //Удаление поддерева
    13. $tree->deleteSubTree(8);
    14.  
    15. /*С сортировкой*/
    16. $sortTree= new FHTree("FHSortTree", "cid", "pid", "level", "FHSortTreeData",  'id', "content", "sorder", $DBCon);
    17. //Добавление с указанием порядкового номера
    18. $sortTree->add('1.2','1',DBTree::BOTH_NAME,1);
    19. //Изменение порядка
    20. $sortTree->setOrderNum('1.1',1, DBTree::BOTH_NAME); //Перестановка в начало
    21. $sortTree->setOrderNum('1',0, DBTree::BOTH_NAME); //Перестановка в конец
    22. $sortTree->setOrderNum('6',3, DBTree::BOTH_NAME); //Перестановка на конкретное место
    23. $sortTree->moveNode(19,3,DBTree::MOVE_UP); //Перестановка на 3 вверх
    24. $sortTree->moveNode(19,2); //Перестановка на 2 вниз
    25. //Перемещение поддерева к новому родителю с указанием порядкового номера
    26. $sortTree->changePar('3','6.1', DBTree::BOTH_NAME,1);
    27.  
    28. //UPD
    29. //Выбор дерева
    30. $tree->getTree();
    31. //Выбор дерева с дополнительными полями
    32. $tree->getTree(array("price"=>"price", "itemColor"=>"color", "profit"=>"(select sum(price*count) from sales where id_item=c.id)"));
    33. //Выбор поддерева
    34. $tree->getTree(null, 8);
    35.  
     
  2. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    Может ниасилил, но чтобы пересортировать придется создавать новый экземпляр?
     
  3. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    интерфейс для класса, который работает с БД потерялся
     
  4. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    может быть ввести новый класс - нода? Вообще библиотека для работы с деревьями - очень правильная затея
     
  5. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    [vs]
    Что значит пересортировать?
    Для изменения порядкового номера узла дерева - да, придется создавать экземпляр.

    Koc
    Интерфейс работы с БД:
    PHP:
    1.    
    2. <?php
    3. /**
    4.      * Абстрактный класс работы с базой данных. От него наследуют классы для работы с конкретной СУБД.
    5.      *
    6.      * @author Костин Алексей Васильевич aka Volt(220)
    7.      * @copyright Copyright (c) 2010, Костин Алексей Васильевич
    8.      * @license [url=http://www.gnu.org/licenses/gpl-3.0.html]http://www.gnu.org/licenses/gpl-3.0.html[/url] GNU Public License
    9.      * @package classes
    10.      * @subpackage DBClasses
    11.      * @abstract
    12.      */
    13.     abstract class SQLDB {
    14.        
    15.         /**
    16.          * Возвращает код последней ошибки
    17.          *
    18.          * @return string Код последней ошибки
    19.          */
    20.         abstract public function getErrorCode();
    21.        
    22.         /**
    23.          * Возвращает последнее сообщение об ошибке
    24.          *
    25.          * @return string Сообщение об ошибке
    26.          */
    27.         abstract public function getErrorMsg();
    28.        
    29.         /**
    30.          * Возвращает количество строк, обработанных последним запросом.
    31.          *
    32.          * @access protected
    33.          * @return int количество строк, обработанных последним запросом
    34.          */
    35.         abstract public function affectRows();
    36.  
    37.         /**
    38.          * Начинает транзакцию
    39.          *
    40.          * Отключает autocommit и посылает серверу команду начать транзакцию.
    41.          */
    42.         abstract public function startTran();
    43.  
    44.         /**
    45.          * Подтверждает транзакцию
    46.          *
    47.          * Подтверждает транзакцию и включает autocommit.
    48.          */
    49.         abstract public function commit();
    50.  
    51.         /**
    52.          * Откатывает транзакцию
    53.          *
    54.          * Производит откат транзакции и включает autocommit.
    55.          */
    56.         abstract public function rollback();
    57.  
    58.         /**
    59.          * Возвращает очередную строку из результата запроса в виде ассоциативного массива
    60.          *
    61.          * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res
    62.          * @return array Ассоциативный массив, содержащий значения из очередной строки результата
    63.          */
    64.         abstract public function fetchAssoc($res=null);
    65.  
    66.         /**
    67.          * Возвращает очередную строку из результата запроса в виде объекта
    68.          *
    69.          * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res
    70.          * @return array Объект, содержащий значения из очередной строки результата
    71.          */
    72.         abstract public function fetchObj($res=null);
    73.  
    74.         /**
    75.          * Возвращает очередную строку из результата запроса в виде пронумерованного массива
    76.          *
    77.          * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res
    78.          * @return array Пронумерованный массив, содержащий значения из очередной строки результата
    79.          */
    80.         abstract public function fetchRow($res=null);
    81.  
    82.         /**
    83.          * Возвращает единственное значение из результата запроса
    84.          *
    85.          * Этот метод следует использовать, когда ожидется выбор единственного значения
    86.          *
    87.          * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res
    88.          * @return mixed Значение из результата запроса
    89.          */
    90.         abstract public function fetchField($res=null);
    91.  
    92.         /**
    93.          * Обрабатывает спецсимволы в строке для безопасного ее использования в запросе
    94.          *
    95.          * @param mixed $str Строка, в которой надо экранировать спецсимволы.
    96.          * @return mixed Строка с экранированными спецсимволы.
    97.          */
    98.         abstract public function escape($str);
    99.        
    100.        
    101.                                             /////////////////////
    102.                                             //Магические методы//
    103.                                             /////////////////////
    104.  
    105.         /**
    106.          * Запрещение клонирования
    107.          * @throws Exception Клонирование запрещено
    108.          */
    109.         public function __clone(){
    110.             throw new Exeption('Клонирование запрещено!');
    111.         }
    112.  
    113.        
    114.         /**
    115.          * Преобразует объект в строковое представление.
    116.          * @return string Строковое представление объекта
    117.          */
    118.         public function __toString(){}
    119.  
    120.         /**
    121.          * Деструктор.
    122.          *
    123.          * Закрывает соединение. Удаляет себя из списка фабрики.
    124.          */
    125.         public function __destruct(){}
    126.                                             ////////////////////
    127.                                             //Сеттеры, геттеры//
    128.                                             ////////////////////
    129.         /**
    130.          * Устанавливает значение {@link factId}
    131.          * @param string $id Новый идентификатор
    132.          */
    133.         public function setId($id){}
    134.        
    135.         /**
    136.          * Возвращает значение {@link factId}
    137.          * @return string Текущий идентификатор
    138.          */
    139.         public function getId(){}
    140.        
    141.         /**
    142.          * Конфигурирует объект в соответствии с массивом конфигурации
    143.          * @throws FormatException
    144.          * @param array $config Массив для установки конфигурации.
    145.          */
    146.         protected function setConfig($config){}
    147.        
    148.        
    149.         /**
    150.          * Возвращает текущую конфигурацию объекта
    151.          * @return array массив с конфигурационными переменными
    152.          */
    153.         public function getConfig(){}
    154.        
    155.         /**
    156.          * Возвращает последний запрос.
    157.          * @return string Последний запрос
    158.          */
    159.         public function getLastQuery(){}
    160.                                                     ///////////
    161.                                                     //Запросы//
    162.                                                     ///////////
    163.         /**
    164.          * Выполняет запрос select.
    165.          *
    166.          * Выполняет запрос $sql. Запрос должен быть запросом типа select.
    167.          * Для предотвращения несанкционированных действий
    168.          * перед выполнением запрос проверяется на наличие оператора union.
    169.          * Если оператор union будет найден, то будет выброшено исключение.
    170.          * Если в запросе необходимо использовать оператор union,
    171.          * то количество таких операторов нужно указать в параметре $numUnion.
    172.          *
    173.          * @throws SqlException, FormatException
    174.          * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора.
    175.          * Если $arr это строка, то она трактуется как sql-запрос для выполнения.  
    176.          * @param string $tab Таблица, в которой выполняется обновление.
    177.          * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска,
    178.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    179.          * @param int $numUnion Количество операторов union в запросе.
    180.          * @return resource Результат запроса
    181.          */                                                
    182.         public function select($arr, $tab=null, $where=null, $numUnion=0){}
    183.  
    184.         /**
    185.          * Выполняет вставку строки в таблицу.
    186.          *
    187.          * @throws SqlException
    188.          * @param mixed $arr Если $arr массив, то ключи массива трактуются как поля таблицы,
    189.          * а соответствующие значения как значения этих полей. Значения проходят обработку {@link escapeString}
    190.          * Если $arr это строка, то она трактуется как sql-запрос для выполнения.  
    191.          * @param string $tab Таблица, в которую нужно вставить значения.
    192.          * @return mixed id только что вставленной записи
    193.          */
    194.         public function insert($arr, $tab=null){}
    195.  
    196.         /**
    197.          * Выполняет обновление данных в таблице.
    198.          *
    199.          * @throws SqlException, FormatException
    200.          * @param mixed $arr Если $arr массив, то ключи массива трактуются как поля таблицы,
    201.          * а соответствующие значения как значения этих полей.
    202.          * Если $arr это строка, то она трактуется как sql-запрос для выполнения.  
    203.          * @param string $tab Таблица, в которой выполняется обновление.
    204.          * @param mixed $where Условие, при котором выполняется обновление, или id записи для обновления,
    205.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    206.          * @return int Количество обновленных строк.
    207.          */
    208.         public function update($arr, $tab=null, $where=null){}
    209.  
    210.         /**
    211.          * Выполняет удаление из таблицы.
    212.          *
    213.          * @throws SqlException, FormatException
    214.          * @param string $tab Таблица из которой удалять или sql-запрос.
    215.          * @param mixed $where Условие, при котором выполняется удаление, или id записи для удаления,
    216.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    217.          * Если $where ничего из вышеперечисленного, то $tab считается sql-запросом.
    218.          * @return int Количество удаленных строк.
    219.          */
    220.         public function delete($tab, $where=null){}
    221.        
    222.         /**
    223.          * Определяет id по уникальному значению.
    224.          *
    225.          * @param string $tab Имя таблицы.
    226.          * @param array $where Массив для определения id. Ключи - имена полей, значения - значения полей.
    227.          * @return mixed Идентификатор.
    228.          */
    229.         public function findOrInsert($tab, $where){}
    230.  
    231.         /**
    232.          * Находит id по уникальному значению.
    233.          *
    234.          * @param string $tab Имя таблицы.
    235.          * @param array $where Массив для определения id. Ключи - имена полей, значения - значения полей.
    236.          * @return mixed Идентификатор.
    237.          */
    238.         public function findId($tab, $where){}
    239.        
    240.                                                 ////////////////////
    241.                                                 //Запросы значений//
    242.                                                 ////////////////////
    243.         /**
    244.          * Возвращает первую строку из результата запроса $sql в виде ассоциативного массива.
    245.          *
    246.          * @throws SqlException, FormatException
    247.          * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора.
    248.          * Если $arr это строка, то она трактуется как sql-запрос для выполнения.  
    249.          * @param string $tab Таблица, в которой выполняется обновление.
    250.          * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска,
    251.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    252.          * @param int $numUnion Количество операторов union в запросе.
    253.          * @return array Ассоциативный массив, содержащий значения из первой строки результата запроса
    254.          */
    255.         public function getAssoc($arr, $tab=null, $where=null, $numUnion=0){}
    256.  
    257.         /**
    258.          * Возвращает первую строку из результата запроса $sql в виде пронумированного массива.
    259.          *
    260.          * @throws SqlException, FormatException
    261.          * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора.
    262.          * Если $arr это строка, то она трактуется как sql-запрос для выполнения.  
    263.          * @param string $tab Таблица, в которой выполняется обновление.
    264.          * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска,
    265.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    266.          * @param int $numUnion Количество операторов union в запросе.
    267.          * @return array Пронумированный массив, содержащий значения из первой строки результата запроса
    268.          */
    269.         public function getRow($arr, $tab=null, $where=null, $numUnion=0){}
    270.  
    271.         /**
    272.          * Возвращает первый столбец из результата запроса $sql в виде пронумированного массива.
    273.          *
    274.          * @throws SqlException, FormatException
    275.          * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора.
    276.          * Если $arr это строка, то она трактуется как sql-запрос для выполнения.  
    277.          * @param string $tab Таблица, в которой выполняется обновление.
    278.          * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска,
    279.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    280.          * @param int $numUnion Количество операторов union в запросе.
    281.          * @return array Пронумированный массив, содержащий значения из первого столбца результата запроса
    282.          */
    283.         public function getColumn($arr, $tab=null, $where=null, $numUnion=0){}
    284.        
    285.         /**
    286.          * Возвращает первую строку из результата запроса $sql в виде объекта
    287.          *
    288.          * @throws SqlException, FormatException
    289.          * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора.
    290.          * Если $arr это строка, то она трактуется как sql-запрос для выполнения.  
    291.          * @param string $tab Таблица, в которой выполняется обновление.
    292.          * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска,
    293.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    294.          * @param int $numUnion Количество операторов union в запросе.
    295.          * @return object Объект, содержащий значения из первой строки результата запроса
    296.          */
    297.         public function getObj($arr, $tab=null, $where=null, $numUnion=0){}
    298.        
    299.         /**
    300.          * Возвращает единственное значение из результата запроса $sql.
    301.          *
    302.          * @throws SqlException, FormatException
    303.          * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора.
    304.          * Если $arr это строка, то она трактуется как sql-запрос для выполнения.  
    305.          * @param string $tab Таблица, в которой выполняется обновление.
    306.          * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска,
    307.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    308.          * @param int $numUnion Количество операторов union в запросе.
    309.          * @return mixed Значение из первой строки результата запроса
    310.          */
    311.         public function getVal($arr, $tab=null, $where=null, $numUnion=0){}
    312.        
    313.         /**
    314.          * Возвращает весь результат запроса в виде двумерного массива.
    315.          *
    316.          * @throws SqlException, FormatException
    317.          * @param mixed $arr Если $arr массив, то он должен содержать поля для выбора.
    318.          * Если $arr это строка, то она трактуется как sql-запрос для выполнения.  
    319.          * @param string $tab Таблица, в которой выполняется обновление.
    320.          * @param mixed $where Условие, по которому происходит поиск, или id записи для поиска,
    321.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    322.          * @param int $numUnion Количество операторов union в запросе.
    323.          * @return array Двумерный массив с результатом запроса.
    324.          */
    325.         public function getTable($arr, $tab=null, $where=null, $numUnion=0){}
    326.        
    327.                
    328.                                                         //////////////////////
    329.                                                         //Подгтовка запросов//
    330.                                                         //////////////////////
    331.         /**
    332.          * Создает и проверяет запрос select.
    333.          *
    334.          * @see select
    335.          * @throws SqlException, FormatException
    336.          * @param mixed $arr Запрос для проверки или массив со значениями.
    337.          * @param string $tab Таблица, в которой обновляются значения.
    338.          * @param mixed $where Условие, по которому обновляются значения, или id записи для поиска.
    339.          * @param int $numUnion Количество операторов uniond в запросе.
    340.          * @return string Проверенный запрос
    341.          */
    342.         public function getSelect($arr, $tab=null, $where=null, $numUnion=0){}
    343.  
    344.         /**
    345.          * Формирует и проверяет запрос insert.
    346.          * @see insert
    347.          * @throws SqlException, FormatException
    348.          * @param mixed $arr Запрос для проверки или массив со значениями
    349.          * @param string $tab Таблица для вставки значения
    350.          * @return string Проверенный запрос
    351.          */
    352.         public function getInsert($arr, $tab=null){}
    353.  
    354.         /**
    355.          * Формирует и проверяет запрос update.
    356.          * @see update
    357.          * @throws SqlException, FormatException
    358.          * @param mixed $arr Запрос для проверки или массив со значениями
    359.          * @param string $tab Таблица, в которой обновляются значения
    360.          * @param mixed $where Условие, по которому обновляются значения, или id записи для обновления
    361.          * @return string Проверенный запрос
    362.          */
    363.         public function getUpdate($arr, $tab=null, $where=null){}
    364.  
    365.         /**
    366.          * Формирует и проверяет запрос delete.
    367.          * @see delete
    368.          * @throws SqlException, FormatException
    369.          * @param string $tab Запрос для проверки или таблица, в которой обновляются значения
    370.          * @param mixed $where Условие, при котором удаляется запись, или id записи для удаления
    371.          * @return string Проверенный запрос
    372.          */
    373.         public function getDelete($tab, $where=null){}
    374.        
    375.         /**
    376.          * Формирует условие where.
    377.          *
    378.          * @param mixed $where Условие, при котором выполняется действие, или id записи,
    379.          * или массив где ключи массива трактуются как поля таблицы, а соответствующие значения как значения этих полей
    380.          * @return string Сформированное условие.
    381.          */
    382.         public function getWhere($where){}
    383.        
    384.         /**
    385.          * Подготовливает выполнение sql-запроса
    386.          *
    387.          * Функция запоминает sql-запрос в свойстве класса.
    388.          * Логирует и выводит его на экран, если это указано в настройках  $vf["db"]["sqlLog"] и $vf["db"]["sqlShow"].
    389.          * Производит перекодировку запроса, если нужно.
    390.          * Выбрасывает исключение в случае ошибки на sql-сервере.
    391.          *
    392.          * @param string sql-запрос для выполнения
    393.          */
    394.         public function exec($sql, $save=true){}
    395.        
    396.         /**
    397.          * Функция создает строку присвоения/сравнения из массива.
    398.          *
    399.          * Функция создает строку присвоения/сравнения из массива.
    400.          * Ключи массива трактуются как поля таблицы, а соответствующие значения, как те значения которые нужно присвоить/сравнить.
    401.          *
    402.          * @param array $arr Массив из которого нужно сделать строку присвоения/сравнения
    403.          * @param string $delim Строка, которая вставляется между ассоциированными парами.
    404.          * @return string Требуемая строка
    405.          */
    406.         public function getAssignmentString($arr, $delim){}
    407.  
    408.         /**
    409.          * Обрабатывает спецсимволы в строке для безопасного ее использования в запросе
    410.          *
    411.          * @param mixed $str Строка, в которой надо экранировать спецсимволы или число или null.
    412.          * @return mixed Строка с экранированными спецсимволы, или число или строка "null".
    413.          */
    414.         public function escapeString($str){
    415.             if (is_array($str)){
    416.                 foreach($str as $key=>$val){
    417.                     $str[$key]=$this->escapeString($val);
    418.                 }
    419.                 return $str;
    420.             }
    421.             if (is_null($str)) return "NULL";
    422.             if (is_string($str)) return "'".$this->escape($str)."'";
    423.             return $str;
    424.         }
    425.  
    426.         /**
    427.          * Обрамляет имя поля в специальные символы.
    428.          *
    429.          * В большинстве БД для использования некоторых символов (например, пробелов) в имени поля нужно все поле обрамить спецсимволами.
    430.          * Именно это и делает данная функция
    431.          *
    432.          * @param string $str Имя поля
    433.          * @return string имя поля заключенное в спецсимволы
    434.          */
    435.         public function escapeKeys($key){}
    436.        
    437.         /**
    438.          * Возвращает результат запроса в виде двумерного массива.
    439.          *
    440.          * @param resource $res Если параметр $res задан, то строка берется из него, в противном случае строка берется из $this->res
    441.          * @return array Двумерный массив с результатом запроса.
    442.          */
    443.         public function fetchTable($rez=null){}
    444.     }
    445.  
    На счет ноды:
    Мне кажется это перспективная идея в плане хранения дерева. Т.е. уйти от массива массивов к ноде.
     
  6. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    крутотень!

    еще б нотацию PEAR/ZF в именовании классов.

    Код (Text):
    1. throw new FormatException("Недостаточно данных.","Указаны не все данные");
    spl: InvalidArgumentException?
     
  7. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    PHP:
    1. public function __construct($tab, $idName, $idParName, $levelName, $nameTab=null,  $idNameField='id', $nameField=null, $orderField=null, $DBCon=null){
    названия полей можно б массивом передавать, а к $DBCon добавить тайпхинтинг SQLDB
     
  8. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    Да, вполне.

    Можно.

    Для некой единости библиотек - да.
    Для всеобщего обозрения/использования - не обязательно. По идее можно передать и объект не SQLDB класса, который реализует нужные методы.
     
  9. Костян

    Костян Активный пользователь

    С нами с:
    12 ноя 2009
    Сообщения:
    1.724
    Симпатии:
    1
    Адрес:
    адуктО
    Volt(220) красава!
     
  10. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    Костян
    Еще не совсем. Вот если Nested Sets осилю... =))
     
  11. Костян

    Костян Активный пользователь

    С нами с:
    12 ноя 2009
    Сообщения:
    1.724
    Симпатии:
    1
    Адрес:
    адуктО
    Volt(220)
    давай давай...
     
  12. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    И примерно вот так это дерево можно вывести:
    1)Шаблонизатор
    а) http://www.php.ru/forum/viewtopic.php?p=211516#211516
    б)
    PHP:
    1. <?php
    2.     /**
    3.      * Шаблон вывода дерева.
    4.      *
    5.      * @author Костин Алексей Васильевич aka Volt(220)
    6.      * @copyright Copyright (c) 2010, Костин Алексей Васильевич
    7.      * @license [url=http://www.gnu.org/licenses/gpl-3.0.html]http://www.gnu.org/licenses/gpl-3.0.html[/url] GNU Public License
    8.      * @package classes
    9.      * @subpackage templates
    10.      */
    11.  
    12.     class TreeTpl extends Template{
    13.  
    14.         const UL=0;
    15.         const EXTJS=1;
    16.         const EXTJSADV=2;
    17.        
    18.         /**
    19.          * Конструктор.
    20.          *
    21.          * @param array $tree Дерево.
    22.          * @param int $tplType Тип шаблона (список, extjs дерево, extjs дерево в таблице).
    23.          * @param boolean $cache Нужно ли кэширование шаблона.
    24.          * @param string $dir Дирректория для кэша шаблона.
    25.          */
    26.         public function __construct($tree, $tplType=TreeTpl::UL, $cache=null, $dir=null){
    27.             switch ($tplType) {
    28.                 case TreeTpl::UL: parent::__construct(VCROOT."/Templates/tree.tpl", $cache, $dir); break;
    29.                 case TreeTpl::EXTJS: parent::__construct(VCROOT."/Templates/extJSTree.tpl", $cache, $dir); break;
    30.                 case TreeTpl::EXTJSADV:parent::__construct(VCROOT."/Templates/extJSAdvTree.tpl", $cache, $dir); break;
    31.                 default: parent::__construct(VCROOT."/Templates/tree.tpl", $cache, $dir);
    32.             }
    33.             $this->tree=$tree;
    34.         }
    35.     }
    36.  
    2)Шаблоны:
    Просто дерево
    PHP:
    1. <ul>
    2. <?php   foreach($tree as $child) { ?>
    3.     <li>
    4. <?php       echo $child['name'] ?>
    5. <?php       if($child['tree'] && is_array($child['tree'])) { ?>
    6. <?php           echo new TreeTpl($child['tree']);?>
    7. <?php       } ?>
    8.     </li>
    9. <?php   } ?>
    10. </ul>
    Просто ExtJS дерево
    PHP:
    1. <?php if (!empty($tree)) {?>
    2.     children: [{
    3. <?php $first=true; ?>
    4. <?php   foreach($tree as $child) { ?>
    5. <?php       if ($first) $first=false; else { ?>
    6. ,{
    7. <?php       } ?>
    8.         text: "<?php echo $child['name']?>",
    9.         expanded: false,
    10. <?php       if($child['tree'] && is_array($child['tree'])) { ?>
    11. <?php           echo new TreeTpl($child['tree'], TreeTpl::EXTJS);?>
    12. <?php       }else {?>
    13.         leaf: true
    14. <?php       } ?>
    15.         }
    16. <?php   } ?>
    17.     ]
    18. <?php }else { ?>
    19.     leaf: true
    20. <?php } ?>
    Сложное ExtJS дерево
    PHP:
    1. <?php if (!empty($tree)) {?>
    2.     children: [{
    3. <?php $first=true; ?>
    4. <?php   foreach($tree as $child) { ?>
    5. <?php       if ($first) $first=false; else {?>
    6. ,{
    7. <?php       } ?>
    8.         text: "<?php echo $child['name']?>",
    9. <?php       foreach($child as $key=>$data) {?>
    10. <?php           if($key!="tree" && $key!="name") {?>
    11. <?php       echo $key;?>: "<?php echo $data;?>",
    12. <?php           } ?>
    13. <?php       } ?>
    14.         expanded: false,
    15. <?php       if(isset($child['tree']) && is_array($child['tree'])) { ?>
    16. <?php           echo new TreeTpl($child['tree'], TreeTpl::EXTJSADV);?>
    17. <?php       }else {?>
    18.         leaf: true
    19. <?php       } ?>
    20.         }
    21. <?php   } ?>
    22.     ]
    23. <?php }else { ?>
    24.     leaf: true
    25. <?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
     
  13. Padaboo

    Padaboo Старожил
    Команда форума Модератор

    С нами с:
    26 окт 2009
    Сообщения:
    5.242
    Симпатии:
    1
    Volt(220)
    нифига не понимаю, пойду забурюсь в ман...
    мне вот как раз примерно вот это и нужно... засада
    :D
     
  14. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    Попытался применить эту версию на практике и сразу возникла проблема - идентификатор корня не равен 1.
    Решил. В итоге в конструктор передается массив имен как и советовал Кос.
    Последняя версия:
    http://code.google.com/p/voltcore/sourc ... sses/Trees
    Тесты, из которых можно вытащить примеры создания и манипулирования:
    http://code.google.com/p/voltcore/sourc ... sses/Tests
     
  15. Padaboo

    Padaboo Старожил
    Команда форума Модератор

    С нами с:
    26 окт 2009
    Сообщения:
    5.242
    Симпатии:
    1
    Volt(220)
    а если у меня допустим максимальная вложенность 3 и из данных нужно хранить только названия узлов
    можно использовать как вот тут первый вариант или он совсем негодный?
    http://gsbelarus.com/gs/modules.php?nam ... le&sid=314
     
  16. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    Первый это где тупо ссылка на родителя? Собственно это и есть Adjacency List.
    Если объединить мои две таблицы ALTree и ALTreeData, то получиться одна таблица. Насколько я понял большинство так и хранит. Возможно даже конструкция типа:
    Код (Text):
    1. $tree= new ALTree("ALTree", "id", "pid", "ALTree",  'id', "content", null, $DBCon);
    будет с ней работать.

    Я начал изучение деревьев с http://phpclub.ru/faq/Tree . И там говориться что стоит разделять дерево и данные.

    Или в чем вопрос?
     
  17. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    Сделал Nested Sets. Нужно будет прокомментировать ключевые моменты.

    Явно требуется вводить класс TreeNode.
     
  18. Koc

    Koc Активный пользователь

    С нами с:
    3 мар 2008
    Сообщения:
    2.253
    Симпатии:
    0
    Адрес:
    \Ukraine\Dnepropetrovsk
    да, нужно. Но у меня куда-то пропало все вдохновение (( переработал что ли, сам не знаю
     
  19. tenshi

    tenshi Активный пользователь

    С нами с:
    1 июн 2010
    Сообщения:
    191
    Симпатии:
    0
    странно, а внешне на индуса совсем не похож %-)
     
  20. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    tenshi
    Где конкретно и как лучше сделать то же самое? =)
     
  21. tenshi

    tenshi Активный пользователь

    С нами с:
    1 июн 2010
    Сообщения:
    191
    Симпатии:
    0
    не надо то же самое, надо проще..
     
  22. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    tenshi
    Не, не...
    Есть задача - работать с деревьями, которые хранятся в базе.

    Вот я и спрашиваю: как реализовать данную задачу, с данным интерфейсом проще?
     
  23. tenshi

    tenshi Активный пользователь

    С нами с:
    1 июн 2010
    Сообщения:
    191
    Симпатии:
    0
    abstract public function fetchAssoc($res=null);
    abstract public function fetchObj($res=null);

    вот зачем фетчить во всех вариантах? лучше выбрать что-нибудь одно. и так далее.....
     
  24. Volt(220)

    Volt(220) Активный пользователь

    С нами с:
    11 июн 2009
    Сообщения:
    1.640
    Симпатии:
    1
    Затем что в разных ситуациях нужны разные данные.
    Так, например, ExtJS настроен на получение массивов объектов. Мне удобно работать с ассоциативными массивами. И пару раз мне попадался вариант, когда лучше разобрать в нумерованный массив.

    Ну и да этот топик в основном посвящен деревьям, потому я ожидал отзывов именно по ..Tree классам, хотя я не против конструктивной критики и по другим моим решениям.
     
  25. tenshi

    tenshi Активный пользователь

    С нами с:
    1 июн 2010
    Сообщения:
    191
    Симпатии:
    0
    $object= (object)$array;
    $values= array_values( $array );

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