Commit 4a93637e authored by legenerationlazi's avatar legenerationlazi

combined hierarchical data docs(missing from TOC structure renaming)

parent c3439aee
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)
<code type="php">
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() .'</br>';
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() .'</br>';
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 . '</br/>';
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 . '</br/>';
output_message('one isRoot?');
$isRoot = $one->getNode()->isRoot();
$msg = $isRoot ? 'true' : 'false';
echo $msg . '</br/>';
output_message('root hasParent');
$hasParent = $root->getNode()->hasParent();
$msg = $hasParent ? 'true' : 'false';
echo $msg . '</br/>';
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 . '</br/>';
output_message('New parent is descendant of 2');
$two->refresh();
$res = $new->getNode()->isDescendantOf($two);
$msg = $res ? 'true' : 'false';
echo $msg . '</br/>';
output_message('New parent is descendant of 1.2');
$one_two->refresh();
$res = $new->getNode()->isDescendantOf($one_two);
$msg = $res ? 'true' : 'false';
echo $msg . '</br/>';
output_message('New parent is descendant of or equal to 1');
$one->refresh();
$res = $new->getNode()->isDescendantOfOrEqualTo($one);
$msg = $res ? 'true' : 'false';
echo $msg . '</br/>';
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 . '</br/>';
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 . '</br/>';
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().') '.'<br/>';
}
function output_message($msg)
{
echo "
**//$msg//**".'
';
}
</code>
\ 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:
<code type="php">
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();
</code>
\ No newline at end of file
......@@ -9,3 +9,29 @@ 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.
<code type="php">
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');
}
}
</code>
\ No newline at end of file
......@@ -5,3 +5,48 @@ 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)
<code type="php">
/*
* 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
}
</code>
\ 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:
<code type="php">
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();
</code>
\ No newline at end of file
......@@ -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]
To set up your model as Nested Set, you must add the following code to your model's table definition.
<code type="php">
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');
}
}
</code>
\ No newline at end of file
......@@ -5,3 +5,32 @@ 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:
<code type="php">
//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();
</code>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment