| [ Index ] |
PHP Cross Reference of Limb3 |
[Summary view] [Print] [Text view]
1 <?php 2 /* 3 * Limb PHP Framework 4 * 5 * @link http://limb-project.com 6 * @copyright Copyright © 2004-2007 BIT(http://bit-creative.com) 7 * @license LGPL http://www.gnu.org/copyleft/lesser.html 8 */ 9 lmb_require('limb/dbal/src/criteria/lmbSQLFieldCriteria.class.php'); 10 lmb_require('limb/dbal/src/lmbTableGateway.class.php'); 11 lmb_require('limb/core/src/lmbCollection.class.php'); 12 lmb_require('limb/tree/src/lmbTree.interface.php'); 13 lmb_require('limb/tree/src/exception/lmbTreeException.class.php'); 14 lmb_require('limb/tree/src/exception/lmbTreeInvalidNodeException.class.php'); 15 lmb_require('limb/tree/src/exception/lmbTreeConsistencyException.class.php'); 16 17 /** 18 * Base class implementing a Nested Sets approach for storing tree-like structures in database tables. 19 * @package tree 20 * @version $Id$ 21 */ 22 class lmbNSTree implements lmbTree 23 { 24 protected $_conn; 25 26 protected $_system_columns = array(); 27 protected $_column_map; 28 29 protected $_id; 30 protected $_parent_id; 31 protected $_left; 32 protected $_right; 33 protected $_identifier; 34 protected $_level; 35 36 protected $_db_table; 37 38 function __construct($node_table = 'ns_tree', 39 $conn = null, 40 $column_map = array('id' => 'id', 'parent_id' => 'parent_id', 41 'c_left' => 'c_left', 'c_right' => 'c_right', 42 'level' => 'level', 'identifier' => 'identifier' 43 )) 44 { 45 $this->_mapColumns($column_map); 46 47 if($conn) 48 $this->_conn = $conn; 49 else 50 $this->_conn = lmbToolkit :: instance()->getDefaultDbConnection(); 51 52 $this->_node_table = $node_table; 53 $this->_db_table = new lmbTableGateway($this->_node_table, $this->_conn); 54 } 55 56 protected function _mapColumns($column_map) 57 { 58 $this->_id = isset($column_map['id']) ? $column_map['id'] : 'id'; 59 $this->_parent_id = isset($column_map['parent_id']) ? $column_map['parent_id'] : 'parent_id'; 60 $this->_left = isset($column_map['c_left']) ? $column_map['c_left'] : 'c_left'; 61 $this->_right = isset($column_map['c_right']) ? $column_map['c_right'] : 'c_right'; 62 $this->_level = isset($column_map['level']) ? $column_map['level'] : 'level'; 63 $this->_identifier = isset($column_map['identifier']) ? $column_map['identifier'] : 'identifier'; 64 65 $this->_system_columns = array($this->_id, $this->_parent_id, $this->_left, 66 $this->_right, $this->_level); 67 68 $this->_column_map = array('id' => $this->_id, 'parent_id' => $this->_parent_id, 69 'level' => $this->_level, 'identifier' => $this->_identifier, 70 'c_left' => $this->_left, 'c_right' => $this->_right); 71 } 72 73 function setNodeTable($table_name) 74 { 75 $this->_node_table = $table_name; 76 } 77 78 function getNodeTable() 79 { 80 return $this->_node_table; 81 } 82 83 function _getSelectFields($table = null) 84 { 85 if($table === null) 86 $table = $this->_node_table; 87 88 $flipped = array_flip($this->_column_map); 89 90 foreach($this->_db_table->getColumnsForSelect() as $name) 91 { 92 if(isset($flipped[$name])) 93 $alias = $flipped[$name]; 94 else 95 $alias = $name; 96 97 $sql_exec_fields[] = "{$table}.{$name} AS {$alias}"; 98 } 99 100 return implode(', ', $sql_exec_fields); 101 } 102 103 function _processUserValues($values) 104 { 105 $processed = array(); 106 foreach($values as $name => $value) 107 { 108 if(isset($this->_column_map[$name])) 109 $column = $this->_column_map[$name]; 110 else 111 $column = $name; 112 113 if(in_array($column, $this->_system_columns)) 114 continue; 115 116 $processed[$column] = $value; 117 } 118 return $processed; 119 } 120 121 function initTree() 122 { 123 $stmt = $this->_conn->newStatement("DELETE FROM {$this->_node_table}"); 124 $stmt->execute(); 125 126 return $this->_createRootNode(); 127 } 128 129 function getRootNode() 130 { 131 $sql = "SELECT " . $this->_getSelectFields() . " 132 FROM {$this->_node_table} WHERE {$this->_level}=0"; 133 $stmt = $this->_conn->newStatement($sql); 134 135 if($root_node = $stmt->getOneRecord()) 136 return $root_node; 137 138 return null; 139 } 140 141 function getParents($node) 142 { 143 $child = $this->_ensureNode($node); 144 145 if($child['level'] < 1) 146 return null; 147 148 $sql = "SELECT " . $this->_getSelectFields() . " 149 FROM {$this->_node_table} 150 WHERE {$this->_left} < {$child['c_left']} 151 AND {$this->_right} > {$child['c_right']} 152 ORDER BY {$this->_left} ASC"; 153 154 $stmt = $this->_conn->newStatement($sql); 155 156 return $stmt->getRecordSet(); 157 } 158 159 function getParent($node) 160 { 161 $child = $this->_ensureNode($node); 162 163 if($child['level'] < 1) 164 return null; 165 $sql = "SELECT " . $this->_getSelectFields() . " 166 FROM {$this->_node_table} 167 WHERE {$this->_left} < {$child['c_left']} 168 AND {$this->_right} > {$child['c_right']} 169 AND {$this->_level} = ".($child['level']-1); 170 171 $stmt = $this->_conn->newStatement($sql); 172 173 return $stmt->getOneRecord(); 174 } 175 176 function getSiblings($node) 177 { 178 $sibling = $this->_ensureNode($node); 179 180 if(!$parent = $this->getParent($sibling['id'])) 181 return new lmbCollection(array($sibling)); 182 183 return $this->getChildren($parent['id']); 184 } 185 186 function getChildren($node, $depth = 1) 187 { 188 return $this->_getChildren($node, $depth); 189 } 190 191 function getChildrenAll($node) 192 { 193 return $this->_getChildren($node); 194 } 195 196 protected function _getChildren($node, $depth = -1) 197 { 198 $parent = $this->_ensureNode($node); 199 200 $sql = "SELECT " . $this->_getSelectFields() . " 201 FROM {$this->_node_table} 202 WHERE {$parent['c_left']} < {$this->_left} 203 AND {$parent['c_right']} > {$this->_right}"; 204 if($depth!=-1) 205 $sql .= " AND {$this->_level} <= ".($parent['level']+$depth); 206 $sql .= " ORDER BY {$this->_left}"; 207 208 $stmt = $this->_conn->newStatement($sql); 209 210 return $stmt->getRecordSet(); 211 } 212 213 function countChildren($node, $depth = 1) 214 { 215 return $this->_countChildren($node, $depth); 216 } 217 218 function countChildrenAll($node) 219 { 220 return $this->_countChildren($node); 221 } 222 223 protected function _countChildren($node, $depth = -1) 224 { 225 $parent = $this->_ensureNode($node); 226 227 $sql = "SELECT count({$this->_id}) as counter 228 FROM {$this->_node_table} 229 WHERE {$parent['c_left']} < {$this->_left} 230 AND {$parent['c_right']} > {$this->_right}"; 231 232 if($depth!=-1) 233 $sql .= " AND {$this->_level} <= ".($parent['level']+$depth); 234 235 $stmt = $this->_conn->newStatement($sql); 236 237 return $stmt->getOneValue(); 238 } 239 240 function getNode($node) 241 { 242 if(is_string($node) && !is_numeric($node)) 243 { 244 if(!$res = $this->getNodeByPath($node)) 245 return null; 246 return $res; 247 } 248 249 if(is_array($node) or is_object($node)) 250 { 251 if(isset($node['id'])) 252 $id = $node['id']; 253 else 254 return null; 255 } 256 257 if(!isset($id)) 258 $id = $node; 259 260 $sql = "SELECT " . $this->_getSelectFields() . " 261 FROM {$this->_node_table} WHERE {$this->_id}=:id:"; 262 263 $stmt = $this->_conn->newStatement($sql); 264 $stmt->setInteger('id', $id); 265 266 if($r = $stmt->getOneRecord()) 267 return $r; 268 269 return null; 270 } 271 272 function getNodeByPath($path) 273 { 274 $path = preg_replace('~\/+~', '/', $path); 275 276 if($path == '/') 277 return $this->getRootNode(); 278 279 $path_array = explode('/', $path); 280 281 //if(reset($path_array) == '') 282 array_shift($path_array); 283 if(end($path_array) == '') 284 array_pop($path_array); 285 286 if(!$path_array) 287 return null; 288 289 $sql = "SELECT " . $this->_getSelectFields() . " 290 FROM {$this->_node_table} WHERE {$this->_level}=0"; 291 $stmt = $this->_conn->newStatement($sql); 292 293 if(!$root_node = $stmt->getOneRecord()) 294 return null; 295 296 $t=''; 297 $w="t0.{$this->_id}=".$root_node['id']; 298 for($i=0;$i<count($path_array);$i++) 299 { 300 $c=$i+1; 301 $t.=",\n{$this->_node_table} t".$c; 302 $w.=" AND t".$c.".{$this->_identifier}='".addslashes($path_array[$i])."' 303 AND t".$c.".{$this->_left} BETWEEN t".$i.".{$this->_left}+1 AND t".$i.".{$this->_right} 304 AND t".$c.".{$this->_level}=t".$i.".{$this->_level}+1"; 305 } 306 $sql = "SELECT ".$this->_getSelectFields('t'.$c)." FROM {$this->_node_table} t0$t WHERE $w"; 307 308 $stmt = $this->_conn->newStatement($sql); 309 if($r = $stmt->getOneRecord()) 310 return $r; 311 312 return null; 313 } 314 315 function getPathToNode($node, $delimeter = '/') 316 { 317 $node = $this->_ensureNode($node); 318 319 $path = ''; 320 321 if(!$parents = $this->getParents($node)) 322 return $path .= $delimeter . $node['identifier']; 323 324 foreach($parents as $parent) 325 $path .= $delimeter . $parent['identifier']; 326 327 $path .= $delimeter . $node['identifier']; 328 return substr($path, 1); 329 } 330 331 function getNodesByIds($ids) 332 { 333 if(!$ids) 334 return new lmbCollection(); 335 336 $sql = "SELECT " . $this->_getSelectFields() . " 337 FROM {$this->_node_table} 338 WHERE " . $this->_dbIn($this->_id, $ids) . " 339 ORDER BY {$this->_left}"; 340 341 $stmt = $this->_conn->newStatement($sql); 342 return $stmt->getRecordSet(); 343 } 344 345 function isNode($id) 346 { 347 return ($this->getNode($id) !== null); 348 } 349 350 function _getNextNodeInsertId() 351 { 352 $sql = "SELECT MAX({$this->_id}) as m FROM ". $this->_node_table; 353 $stmt = $this->_conn->newStatement($sql); 354 $max = $stmt->getOneValue(); 355 356 return isset($max) ? $max + 1 : 1; 357 } 358 359 function _dbIn($column_name, $values) 360 { 361 $in_ids = implode("','", $values); 362 return $column_name . " IN ('" . $in_ids . "')"; 363 } 364 365 function createNode($node, $user_values) 366 { 367 $parent_node = $this->_ensureNode($node); 368 369 $values = $this->_processUserValues($user_values); 370 371 if(!isset($values[$this->_identifier]) || $values[$this->_identifier] == '') 372 throw new lmbTreeConsistencyException("Identifier property is required"); 373 374 $this->_ensureUniqueSiblingIdentifier($values[$this->_identifier], $parent_node); 375 376 $values[$this->_id] = $this->_getNextNodeInsertId(); 377 $values[$this->_parent_id] = $parent_node['id']; 378 $values[$this->_left] = $parent_node['c_right']; 379 $values[$this->_right] = $parent_node['c_right']+1; 380 $values[$this->_level] = $parent_node['level']+1; 381 382 // creating a place for the record being inserted 383 $sql = "UPDATE {$this->_node_table} 384 SET {$this->_left}= CASE WHEN {$this->_left}>{$parent_node['c_right']} THEN {$this->_left}+2 ELSE {$this->_left} END, 385 {$this->_right}=CASE WHEN {$this->_right}>={$parent_node['c_right']} THEN {$this->_right}+2 ELSE {$this->_right} END 386 WHERE {$this->_right}>={$parent_node['c_right']}"; 387 $stmt = $this->_conn->newStatement($sql); 388 $stmt->execute(); 389 390 $this->_db_table->insert($values); 391 392 return $values[$this->_id]; 393 } 394 395 protected function _createRootNode() 396 { 397 $values = array(); 398 $values[