Apache Zeta Components Manual :: File Source for db_nested_set.php

Source for file db_nested_set.php

Documentation is available at db_nested_set.php

  1. <?php
  2. /**
  3.  * File containing the ezcTreeDbNestedSet class.
  4.  *
  5.  * Licensed to the Apache Software Foundation (ASF) under one
  6.  * or more contributor license agreements.  See the NOTICE file
  7.  * distributed with this work for additional information
  8.  * regarding copyright ownership.  The ASF licenses this file
  9.  * to you under the Apache License, Version 2.0 (the
  10.  * "License"); you may not use this file except in compliance
  11.  * with the License.  You may obtain a copy of the License at
  12.  * 
  13.  *   http://www.apache.org/licenses/LICENSE-2.0
  14.  * 
  15.  * Unless required by applicable law or agreed to in writing,
  16.  * software distributed under the License is distributed on an
  17.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  18.  * KIND, either express or implied.  See the License for the
  19.  * specific language governing permissions and limitations
  20.  * under the License.
  21.  *
  22.  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
  23.  * @version //autogentag//
  24.  * @filesource
  25.  * @package TreeDatabaseTiein
  26.  */
  27.  
  28. /**
  29.  * ezcTreeDbNestedSet implements a tree backend which stores parent/child
  30.  * information with left and right values.
  31.  *
  32.  * The table that stores the index (configured using the $indexTableName argument
  33.  * of the {@link __construct} method) should contain at least four fields. The
  34.  * first one 'id' will contain the node's ID, the second one 'parent_id' the ID
  35.  * of the node's parent. Both fields should be of the same database field type.
  36.  * Supported field types are either integer or a string type.  The other two
  37.  * fields "lft" and "rgt" will store the left and right values that the
  38.  * algorithm requires. These two fields should be of an integer type.
  39.  * In order to use auto-generated IDs, the 'id' field needs to be an
  40.  * auto-incrementing integer field, by using either an auto-increment field, or
  41.  * a sequence.
  42.  *
  43.  * @property-read ezcTreeDbDataStore $store 
  44.  *                 The data store that is used for retrieving/storing data.
  45.  * @property      string $nodeClassName 
  46.  *                 Which class is used as tree node - this class *must* inherit
  47.  *                 the ezcTreeNode class.
  48.  *
  49.  * @package TreeDatabaseTiein
  50.  * @version //autogentag//
  51.  * @mainclass
  52.  */
  53. {
  54.     /**
  55.      * Creates a new ezcTreeDbNestedSet object.
  56.      *
  57.      * The different arguments to the method configure which database
  58.      * connection ($dbh) is used to access the database and the $indexTableName
  59.      * argument which table is used to retrieve the relation data from. The
  60.      * $store argument configure which data store is used with this tree.
  61.      *
  62.      * It is up to the user to create the database table and make sure it is
  63.      * empty.
  64.      * 
  65.      * @param ezcDbHandler       $dbh 
  66.      * @param string             $indexTableName 
  67.      * @param ezcTreeDbDataStore $store 
  68.      */
  69.     public static function createezcDbHandler $dbh$indexTableNameezcTreeDbDataStore $store )
  70.     {
  71.         return new ezcTreeDbNestedSet$dbh$indexTableName$store );
  72.     }
  73.  
  74.     /**
  75.      * Returns all the nodes in the path from the root node to the node with ID
  76.      * $nodeId, including those two nodes.
  77.      *
  78.      * @param string $nodeId 
  79.      * @return ezcTreeNodeList 
  80.      */
  81.     public function fetchPath$nodeId )
  82.     {
  83.         $className $this->properties['nodeClassName'];
  84.         $list new ezcTreeNodeList;
  85.  
  86.         $db $this->dbh;
  87.         $q $db->createSelectQuery();
  88.  
  89.         // SELECT parent.id
  90.         // FROM indexTable as node,
  91.         //      indexTable as parent
  92.         // WHERE
  93.         //     node.lft BETWEEN parent.lft AND parent.rgt
  94.         //     AND
  95.         //     node.id = $nodeId
  96.         // ORDER BY parent.lft
  97.         $q->select'parent.id' )
  98.           ->from$db->quoteIdentifier$this->indexTableName " as node" )
  99.           ->from$db->quoteIdentifier$this->indexTableName " as parent" )
  100.           ->where$q->expr->lAnd(
  101.               $q->expr->between'node.lft''parent.lft''parent.rgt' ),
  102.               $q->expr->eq'node.id'$q->bindValue$nodeId ) )
  103.             ))
  104.           ->orderBy'parent.lft' );
  105.  
  106.         $s $q->prepare();
  107.         $s->execute();
  108.  
  109.         foreach $s as $result )
  110.         {
  111.             $list->addNodenew $className$this$result['id') );
  112.         }
  113.         return $list;
  114.     }
  115.  
  116.     /**
  117.      * Returns the node with ID $nodeId and all its children, sorted according to
  118.      * the {@link http://en.wikipedia.org/wiki/Depth-first_search Depth-first sorting}
  119.      * algorithm.
  120.      *
  121.      * @param string $nodeId 
  122.      * @return ezcTreeNodeList 
  123.      */
  124.     public function fetchSubtreeDepthFirst$nodeId )
  125.     {
  126.         $className $this->properties['nodeClassName'];
  127.         $list new ezcTreeNodeList;
  128.         $db $this->dbh;
  129.  
  130.         // Fetch parent information
  131.         list$left$right$width $this->fetchNodeInformation$nodeId );
  132.  
  133.         // Fetch subtree
  134.         //   SELECT id
  135.         //   FROM indexTable
  136.         //   WHERE lft BETWEEN $left AND $right
  137.         //   ORDER BY lft
  138.         $q $db->createSelectQuery();
  139.         $q->select'id' )
  140.           ->from$db->quoteIdentifier$this->indexTableName ) )
  141.           ->where$q->expr->between'lft'$q->bindValue$left )$q->bindValue$right ) ) )
  142.           ->orderBy'lft' );
  143.         $s $q->prepare();
  144.         $s->execute();
  145.  
  146.         foreach $s as $result )
  147.         {
  148.             $list->addNodenew $className$this$result['id') );
  149.         }
  150.         return $list;
  151.     }
  152.  
  153.     /**
  154.      * Returns the distance from the root node to the node with ID $nodeId.
  155.      *
  156.      * @param string $nodeId 
  157.      * @return int 
  158.      */
  159.     public function getPathLength$nodeId )
  160.     {
  161.         $path $this->fetchPath$nodeId );
  162.         return count$path->nodes 1;
  163.     }
  164.  
  165.     /**
  166.      * Returns whether the node with ID $childId is a direct or indirect child
  167.      * of the node with ID $parentId.
  168.      *
  169.      * @param string $childId 
  170.      * @param string $parentId 
  171.      * @return bool 
  172.      */
  173.     public function isDescendantOf$childId$parentId )
  174.     {
  175.         $path $this->fetchPath$childId );
  176.  
  177.         if isset$path[$parentId&& $childId !== $parentId ) )
  178.         {
  179.             return true;
  180.         }
  181.         return false;
  182.     }
  183.  
  184.     /**
  185.      * Sets a new node as root node, this also wipes out the whole tree.
  186.      *
  187.      * @param ezcTreeNode $node 
  188.      */
  189.     public function setRootNodeezcTreeNode $node )
  190.     {
  191.         $db $this->dbh;
  192.  
  193.         // Remove nodes from tree
  194.         //   DELETE FROM indexTable
  195.         $q $db->createDeleteQuery();
  196.         $q->deleteFrom$db->quoteIdentifier$this->indexTableName ) );
  197.         $s $q->prepare();
  198.         $s->execute();
  199.  
  200.         // Remove all data belonging to those nodes
  201.         $this->store->deleteDataForAllNodes();
  202.  
  203.         // Create new root node
  204.         //   INSERT INTO indexTable
  205.         //   SET parent_id = null,
  206.         //       id = $node->id,
  207.         //       lft = 1, rgt = 2
  208.         $q $db->createInsertQuery();
  209.         $q->insertInto$db->quoteIdentifier$this->indexTableName ) )
  210.           ->set'parent_id'"null" )
  211.           ->set'id'$q->bindValue$node->id ) )
  212.           ->set'lft')
  213.           ->set'rgt');
  214.         $s $q->prepare();
  215.         $s->execute();
  216.  
  217.         // Store data for new root node
  218.         $this->store->storeDataForNode$node$node->data );
  219.     }
  220.  
  221.     /**
  222.      * Creates the query to insert an empty node into the database, so that the last-inserted ID can be obtained.
  223.      *
  224.      * @return ezcQueryInsert 
  225.      */
  226.     protected function createAddEmptyNodeQuery()
  227.     {
  228.         $db $this->dbh;
  229.  
  230.         $q $db->createInsertQuery();
  231.         $q->insertInto$db->quoteIdentifier$this->indexTableName ) )
  232.           ->set'lft')
  233.           ->set'rgt');
  234.  
  235.         return $q;
  236.     }
  237.  
  238.     /**
  239.      * Updates the left and right values of the nodes that are added while
  240.      * adding a whole subtree as child of a node.
  241.      *
  242.      * The method does not update nodes where the IDs are in the $excludedIds
  243.      * list.
  244.      *
  245.      * @param int $right 
  246.      * @param int $width 
  247.      * @param array(string) $excludedIds 
  248.      */
  249.     protected function updateNestedValuesForSubtreeAddition$right$width$excludedIds array() )
  250.     {
  251.         $db $this->dbh;
  252.  
  253.         // Move all the right values + $width for nodes where the the right value >=
  254.         // the parent right value:
  255.         //   UPDATE indexTable
  256.         //   SET rgt = rgt + $width
  257.         //   WHERE rgt >= $right
  258.         $q $db->createUpdateQuery();
  259.         $q->update$db->quoteIdentifier$this->indexTableName ) )
  260.           ->set'rgt'$q->expr->add'rgt'$width ) )
  261.           ->where$q->expr->gte'rgt'$right ) );
  262.         if count$excludedIds ) )
  263.         {
  264.             $q->where$q->expr->not$q->expr->in'id'$excludedIds ) ) );
  265.         }
  266.         $q->prepare()->execute();
  267.  
  268.         // Move all the left values + $width for nodes where the the right value >=
  269.         // the parent left value
  270.         //   UPDATE indexTable
  271.         //   SET lft = lft + $width
  272.         //   WHERE lft >= $right
  273.         $q $db->createUpdateQuery();
  274.         $q->update$db->quoteIdentifier$this->indexTableName ) )
  275.           ->set'lft'$q->expr->add'lft'$width ) )
  276.           ->where$q->expr->gte'lft'$right ) );
  277.         if count$excludedIds ) )
  278.         {
  279.             $q->where$q->expr->not$q->expr->in'id'$excludedIds ) ) );
  280.         }
  281.         $q->prepare()->execute();
  282.     }
  283.  
  284.     /**
  285.      * Adds the node $childNode as child of the node with ID $parentId.
  286.      *
  287.      * @param string $parentId 
  288.      * @param ezcTreeNode $childNode 
  289.      */
  290.     public function addChild$parentIdezcTreeNode $childNode )
  291.     {
  292.         if $this->inTransaction )
  293.         {
  294.             $this->addTransactionItemnew ezcTreeTransactionItemezcTreeTransactionItem::ADD$childNodenull$parentId ) );
  295.             return;
  296.         }
  297.  
  298.         $db $this->dbh;
  299.  
  300.         // Fetch parent's information
  301.         list$left$right$width $this->fetchNodeInformation$parentId );
  302.  
  303.         // Update left and right values to account for new subtree
  304.         $this->updateNestedValuesForSubtreeAddition$right);
  305.  
  306.         // Add new node
  307.         if $width == )
  308.         {
  309.             $newLeft $left 1;
  310.             $newRight $left 2;
  311.         }
  312.         else
  313.         {
  314.             $newLeft $right;
  315.             $newRight $right 1;
  316.         }
  317.  
  318.         // INSERT INTO indexTable
  319.         // SET parent_id = $parentId,
  320.         //     id = $childNode->id,
  321.         //     lft = $newLeft,
  322.         //     rgt = $newRight
  323.         $q $this->createAddNodeQuery$childNode->id );
  324.         $q->set'parent_id'$q->bindValue$parentId ) )
  325.           ->set'id'$q->bindValue$childNode->id ) )
  326.           ->set'lft'$q->bindValue$newLeft ) )
  327.           ->set'rgt'$q->bindValue$newRight ) );
  328.         $s $q->prepare();
  329.         $s->execute();
  330.  
  331.         // Add the data for the new node
  332.         $this->store->storeDataForNode$childNode$childNode->data );
  333.     }
  334.  
  335.     /**
  336.      * Returns the left, right and width values for the node with ID $nodeId as an
  337.      * array.
  338.      *
  339.      * The format of the array is:
  340.      * - 0: left value
  341.      * - 1: right value
  342.      * - 2: width value (right - left + 1)
  343.      *
  344.      * @param string $nodeId 
  345.      * @return array(int) 
  346.      */
  347.     protected function fetchNodeInformation$nodeId )
  348.     {
  349.         $db $this->dbh;
  350.  
  351.         // SELECT lft, rgt, rft-lft+1 as width
  352.         // FROM indexTable
  353.         // WHERE id = $nodeId
  354.         $q $db->createSelectQuery();
  355.         $q->select'lft, rgt, rgt - lft + 1 as width' )
  356.           ->from$db->quoteIdentifier$this->indexTableName ) )
  357.           ->where$q->expr->eq'id'$q->bindValue$nodeId ) ) );
  358.         $s $q->prepare();
  359.         $s->execute();
  360.         $r $s->fetchAllPDO::FETCH_NUM );
  361.         return $r[0];
  362.     }
  363.  
  364.     /**
  365.      * Updates the left and right values in case a subtree is deleted.
  366.      *
  367.      * @param int $right 
  368.      * @param int $width 
  369.      */
  370.     protected function updateNestedValuesForSubtreeDeletion$right$width )
  371.     {
  372.         $db $this->dbh;
  373.  
  374.         // Move all the right values + $width for nodes where the the right
  375.         // value > the parent right value
  376.         //   UPDATE indexTable
  377.         //   SET rgt = rgt - $width
  378.         //   WHERE rgt > $right
  379.         $q $db->createUpdateQuery();
  380.         $q->update$db->quoteIdentifier$this->indexTableName ) )
  381.           ->set'rgt'$q->expr->sub'rgt'$width ) )
  382.           ->where$q->expr->gt'rgt'$right ) );
  383.         $q->prepare()->execute();
  384.  
  385.         // Move all the right values + $width for nodes where the the left
  386.         // value > the parent right value
  387.         //   UPDATE indexTable
  388.         //   SET lft = lft - $width
  389.         //   WHERE lft > $right
  390.         $q $db->createUpdateQuery();
  391.         $q->update$db->quoteIdentifier$this->indexTableName ) )
  392.           ->set'lft'$q->expr->sub'lft'$width ) )
  393.           ->where$q->expr->gt'lft'$right ) );
  394.         $q->prepare()->execute();
  395.     }
  396.  
  397.     /**
  398.      * Deletes the node with ID $nodeId from the tree, including all its children.
  399.      *
  400.      * @param string $nodeId 
  401.      */
  402.     public function delete$nodeId )
  403.     {
  404.         if $this->inTransaction )
  405.         {
  406.             $this->addTransactionItemnew ezcTreeTransactionItemezcTreeTransactionItem::DELETEnull$nodeId ) );
  407.             return;
  408.         }
  409.  
  410.         // Delete all data for the deleted nodes
  411.         $nodeList $this->fetchSubtreeDepthFirst$nodeId );
  412.         $this->store->deleteDataForNodes$nodeList );
  413.  
  414.         // Fetch node information
  415.         list$left$right$width $this->fetchNodeInformation$nodeId );
  416.  
  417.         // DELETE FROM indexTable
  418.         // WHERE lft BETWEEN $left and $right
  419.         $db $this->dbh;
  420.         $q $db->createDeleteQuery();
  421.         $q->deleteFrom$db->quoteIdentifier$this->indexTableName ) )
  422.           ->where$q->expr->between'lft'$left$right ) );
  423.         $s $q->prepare();
  424.         $s->execute();
  425.  
  426.         // Update the left and right values to account for the removed subtree
  427.         $this->updateNestedValuesForSubtreeDeletion$right$width );
  428.     }
  429.  
  430.     /**
  431.      * Moves the node with ID $nodeId as child to the node with ID $targetParentId.
  432.      *
  433.      * @param string $nodeId 
  434.      * @param string $targetParentId 
  435.      */
  436.     public function move$nodeId$targetParentId )
  437.     {
  438.         if $this->inTransaction )
  439.         {
  440.             $this->addTransactionItemnew ezcTreeTransactionItemezcTreeTransactionItem::MOVEnull$nodeId$targetParentId ) );
  441.             return;
  442.         }
  443.  
  444.         $db $this->dbh;
  445.  
  446.         // Get the nodes that are gonne be moved in the subtree
  447.         $nodeIds array();
  448.         foreach $this->fetchSubtreeDepthFirst$nodeId )->nodes as $node )
  449.         {
  450.             $nodeIds[$node->id;
  451.         }
  452.  
  453.         // Update parent ID for the node
  454.         //   UPDATE indexTable
  455.         //   SET parent_id = $targetParentId
  456.         //   WHERE id = $nodeId
  457.         $q $db->createUpdateQuery();
  458.         $q->update$db->quoteIdentifier$this->indexTableName ) )
  459.           ->set'parent_id'$q->bindValue$targetParentId ) )
  460.           ->where$q->expr->eq'id'$q->bindValue$nodeId ) ) );
  461.  
  462.         $s $q->prepare();
  463.         $s->execute();
  464.  
  465.         // Fetch node information
  466.         list$origLeft$origRight$origWidth $this->fetchNodeInformation$nodeId );
  467.  
  468.         // Update the nested values to account for the moved subtree (delete part)
  469.         $this->updateNestedValuesForSubtreeDeletion$origRight$origWidth );
  470.  
  471.         // Fetch node information
  472.         list$targetParentLeft$targetParentRight$targerParentWidth $this->fetchNodeInformation$targetParentId );
  473.  
  474.         // Update the nested values to account for the moved subtree (addition part)
  475.         $this->updateNestedValuesForSubtreeAddition$targetParentRight$origWidth$nodeIds );
  476.  
  477.         // Update nodes in moved subtree
  478.         $adjust $targetParentRight $origLeft;
  479.  
  480.         // UPDATE indexTable
  481.         // SET rgt = rgt + $adjust
  482.         // WHERE id in $nodeIds
  483.         $q $db->createUpdateQuery();
  484.         $q->update$db->quoteIdentifier$this->indexTableName ) )
  485.           ->set'rgt'$q->expr->add'rgt'$adjust ) )
  486.           ->where$q->expr->in'id'$nodeIds ) );
  487.         $q->prepare()->execute();
  488.  
  489.         // UPDATE indexTable
  490.         // SET lft = lft + $adjust
  491.         // WHERE id in $nodeIds
  492.         $q $db->createUpdateQuery();
  493.         $q->update$db->quoteIdentifier$this->indexTableName ) )
  494.           ->set'lft'$q->expr->add'lft'$adjust ) )
  495.           ->where$q->expr->in'id'$nodeIds ) );
  496.         $q->prepare()->execute();
  497.     }
  498. }
  499. ?>
Documentation generated by phpDocumentor 1.4.3