1
0
mirror of synced 2024-12-15 07:36:03 +03:00
doctrine2/manual/new/docs/en/object-relational-mapping/hierarchical-data/introduction.txt

249 lines
8.9 KiB
Plaintext
Raw Normal View History

++++ About
Most users at one time or another have dealt with hierarchical data in a SQL database and no doubt learned that the management of hierarchical data is not what a relational database is intended for. The tables of a relational database are not hierarchical (like XML), but are simply a flat list. Hierarchical data has a parent-child relationship that is not naturally represented in a relational database table.
For our purposes, hierarchical data is a collection of data where each item has a single parent and zero or more children (with the exception of the root item, which has no parent). Hierarchical data can be found in a variety of database applications, including forum and mailing list threads, business organization charts, content management categories, and product categories.
In a hierarchical data model, data is organized into a tree-like structure. The tree structure allows repeating information using parent/child relationships. For an explanation of the tree data structure, see [http://en.wikipedia.org/wiki/Tree_data_structure here].
There are three major approaches to managing tree structures in relational databases, these are:
* the adjacency list model
* the nested set model (otherwise known as the modified pre-order tree traversal algorithm)
* materialized path model
These are explained in more detail in the following chapters, or see
* [http://www.dbazine.com/oracle/or-articles/tropashko4 http://www.dbazine.com/oracle/or-articles/tropashko4]
* [http://dev.mysql.com/tech-resources/articles/hierarchical-data.html http://dev.mysql.com/tech-resources/articles/hierarchical-data.html]
++++ Setting up
Managing tree structures in doctrine is easy. Doctrine currently fully supports Nested Set, and plans to support the other implementations soon. To set your model to act as a tree, simply add the code below to your models table definition.
Now that Doctrine knows that this model acts as a tree, it will automatically add any required columns for your chosen implementation, so you do not need to set any tree specific columns within your table definition.
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>
++++ Node interface
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>
++++ Tree interface
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>
++++ Traversing or Walking Trees
You can traverse a Tree in different ways, please see here for more information [http://en.wikipedia.org/wiki/Tree_traversal http://en.wikipedia.org/wiki/Tree_traversal].
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>
++++ Read me
If performing batch tree manipulation tasks, then remember to refresh your records (see record::refresh()), as any transformations of the tree are likely to affect all instances of records that you have in your scope.
You can save an already existing node using record::save() without affecting it's position within the tree. Remember to never set the tree specific record attributes manually.
If you are inserting or moving a node within the tree, you must use the appropriate node method. Note: you do not need to save a record once you have inserted it or moved it within the tree, any other changes to your record will also be saved within these operations. You cannot save a new record without inserting it into the tree.
If you wish to delete a record, you MUST delete the node and not the record, using $record->deleteNode() or $record->getNode()->delete(). Deleting a node, will by default delete all its descendants. if you delete a record without using the node::delete() method you tree is likely to become corrupt (and fall down)!
The difference between descendants and children is that descendants include children of children whereas children are direct descendants of their parent (real children not gran children and great gran children etc etc).