[ Index ]

PHP Cross Reference of Limb3

title

Body

[close]

/tree/src/ -> lmbMPTree.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/core/src/lmbCollection.class.php');
  10  lmb_require('limb/dbal/src/criteria/lmbSQLFieldCriteria.class.php');
  11  lmb_require('limb/dbal/src/lmbTableGateway.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   * class lmbMPTree.

  19   *

  20   * @package tree

  21   * @version $Id$

  22   */
  23  class lmbMPTree implements lmbTree
  24  {
  25    protected $_conn;
  26  
  27    protected $_system_columns = array();
  28    protected $_column_map;
  29    protected $_select_columns = '';
  30  
  31    //convenience aliases for physical table column names

  32    protected $_id;
  33    protected $_parent_id;
  34    protected $_identifier;
  35    protected $_level;
  36    protected $_path;
  37  
  38    protected $_db_table;
  39  
  40    function __construct($node_table = 'mp_tree',
  41                         $conn = null,
  42                         $column_map = array('id' => 'id', 'parent_id' => 'parent_id',
  43                                             'level' => 'level', 'identifier' => 'identifier',
  44                                             'path' => 'path'))
  45    {
  46      $this->_mapColumns($column_map);
  47  
  48      if(!$conn)
  49        $this->_conn = lmbToolkit :: instance()->getDefaultDbConnection();
  50      else
  51        $this->_conn = $conn;
  52  
  53      $this->_node_table = $node_table;
  54      $this->_db_table = new lmbTableGateway($this->_node_table, $this->_conn);
  55    }
  56  
  57    protected function _mapColumns($column_map)
  58    {
  59      $this->_id = isset($column_map['id']) ? $column_map['id'] : 'id';
  60      $this->_parent_id = isset($column_map['parent_id']) ? $column_map['parent_id'] : 'parent_id';
  61      $this->_level = isset($column_map['level']) ? $column_map['level'] : 'level';
  62      $this->_identifier = isset($column_map['identifier']) ? $column_map['identifier'] : 'identifier';
  63      $this->_path = isset($column_map['path']) ? $column_map['path'] : 'path';
  64  
  65      $this->_system_columns = array($this->_id, $this->_parent_id, $this->_level, $this->_path);
  66  
  67      $this->_column_map = array('id' => $this->_id, 'parent_id' => $this->_parent_id,
  68                                 'level' => $this->_level, 'identifier' => $this->_identifier,
  69                                 'path' => $this->_path);
  70    }
  71  
  72    function initTree()
  73    {
  74      $stmt = $this->_conn->newStatement("DELETE FROM {$this->_node_table}");
  75      $stmt->execute();
  76      return $this->_createRootNode();
  77    }
  78  
  79    function getRootNode()
  80    {
  81      $sql = "SELECT " . $this->_getSelectFields() . " FROM {$this->_node_table} WHERE {$this->_level}=0";
  82      $stmt = $this->_conn->newStatement($sql);
  83  
  84      if($root_node = $stmt->getOneRecord())
  85        return $root_node;
  86  
  87      return null;
  88    }
  89  
  90    function setNodeTable($table_name)
  91    {
  92      $this->_node_table = $table_name;
  93    }
  94  
  95    function getNodeTable()
  96    {
  97      return $this->_node_table;
  98    }
  99  
 100    function _getSelectFields()
 101    {
 102      if($this->_select_columns)
 103        return $this->_select_columns;
 104  
 105      $flipped = array_flip($this->_column_map);
 106  
 107      foreach($this->_db_table->getColumnsForSelect() as $name)
 108      {
 109        if(isset($flipped[$name]))
 110          $alias = $flipped[$name];
 111        else
 112          $alias = $name;
 113  
 114        $sql_exec_fields[] = "{$this->_node_table}.{$name} AS {$alias}";
 115      }
 116  
 117      $this->_select_columns = implode(', ', $sql_exec_fields);
 118      return $this->_select_columns;
 119    }
 120  
 121    function _processUserValues($values)
 122    {
 123      $processed = array();
 124      foreach($values as $name => $value)
 125      {
 126        if(isset($this->_column_map[$name]))
 127          $column = $this->_column_map[$name];
 128        else
 129          $column = $name;
 130  
 131        if(in_array($column, $this->_system_columns))
 132          continue;
 133  
 134        $processed[$column] = $value;
 135      }
 136      return $processed;
 137    }
 138  
 139    function getParents($node)
 140    {
 141      $child = $this->_ensureNode($node);
 142  
 143      if($child['level'] < 1)
 144        return null;
 145  
 146      $join_table = $this->_node_table . '2';
 147      $concat = $this->_dbConcat(array($this->_node_table . '.' . $this->_path, "'%'"));
 148  
 149      $sql = "SELECT " . $this->_getSelectFields() . "
 150              FROM {$this->_node_table}, {$this->_node_table} AS {$join_table}
 151              WHERE
 152              {$join_table}.{$this->_path} LIKE {$concat} AND
 153              {$this->_node_table}.{$this->_level} < :level: AND
 154              {$join_table}.{$this->_id} = :id:
 155              ORDER BY {$this->_node_table}.{$this->_level} ASC";
 156  
 157      $stmt = $this->_conn->newStatement($sql);
 158      $stmt->setVarChar('level', $child['level']);
 159      $stmt->setVarChar('id', $child['id']);
 160  
 161      return $stmt->getRecordSet();
 162    }
 163  
 164    function getParent($node)
 165    {
 166      $child = $this->_ensureNode($node);
 167      return $this->getNode($child['parent_id']);
 168    }
 169  
 170    function getSiblings($node)
 171    {
 172      $me = $this->_ensureNode($node);
 173  
 174      if(!$me['parent_id'])
 175        return new lmbCollection(array($me));
 176  
 177      return $this->getChildren($me['parent_id']);
 178    }
 179  
 180    function getChildren($node, $depth = 1)
 181    {
 182      $parent = $this->_ensureNode($node);
 183  
 184      if($depth == 1)
 185      {
 186        $sql = "SELECT " . $this->_getSelectFields() . "
 187                FROM {$this->_node_table}
 188                WHERE {$this->_parent_id} = :parent_id:";
 189  
 190        $stmt = $this->_conn->newStatement($sql);
 191        $stmt->setInteger('parent_id', $parent['id']);
 192      }
 193      else
 194      {
 195        $sql = "SELECT " . $this->_getSelectFields() . "
 196                FROM {$this->_node_table}
 197                WHERE {$this->_path} LIKE '{$parent['path']}%'
 198                AND {$this->_id} != {$parent['id']}";
 199        if($depth != -1)
 200          $sql .= " AND {$this->_level} < ". ($parent['level'] + 1 + $depth);
 201        $stmt = $this->_conn->newStatement($sql);
 202      }
 203  
 204      $rs = $stmt->getRecordSet();
 205      $rs->sort(array($this->_path => 'ASC'));
 206      return $rs;
 207    }
 208  
 209    function getChildrenAll($node)
 210    {
 211      $node = $this->_ensureNode($node);
 212  
 213      $sql = "SELECT " . $this->_getSelectFields() . "
 214              FROM {$this->_node_table}
 215              WHERE {$this->_path} LIKE '{$node['path']}%'
 216              AND {$this->_id} != {$node['id']}";
 217  
 218      $stmt = $this->_conn->newStatement($sql);
 219  
 220      $rs = $stmt->getRecordSet();
 221      $rs->sort(array($this->_path => 'ASC'));
 222      return $rs;
 223    }
 224  
 225    function countChildren($node, $depth = 1)
 226    {
 227      $parent = $this->_ensureNode($node);
 228  
 229      if($depth == 1)
 230      {
 231        $sql = "SELECT count({$this->_id}) as counter FROM {$this->_node_table}
 232                WHERE {$this->_parent_id} = :parent_id:";
 233  
 234        $stmt = $this->_conn->newStatement($sql);
 235        $stmt->setInteger('parent_id', $parent['id']);
 236      }
 237      else
 238      {
 239        $sql = "SELECT count({$this->_id}) as counter
 240                FROM {$this->_node_table}
 241                WHERE {$this->_path} LIKE '{$parent['path']}%'
 242                AND {$this->_id} != {$parent['id']}";
 243        if($depth !=-1)
 244          $sql .= " AND {$this->_level} < " . ($parent['level'] + 1 + $depth);
 245        $stmt = $this->_conn->newStatement($sql);
 246      }
 247      return $stmt->getOneValue();
 248    }
 249  
 250    function countChildrenAll($node)
 251    {
 252      $parent = $this->_ensureNode($node);
 253  
 254      $sql = "SELECT count({$this->_id}) as counter
 255              FROM {$this->_node_table}
 256              WHERE {$this->_path} LIKE '{$parent['path']}%'
 257              AND {$this->_id} != {$parent['id']}";
 258  
 259      $stmt = $this->_conn->newStatement($sql);
 260      return $stmt->getOneValue();
 261    }
 262  
 263    function getNode($node)
 264    {
 265      if(is_string($node) && !is_numeric($node))
 266      {
 267        if(!$res = $this->getNodeByPath($node))
 268          return null;
 269        return $res;
 270      }
 271      elseif(is_array($node) || is_object($node))
 272      {
 273        if(isset($node['id']))
 274          $id = $node['id'];
 275        else
 276          return null;
 277      }
 278      else
 279        $id = $node;
 280  
 281      $sql = "SELECT " . $this->_getSelectFields() . "
 282              FROM {$this->_node_table} WHERE {$this->_id}=:id:";
 283  
 284      $stmt = $this->_conn->newStatement($sql);
 285      $stmt->setInteger($this->_id, $id);
 286  
 287      if($node = $stmt->getOneRecord())
 288        return $node;
 289  
 290      return null;
 291    }
 292  
 293    function getNodeByPath($path)
 294    {
 295      $path = preg_replace('~\/+~', '/', $path);
 296  
 297      if($path == '/')
 298        return $this->getRootNode();
 299  
 300      $path_array = explode('/', $path);
 301  
 302      array_shift($path_array);//skip first item

 303      if(end($path_array) == '')//ending slash
 304        array_pop($path_array);
 305  
 306      if(!$path_array)
 307        return null;
 308  
 309      $level = sizeof($path_array);
 310  
 311      $in_condition = $this->_dbIn($this->_identifier, array_unique($path_array));
 312  
 313      $sql = "SELECT " . $this->_getSelectFields() . "
 314              FROM {$this->_node_table}
 315              WHERE
 316              {$in_condition}
 317              AND {$this->_level} <= {$level}
 318              ORDER BY {$this->_path}";
 319  
 320      $stmt = $this->_conn->newStatement($sql);
 321      $rs = $stmt->getRecordSet();
 322  
 323      $curr_level = 0;
 324      $parent_id = null;
 325      $path_to_node = '';
 326  
 327      foreach($rs as $node)
 328      {
 329        if($node['level'] < $curr_level)
 330          continue;
 331  
 332        if($node['identifier'] == $path_array[$curr_level] &&
 333           (!$parent_id ||
 334           $node['parent_id'] == $parent_id))
 335        {
 336          $parent_id = $node['id'];
 337  
 338          $curr_level++;
 339          $path_to_node .= '/' . $node['identifier'];
 340  
 341          if($curr_level == $level)
 342            return $node;
 343        }
 344      }
 345      return null;
 346    }
 347  
 348    function getPathToNode($node)
 349    {
 350      $node = $this->_ensureNode($node);
 351  
 352      if(!$parents = $this->getParents($node['id']))
 353        return '/';
 354  
 355      $path = '';
 356      foreach($parents as $parent)
 357        $path .= $parent['identifier'] . '/';
 358  
 359      return $path .= $node['identifier'];
 360    }
 361  
 362    function getNodesByIds($ids)
 363    {
 364      if(!$ids)
 365        return new lmbCollection();
 366  
 367      $sql = "SELECT " . $this->_getSelectFields() . "
 368              FROM {$this->_node_table}
 369              WHERE " . $this->_dbIn($this->_id, $ids) . "
 370              ORDER BY {$this->_path}";
 371  
 372      $stmt = $this->_conn->newStatement($sql);
 373      return $stmt->getRecordSet();
 374    }
 375  
 376    function isNode($node)
 377    {
 378      return ($this->getNode($node) !== null);
 379    }
 380  
 381    function updateNode($node, $user_values, $internal = false)
 382    {
 383      $node = $this->_ensureNode($node);
 384  
 385      if(isset($user_values['identifier']))
 386      {
 387        if($node['parent_id'] == 0 && $user_values['identifier'])//root check
 388          throw new lmbTreeConsistencyException('Root node is forbidden to have an identifier');
 389  
 390        if($node['identifier'] != $user_values['identifier'])
 391          $this->_ensureUniqueSiblingIdentifier($user_values['identifier'], $node['parent_id']);
 392      }
 393  
 394      if(!$internal)
 395        $values = $this->_processUserValues($user_values);
 396      else
 397        $values = $user_values;
 398  
 399      if(!$values)
 400        return;
 401  
 402      $this->_db_table->updateById($node['id'], $values);
 403    }
 404  
 405    function _getNextNodeInsertId()
 406    {
 407      //if field is autoincremented why do we need it?

 408      $sql = "SELECT MAX({$this->_id}) as m FROM {$this->_node_table}";
 409      $stmt = $this->_conn->newStatement($sql);
 410      $max = $stmt->getOneValue();
 411      return isset($max) ? $max + 1 : 1;
 412    }
 413  
 414    function _dbConcat($values)
 415    {
 416      switch($this->_conn->getType())
 417      {
 418        case 'mysql':
 419          $str = implode(',', $values);
 420          return " CONCAT({