[ Index ]

PHP Cross Reference of Limb3

title

Body

[close]

/tree/src/ -> lmbNSTree.class.php (source)

   1  <?php
   2  /*
   3   * Limb PHP Framework
   4   *
   5   * @link http://limb-project.com 
   6   * @copyright  Copyright &copy; 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[