From 4a93637eb9f0da1e06e1a595768752d1c4fe939b Mon Sep 17 00:00:00 2001 From: legenerationlazi Date: Thu, 19 Apr 2007 20:18:41 +0000 Subject: [PATCH] combined hierarchical data docs(missing from TOC structure renaming) --- ...l data - Adjacency list - Introduction.php | 5 +- ...mapping - Hierarchical data - Examples.php | 405 +++++++++++++++++- ...l data - Introduction - Node interface.php | 97 ++++- ...hical data - Introduction - Setting up.php | 28 +- ...oduction - Traversing or Walking Trees.php | 47 +- ...l data - Introduction - Tree interface.php | 34 +- ...ing - Hierarchical data - Introduction.php | 4 + ...ata - Materialized path - Introduction.php | 5 +- ...hical data - Nested set - Introduction.php | 4 + ...rchical data - Nested set - Setting up.php | 26 +- ...hical data - Nested set - Tree options.php | 31 +- 11 files changed, 677 insertions(+), 9 deletions(-) diff --git a/manual/docs/Object relational mapping - Hierarchical data - Adjacency list - Introduction.php b/manual/docs/Object relational mapping - Hierarchical data - Adjacency list - Introduction.php index e22100a60..0ccc6e8cf 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Adjacency list - Introduction.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Adjacency list - Introduction.php @@ -1 +1,4 @@ -Not yet implemented \ No newline at end of file +Not yet implemented + + + diff --git a/manual/docs/Object relational mapping - Hierarchical data - Examples.php b/manual/docs/Object relational mapping - Hierarchical data - Examples.php index 6db94816c..edb9cb4b6 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Examples.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Examples.php @@ -1 +1,404 @@ -This is an example to show how you would set up and use the doctrine tree interface with the NestedSet implementation (currently the most comprehensively supported by Doctrine) \ No newline at end of file +This is an example to show how you would set up and use the doctrine tree interface with the NestedSet implementation (currently the most comprehensively supported by Doctrine) + + + + + +require_once("path/to/Doctrine.php"); + +function __autoload($classname) { + + return Doctrine::autoload($classname); + +} + +// define our tree +class Menu extends Doctrine_Record { + public function setTableDefinition() { + + $this->setTableName('menu'); + + // add this your table definition to set the table as NestedSet tree implementation + $this->actsAsTree('NestedSet'); + + // you do not need to add any columns specific to the nested set implementation + // these are added for you + $this->hasColumn("name","string",30); + + } + + // this __toString() function is used to get the name for the path, see node::getPath + public function __toString() { + return $this->get('name'); + } +} + +// set connections to database +$dsn = 'mysql:dbname=nestedset;host=localhost'; +$user = 'user'; +$password = 'pass'; + +try { + $dbh = new PDO($dsn, $user, $password); +} catch (PDOException $e) { + echo 'Connection failed: ' . $e->getMessage(); +} + +$manager = Doctrine_Manager::getInstance(); + +$conn = $manager->openConnection($dbh); + +// create root +$root = new Menu(); +$root->set('name', 'root'); + +$manager->getTable('Menu')->getTree()->createRoot($root); + +// build tree +$two = new Menu(); +$two->set('name', '2'); +$root->getNode()->addChild($two); + +$one = new Menu(); +$one->set('name', '1'); +$one->getNode()->insertAsPrevSiblingOf($two); + +// refresh as node's lft and rgt values have changed +$two->refresh(); + +$three = new Menu(); +$three->set('name', '3'); +$three->getNode()->insertAsNextSiblingOf($two); +$two->refresh(); + +$one_one = new Menu(); +$one_one->set('name', '1.1'); +$one_one->getNode()->insertAsFirstChildOf($one); +$one->refresh(); + +$one_two = new Menu(); +$one_two->set('name', '1.2'); +$one_two->getNode()->insertAsLastChildOf($one); +$one_two->refresh(); + +$one_two_one = new Menu(); +$one_two_one->set('name', '1.2.1'); +$one_two->getNode()->addChild($one_two_one); + +$root->refresh(); +$four = new Menu(); +$four->set('name', '4'); +$root->getNode()->addChild($four); + +$root->refresh(); +$five = new Menu(); +$five->set('name', '5'); +$root->getNode()->addChild($five); + +$root->refresh(); +$six = new Menu(); +$six->set('name', '6'); +$root->getNode()->addChild($six); + +output_message('initial tree'); +output_tree($root); + +$one_one->refresh(); +$six->set('name', '1.0 (was 6)'); +$six->getNode()->moveAsPrevSiblingOf($one_one); + +$one_two->refresh(); +$five->refresh(); +$five->set('name', '1.3 (was 5)'); +$five->getNode()->moveAsNextSiblingOf($one_two); + +$one_one->refresh(); +$four->refresh(); +$four->set('name', '1.1.1 (was 4)'); +$four->getNode()->moveAsFirstChildOf($one_one); + +$root->refresh(); +$one_two_one->refresh(); +$one_two_one->set('name', 'last (was 1.2.1)'); +$one_two_one->getNode()->moveAsLastChildOf($root); + +output_message('transformed tree'); +output_tree($root); + +$one_one->refresh(); +$one_one->deleteNode(); + +output_message('delete 1.1'); +output_tree($root); + +// now test fetching root +$tree_root = $manager->getTable('Menu')->getTree()->findRoot(); +output_message('testing fetch root and outputting tree from the root node'); +output_tree($tree_root); + +// now test fetching the tree +output_message('testing fetching entire tree using tree::fetchTree()'); +$tree = $manager->getTable('Menu')->getTree()->fetchTree(); +while($node = $tree->next()) +{ + output_node($node); +} + +// now test fetching the tree +output_message('testing fetching entire tree using tree::fetchTree(), excluding root node'); +$tree = $manager->getTable('Menu')->getTree()->fetchTree(array('include_record' => false)); +while($node = $tree->next()) +{ + output_node($node); +} + +// now test fetching the branch +output_message('testing fetching branch for 1, using tree::fetchBranch()'); +$one->refresh(); +$branch = $manager->getTable('Menu')->getTree()->fetchBranch($one->get('id')); +while($node = $branch->next()) +{ + output_node($node); +} + +// now test fetching the tree +output_message('testing fetching branch for 1, using tree::fetchBranch() excluding node 1'); +$tree = $manager->getTable('Menu')->getTree()->fetchBranch($one->get('id'), array('include_record' => false)); +while($node = $tree->next()) +{ + output_node($node); +} + +// now perform some tests +output_message('descendants for 1'); +$descendants = $one->getNode()->getDescendants(); +while($descendant = $descendants->next()) +{ + output_node($descendant); +} + +// move one and children under two +$two->refresh(); +$one->getNode()->moveAsFirstChildOf($two); + +output_message('moved one as first child of 2'); +output_tree($root); + +output_message('descendants for 2'); +$two->refresh(); +$descendants = $two->getNode()->getDescendants(); +while($descendant = $descendants->next()) +{ + output_node($descendant); +} + +output_message('number descendants for 2'); +echo $two->getNode()->getNumberDescendants() .'
'; + +output_message('children for 2 (notice excludes children of children, known as descendants)'); +$children = $two->getNode()->getChildren(); +while($child = $children->next()) +{ + output_node($child); +} + +output_message('number children for 2'); +echo $two->getNode()->getNumberChildren() .'
'; + +output_message('path to 1'); +$path = $one->getNode()->getPath(' > '); +echo $path .' +'; + +output_message('path to 1 (including 1)'); +$path = $one->getNode()->getPath(' > ', true); +echo $path .' +'; + +output_message('1 has parent'); +$hasParent = $one->getNode()->hasParent(); +$msg = $hasParent ? 'true' : 'false'; +echo $msg . '
'; + +output_message('parent to 1'); +$parent = $one->getNode()->getParent(); +if($parent->exists()) +{ + echo $parent->get('name') .' +'; +} + +output_message('root isRoot?'); +$isRoot = $root->getNode()->isRoot(); +$msg = $isRoot ? 'true' : 'false'; +echo $msg . '
'; + +output_message('one isRoot?'); +$isRoot = $one->getNode()->isRoot(); +$msg = $isRoot ? 'true' : 'false'; +echo $msg . '
'; + +output_message('root hasParent'); +$hasParent = $root->getNode()->hasParent(); +$msg = $hasParent ? 'true' : 'false'; +echo $msg . '
'; + +output_message('root getParent'); +$parent = $root->getNode()->getParent(); +if($parent->exists()) +{ + echo $parent->get('name') .' +'; +} + +output_message('get first child of root'); +$record = $root->getNode()->getFirstChild(); +if($record->exists()) +{ + echo $record->get('name') .' +'; +} + +output_message('get last child of root'); +$record = $root->getNode()->getLastChild(); +if($record->exists()) +{ + echo $record->get('name') .' +'; +} + +$one_two->refresh(); + +output_message('get prev sibling of 1.2'); +$record = $one_two->getNode()->getPrevSibling(); +if($record->exists()) +{ + echo $record->get('name') .' +'; +} + +output_message('get next sibling of 1.2'); +$record = $one_two->getNode()->getNextSibling(); +if($record->exists()) +{ + echo $record->get('name') .' +'; +} + +output_message('siblings of 1.2'); +$siblings = $one_two->getNode()->getSiblings(); +foreach($siblings as $sibling) +{ + if($sibling->exists()) + echo $sibling->get('name') .' +'; +} + +output_message('siblings of 1.2 (including 1.2)'); +$siblings = $one_two->getNode()->getSiblings(true); +foreach($siblings as $sibling) +{ + if($sibling->exists()) + echo $sibling->get('name') .' +'; +} + +$new = new Menu(); +$new->set('name', 'parent of 1.2'); +$new->getNode()->insertAsParentOf($one_two); + +output_message('added a parent to 1.2'); +output_tree($root); + +try { + $dummy = new Menu(); + $dummy->set('name', 'dummy'); + $dummy->save(); +} +catch (Doctrine_Exception $e) +{ + output_message('You cannot save a node unless it is in the tree'); +} + +try { + $fake = new Menu(); + $fake->set('name', 'dummy'); + $fake->set('lft', 200); + $fake->set('rgt', 1); + $fake->save(); +} +catch (Doctrine_Exception $e) +{ + output_message('You cannot save a node with bad lft and rgt values'); +} + +// check last remaining tests +output_message('New parent is descendant of 1'); +$one->refresh(); +$res = $new->getNode()->isDescendantOf($one); +$msg = $res ? 'true' : 'false'; +echo $msg . '
'; + +output_message('New parent is descendant of 2'); +$two->refresh(); +$res = $new->getNode()->isDescendantOf($two); +$msg = $res ? 'true' : 'false'; +echo $msg . '
'; + +output_message('New parent is descendant of 1.2'); +$one_two->refresh(); +$res = $new->getNode()->isDescendantOf($one_two); +$msg = $res ? 'true' : 'false'; +echo $msg . '
'; + +output_message('New parent is descendant of or equal to 1'); +$one->refresh(); +$res = $new->getNode()->isDescendantOfOrEqualTo($one); +$msg = $res ? 'true' : 'false'; +echo $msg . '
'; + +output_message('New parent is descendant of or equal to 1.2'); +$one_two->refresh(); +$res = $new->getNode()->isDescendantOfOrEqualTo($one_two); +$msg = $res ? 'true' : 'false'; +echo $msg . '
'; + +output_message('New parent is descendant of or equal to 1.3'); +$five->refresh(); +$res = $new->getNode()->isDescendantOfOrEqualTo($new); +$msg = $res ? 'true' : 'false'; +echo $msg . '
'; + +function output_tree($root) +{ + // display tree + // first we must refresh the node as the tree has been transformed + $root->refresh(); + + // next we must get the iterator to traverse the tree from the root node + $traverse = $root->getNode()->traverse(); + + output_node($root); + // now we traverse the tree and output the menu items + while($item = $traverse->next()) + { + output_node($item); + } + + unset($traverse); +} + +function output_node($record) +{ + echo str_repeat('-', $record->getNode()->getLevel()) . $record->get('name') + . ' (has children:'.$record->getNode()->hasChildren().') ' + . ' (is leaf:'.$record->getNode()->isLeaf().') '.'
'; +} + +function output_message($msg) +{ + echo " +**//$msg//**".' +'; +} +
\ No newline at end of file diff --git a/manual/docs/Object relational mapping - Hierarchical data - Introduction - Node interface.php b/manual/docs/Object relational mapping - Hierarchical data - Introduction - Node interface.php index 922dbae75..16426c1f7 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Introduction - Node interface.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Introduction - Node interface.php @@ -1,3 +1,98 @@ -The node interface, for inserting and manipulating nodes within the tree, is accessed on a record level. A full implementation of this interface will be as follows: \ No newline at end of file +The node interface, for inserting and manipulating nodes within the tree, is accessed on a record level. A full implementation of this interface will be as follows: + + + + +interface Doctrine_Node_Interface { + + /** + * insert node into tree + */ + public function insertAsParentOf(Doctrine_Record $dest); + + public function insertAsPrevSiblingOf(Doctrine_Record $dest); + + public function insertAsNextSiblingOf(Doctrine_Record $dest); + + public function insertAsFirstChildOf(Doctrine_Record $dest); + + public function insertAsLastChildOf(Doctrine_Record $dest); + + public function addChild(Doctrine_Record $record); + + /** + * moves node (if has children, moves branch) + * + */ + public function moveAsPrevSiblingOf(Doctrine_Record $dest); + + public function moveAsNextSiblingOf(Doctrine_Record $dest); + + public function moveAsFirstChildOf(Doctrine_Record $dest); + + public function moveAsLastChildOf(Doctrine_Record $dest); + + /** + * node information + */ + public function getPrevSibling(); + + public function getNextSibling(); + + public function getSiblings($includeNode = false); + + public function getFirstChild(); + + public function getLastChild(); + + public function getChildren(); + + public function getDescendants(); + + public function getParent(); + + public function getAncestors(); + + public function getPath($seperator = ' > ', $includeNode = false); + + public function getLevel(); + + public function getNumberChildren(); + + public function getNumberDescendants(); + + /** + * node checks + */ + public function hasPrevSibling(); + + public function hasNextSibling(); + + public function hasChildren(); + + public function hasParent(); + + public function isLeaf(); + + public function isRoot(); + + public function isEqualTo(Doctrine_Record $subj); + + public function isDescendantOf(Doctrine_Record $subj); + + public function isDescendantOfOrEqualTo(Doctrine_Record $subj); + + public function isValidNode(); + + /** + * deletes node and it's descendants + */ + public function delete(); +} + +// if your model acts as tree you can retrieve the associated node object as follows +$record = $manager->getTable('Model')->find($pk); +$nodeObj = $record->getNode(); + \ No newline at end of file diff --git a/manual/docs/Object relational mapping - Hierarchical data - Introduction - Setting up.php b/manual/docs/Object relational mapping - Hierarchical data - Introduction - Setting up.php index 43780ac34..10df55837 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Introduction - Setting up.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Introduction - Setting up.php @@ -8,4 +8,30 @@ Now that Doctrine knows that this model acts as a tree, it will automatically ad -Doctrine has standard interface's for managing tree's, that are used by all the implementations. Every record in the table represents a node within the tree (the table), so doctrine provides two interfaces, Tree and Node. \ No newline at end of file +Doctrine has standard interface's for managing tree's, that are used by all the implementations. Every record in the table represents a node within the tree (the table), so doctrine provides two interfaces, Tree and Node. + + + + +class Menu extends Doctrine_Record { + public function setTableDefinition() { + + $this->setTableName('menu'); + + // add this your table definition to set the table as NestedSet tree implementation + // $implName is 'NestedSet' or 'AdjacencyList' or 'MaterializedPath' + // $options is an assoc array of options, see implementation docs for options + $this->option('treeImpl', $implName); + $this->option('treeOptions', $options); + + // you do not need to add any columns specific to the nested set implementation, these are added for you + $this->hasColumn("name","string",30); + + } + + // this __toString() function is used to get the name for the path, see node::getPath() + public function __toString() { + return $this->get('name'); + } +} + \ No newline at end of file diff --git a/manual/docs/Object relational mapping - Hierarchical data - Introduction - Traversing or Walking Trees.php b/manual/docs/Object relational mapping - Hierarchical data - Introduction - Traversing or Walking Trees.php index 6aa3f35a0..b7d808e71 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Introduction - Traversing or Walking Trees.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Introduction - Traversing or Walking Trees.php @@ -4,4 +4,49 @@ You can traverse a Tree in different ways, please see here for more information -The most common way of traversing a tree is Pre Order Traversal as explained in the link above, this is also what is known as walking the tree, this is the default approach when traversing a tree in Doctrine, however Doctrine does plan to provide support for Post and Level Order Traversal (not currently implemented) \ No newline at end of file +The most common way of traversing a tree is Pre Order Traversal as explained in the link above, this is also what is known as walking the tree, this is the default approach when traversing a tree in Doctrine, however Doctrine does plan to provide support for Post and Level Order Traversal (not currently implemented) + + + + +/* + * traverse the entire tree from root + */ +$root = $manager->getTable('Model')->getTree()->fetchRoot(); +if($root->exists()) +{ + $tree = $root->traverse(); + while($node = $tree->next()) + { + // output your tree here + } +} + +// or the optimised approach using tree::fetchTree +$tree = $manager->getTable('Model')->getTree()->fetchTree(); +while($node = $tree->next()) +{ + // output tree here +} + +/* + * traverse a branch of the tree + */ +$record = $manager->getTable('Model')->find($pk); +if($record->exists()) +{ + $branch = $record->traverse(); + while($node = $branch->next()) + { + // output your tree here + } +} + +// or the optimised approach +$branch = $manager->getTable('Model')->getTree()->fetchBranch($pk); +while($node = $branch->traverse()) +{ + // output your tree here +} + + \ No newline at end of file diff --git a/manual/docs/Object relational mapping - Hierarchical data - Introduction - Tree interface.php b/manual/docs/Object relational mapping - Hierarchical data - Introduction - Tree interface.php index bd817a0a5..8638848f8 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Introduction - Tree interface.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Introduction - Tree interface.php @@ -1,3 +1,35 @@ -The tree interface, for creating and accessing the tree, is accessed on a table level. A full implementation of this interface would be as follows: \ No newline at end of file +The tree interface, for creating and accessing the tree, is accessed on a table level. A full implementation of this interface would be as follows: + + + + + +interface Doctrine_Tree_Interface { + + /** + * creates root node from given record or from a new record + */ + public function createRoot(Doctrine_Record $record = null); + + /** + * returns root node + */ + public function findRoot($root_id = 1); + + /** + * optimised method to returns iterator for traversal of the entire tree from root + */ + public function fetchTree($options = array()); + + /** + * optimised method that returns iterator for traversal of the tree from the given record's primary key + */ + public function fetchBranch($pk, $options = array()); +} + +// if your model acts as tree you can retrieve the associated tree object as follows +$treeObj = $manager->getTable('Model')->getTree(); + + \ No newline at end of file diff --git a/manual/docs/Object relational mapping - Hierarchical data - Introduction.php b/manual/docs/Object relational mapping - Hierarchical data - Introduction.php index e69de29bb..fd40910d9 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Introduction.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Introduction.php @@ -0,0 +1,4 @@ + + + + diff --git a/manual/docs/Object relational mapping - Hierarchical data - Materialized path - Introduction.php b/manual/docs/Object relational mapping - Hierarchical data - Materialized path - Introduction.php index e22100a60..0ccc6e8cf 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Materialized path - Introduction.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Materialized path - Introduction.php @@ -1 +1,4 @@ -Not yet implemented \ No newline at end of file +Not yet implemented + + + diff --git a/manual/docs/Object relational mapping - Hierarchical data - Nested set - Introduction.php b/manual/docs/Object relational mapping - Hierarchical data - Nested set - Introduction.php index 031e0c5b1..187e3fc71 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Nested set - Introduction.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Nested set - Introduction.php @@ -8,3 +8,7 @@ For more information, read here: [http://www.sitepoint.com/article/hierarchical-data-database/2 http://www.sitepoint.com/article/hierarchical-data-database/2], [http://dev.mysql.com/tech-resources/articles/hierarchical-data.html http://dev.mysql.com/tech-resources/articles/hierarchical-data.html] + + + + diff --git a/manual/docs/Object relational mapping - Hierarchical data - Nested set - Setting up.php b/manual/docs/Object relational mapping - Hierarchical data - Nested set - Setting up.php index 05acc4ec9..5f8dc320c 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Nested set - Setting up.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Nested set - Setting up.php @@ -1,3 +1,27 @@ -To set up your model as Nested Set, you must add the following code to your model's table definition. \ No newline at end of file +To set up your model as Nested Set, you must add the following code to your model's table definition. + + + + +class Menu extends Doctrine_Record { + public function setTableDefinition() { + + $this->setTableName('menu'); + + // add this your table definition to set the table as NestedSet tree implementation + $this->option('treeImpl', 'NestedSet'); + $this->option('treeOptions', array()); + + // you do not need to add any columns specific to the nested set implementation, these are added for you + $this->hasColumn("name","string",30); + + } + + // this __toString() function is used to get the name for the path, see node::getPath() + public function __toString() { + return $this->get('name'); + } +} + \ No newline at end of file diff --git a/manual/docs/Object relational mapping - Hierarchical data - Nested set - Tree options.php b/manual/docs/Object relational mapping - Hierarchical data - Nested set - Tree options.php index b344d7d11..8c870f12c 100644 --- a/manual/docs/Object relational mapping - Hierarchical data - Nested set - Tree options.php +++ b/manual/docs/Object relational mapping - Hierarchical data - Nested set - Tree options.php @@ -4,4 +4,33 @@ The nested implementation can be configured to allow your table to have multiple -The example below shows how to setup and use multiple roots based upon the set up above: \ No newline at end of file +The example below shows how to setup and use multiple roots based upon the set up above: + + + + +//use these options in the setTableDefinition +$options = array('hasManyRoots' => true, // enable many roots + 'rootColumnName' => 'root_id'); // set root column name, defaults to 'root_id' + +// To create new root nodes, if you have manually set the root_id, then it will be used +// otherwise it will automatically use the next available root id +$root = new Menu(); +$root->set('name', 'root'); + +// insert first root, will auto be assigned root_id = 1 +$manager->getTable('Menu')->getTree()->createRoot($root); + +$another_root = new Menu(); +$another_root->set('name', 'another root'); + +// insert another root, will auto be assigned root_id = 2 +$manager->getTable('Menu')->getTree()->createRoot($another_root); + +// fetching a specifc root +$root = $manager->getTable('Menu')->getTree()->fetchRoot(1); +$another_root = $manager->getTable('Menu')->getTree()->fetchRoot(2); + +// fetching all roots +$roots = $manager->getTable('Menu')->getTree()->fetchRoots(); +