eZ Components - Tree ~~~~~~~~~~~~~~~~~~~~ .. contents:: Table of Contents Introduction ============ The Tree component enables you to create, manipulate and query tree structures. The component provides many operations on trees, as well as on the nodes in the trees. Because there are different algorithms for storing tree structures in a relational database—each with different properties—the component supports multiple back-ends. The Tree component itself comes with a memory (ezcTreeMemory) and XML (ezcTreeXml) back-end. The TreeDatabaseTiein component provides three back-ends that store the tree structure in a database table. Aside from storing the hierarchical data itself, the component also enables you to associate data with the tree nodes. The data is stored through a data store. Depending on the back-ends, different data stores are available, as shown in the following table: =============================== =========================================================================== Back-end Data Stores =============================== =========================================================================== ezcTreeMemory ezcTreeMemoryDataStore ezcTreeXml ezcTreeXmlInternalDataStore ezcTreeDbMaterializedPath [1]_ ezcTreeDbExternalTableDataStore [1]_, ezcTreePersistentObjectDataStore [2]_ ezcTreeDbNestedSet [1]_ ezcTreeDbExternalTableDataStore [1]_, ezcTreePersistentObjectDataStore [2]_ ezcTreeDbParentChild [1]_ ezcTreeDbExternalTableDataStore [1]_, ezcTreePersistentObjectDataStore [2]_ =============================== =========================================================================== .. [1] Available through the TreeDatabaseTiein_ component .. [2] Available through the TreePersistentObjectTiein_ component Dependencies ============ From the table and comments above, it becomes apparent that there are a few optional dependencies. Through the TreeDatabaseTiein_ and TreePersistentObjectTiein_ components, additional functionality becomes available. Class overview ============== ezcTreeMemory, ezcTreeXml These two classes are available (without using the Tiein components) to store tree data in memory or in an XML file. There is a matching data store for each of those two classes (ezcTreeMemoryDataStore and ezcTreeXmlInternalDataStore). ezcTreeDbMaterializedPath, ezcTreeDbNestedSet, ezcTreeDbParentChild These three back-ends are made available through the TreeDatabaseTiein_ component. Each of them implements a different strategy for storing the relations between nodes. The nature of your application will determine the appropriate back-end. For more information, see the Back-ends_ section. ezcTreeNode This class represents one node of the tree. Objects of this class can be added to the tree. The object stores both the ID and data belonging to the node. Data is always fetched on demand, unless ezcTreeNodeListIterator is used with the pre-fetch option. ezcTreeNodeListIterator This class can be used to iterate over an ezcTreeNodeList, which is returned by many of the node fetching operations (see the ezcTree documentation for which operations are supported). It is advised to use this class to iterate through the nodes and not to simply use foreach() on a returned ezcTreeNodeList. This is because this class also supports pre-fetching associated data, which can drastically reduce the number of queries being run in case a database-based data store is used (such as ezcTreeDbExternalTableDataStore or ezcTreePersistentObjectDataStore). Basic usage =========== To use a tree, you will need both a tree back-end (a class inherited from ezcTree), and a data store (implementation of the ezcTreeDataStore interface). The following example shows how to instantiate a new tree object using the ezcTreeXml back-end and the ezcTreeXmlInternalDataStore data store: .. include:: tutorial_example_basic.php :literal: Lines 4 and 5 define the data store and the tree. The parameters to ezcTree's constructor specify which file and data store to use. After opening the tree, lines 7 and 8 demonstrate how to fetch a node from the tree by using the node ID, and then access the data that belongs to the node. IDs can be either integer numbers or strings. There are a few restrictions when using strings as IDs; they must: #. be a valid PHP array key #. consist of XML NameChar_ characters only .. _NameChar: http://www.w3.org/TR/2000/WD-xml-2e-20000814#NT-NameChar Memory trees can never be instantiated, because there is no permanent storage available. They will always have to be created from scratch or created from another tree by using the ezcTree::copy() method. Operations on trees ------------------- It is possible to run many operations on trees and nodes, other than fetching the nodes. The following example demonstrates two different types of operations on the tree or on nodes. .. include:: tutorial_example_treeops.php :literal: Lines 7 to 11 show how to tell whether a node is a descendant of another node. Most of the operations can also be done directly on the tree by using the node IDs. This is shown in lines 13 to 16. All operations are implemented on the tree level, so using the syntax in lines 13 to 16 will result in slightly higher performance. Lines 18 to 23 demonstrate another tree operation: fetching a sub-tree. All operations that can return more than one node do so as an ezcTreeNodeList. Refer to the ezcTreeNode documentation for a full list of supported operations. Iterating over a node list -------------------------- When you iterate over a node list manually, such as in the previous examples, the associated data is fetched on demand - at the moment the ->data property of a node is requested. If you have a large node list to be returned, this can create many database queries if you are using a database-based back-end and data store. In such cases, you might want to fetch all data in one go - with one query. The ezcTreeNodeListIterator class enables you to do this: .. include:: tutorial_example_iterator.php :literal: In line 7 we use the ezcTree->fetchChildren() method to find all the direct children of the node with the ID "NobleGasses". Then in lines 10 to 13 we create a ezcTreeNodeListIterator over the returned ezcTreeNodeList $noble. The first parameter is the tree, the second one is the node list, and the third parameter specifies whether the data should be prefetched. Creating and modifying a tree ----------------------------- If you want to create a new tree, instead of instantiating a tree you use the overloaded ezcTree::create() factory method. Once a tree and associated store are created you can proceed to fill the tree with nodes. The example below demonstrates this (and creates the XML file that is used in the other examples in this tutorial): .. include:: tutorial_example_create.php :literal: In line 5 we create a new tree by using the ezcTreeXml::create() factory method. The name of the file is the first argument and the data store is the second argument. This will create a completely empty tree without nodes or even a root node. In lines 7 and 8 we then create a new node with the ezcTree->createNode() method, which accepts the node ID and node data value as arguments. The ezcTreeDbExternalTableDataStore data store also supports compound data values, as is listed in the documentation for ezcTreeDbExternalTableDataStore->__construct(). Lines 10 to 13 proceed to add two new nodes to the $rootNode and lines 15-26 add further nodes to the $nonMetal and $nobleGasses nodes. Auto-generated IDs `````````````````` The Tree component also supports auto-generated IDs for tree nodes. In order to let the component generate IDs for you, you need to set the Tree property "autoId" to "true". In the example below we create another tree structure, but let the Tree component generate the IDs automatically: .. include:: tutorial_example_create_auto_id.php :literal: The main change compared to the previous example is the setting of autoId to true in line 6. When autoId is set, the ID argument to ezcTree::createNode() can be null. In that case an ID is automatically generated. This property *must* be set immediately after instantiating the tree, unless an already existing tree is opened through the ezcTreeXml class. For database backends, there are a few extra requirements for the schema. The "id" field should be defined as an auto-increment integer field, or an integer field linked with a sequence. Back-ends ========= Non-database back-ends ---------------------- The Tree component comes with two generic back-ends - one that stores the tree structure in an XML file, and another one that only keeps a tree structure in memory. The XML back-end uses PHP's DOM functionality to parse the tree and thus the entire tree structure is loaded into memory when an ezcTreeXml object is instantiated. The ezcTreeMemory back-end has to be created from scratch when it used. However, it is also possible to copy an ezcTreeXml-based tree to a memory-based one. The example below demonstrates this: .. include:: tutorial_example_copy_tree.php :literal: Operations on trees based on ezcTreeMemory are of course faster than operations on trees based on ezcTreeDb or ezcTreeXml. Database-based back-ends ------------------------ By installing the TreeDatabaseTiein_ and TreePersistentObjectTiein_ components, a few more back-ends and data stores are available. There are three additional back-ends: #. ezcTreeDbParentChild - Uses the ID of the parent to keep track of the structure only. #. ezcTreeDbNestedSet - Uses left/right values in addition to the parent ID that the ezcTreeDbParentChild back-end uses to keep track of the tree structure. #. ezcTreeDbMaterializedPath - Uses /1/2/4/6/19/24 style paths to store the tree structure. Each of those three back-ends have different performance-related properties depending on which operation is run. The following table summarizes some of the properties of each algorithm: +----------------------------+-----------------------------------------------------------------------+ | Operation | Back-ends | | +----------------------+-------------------------+----------------------+ | | Parent Child | Nested set | Materialized Path | +============================+======================+=========================+======================+ | addChild() | *Simple operation.* | Possibly long, as on | *Simple operation.* | | | | average the left and | | | | | right values of half of | | | | | the nodes in the tree | | | | | have to be updated. | | +----------------------------+----------------------+-------------------------+----------------------+ | delete() | Recursive operation | *Simple operation.* | *Simple operation.* | | | to find a whole | | but query has to | | | tree. | | use LIKE. | +----------------------------+----------------------+-------------------------+----------------------+ | fetchChildren() | *Simple operation.* | *Simple operation.* | *Simple operation.* | +----------------------------+----------------------+-------------------------+----------------------+ | fetchNodeById() | *Simple operation.* | *Simple operation.* | *Simple operation.* | +----------------------------+----------------------+-------------------------+----------------------+ | fetchParent() | *Simple operation.* | *Simple operation.* | *Simple operation.* | +----------------------------+----------------------+-------------------------+----------------------+ | fetchPath() | Recursive operation | *Simple operation.* | *Simple operation.* | | | to iterate over the | | | | | parents all the way | | | | | to the root node. | | | | | | | | +----------------------------+----------------------+-------------------------+----------------------+ | fetchSubtreeBreadthFirst() | Recursive operation | Recursive operation to | Recursive operation | | | to find the whole | find the whole subtree | to find the whole | | | subtree - order of | - order of nodes for | subtree - order of | | | nodes for each level | each level is not | nodes for each level | | | is not guaranteed. | guaranteed. | is not guaranteed. | +----------------------------+----------------------+-------------------------+----------------------+ | fetchSubtreeDepthFirst() | Recursive operation | Simple operation - | *Simple operation.* | | | to find the whole | order of nodes is the | but query has to use | | | subtree - order of | same order as when they | LIKE - order of | | | nodes is not | were added. | nodes is not | | | guaranteed. | | guaranteed. | +----------------------------+----------------------+-------------------------+----------------------+ | getChildCount() | *Simple operation.* | *Simple operation.* | *Simple operation.* | +----------------------------+----------------------+-------------------------+----------------------+ | getChildCountRecursive() | Recursive operation | *Simple operation.* | *Simple operation.* | | | to find the nodes in | | but query has to use | | | the whole subtree. | | LIKE. | | | | | | | | | | | +----------------------------+----------------------+-------------------------+----------------------+ | getPathLength() | Recursive operation | *Simple operation.* | *Simple operation.* | | | to iterate over the | | | | | parents all the way | | | | | to the root node. | | | +----------------------------+----------------------+-------------------------+----------------------+ | hasChildNodes() | *Simple operation.* | *Simple operation.* | *Simple operation.* | +----------------------------+----------------------+-------------------------+----------------------+ | isChildOf() | *Simple operation.* | *Simple operation.* | *Simple operation.* | +----------------------------+----------------------+-------------------------+----------------------+ | isDescendantOf() | Recursive operation | *Simple operation.* | *Simple operation.* | | | to iterate over the | | | | | parents until either | | | | | the root node or | | | | | when the node is | | | | | found. | | | +----------------------------+----------------------+-------------------------+----------------------+ | isSiblingOf() | *Simple operation.* | *Simple operation.* | *Simple operation.* | +----------------------------+----------------------+-------------------------+----------------------+ | move() | *Simple operation.* | Possibly long, as on | All the nodes in the | | | | average the left and | subtree that is | | | | the right values of | moved need to be | | | | half of the nodes need | updated - this is | | | | to be updated - twice. | done with string | | | | | operations. | +----------------------------+----------------------+-------------------------+----------------------+ | nodeExists() | *Simple operation.* | *Simple operation.* | *Simple operation.* | +----------------------------+----------------------+-------------------------+----------------------+ | setRootNode() | *Simple operation.* | *Simple operation.* | *Simple operation.* | +----------------------------+----------------------+-------------------------+----------------------+ Data stores ``````````` The database back-ends that the TreeDatabaseTiein_ component provides also support two different data stores. One of them, ezcTreeDbExternalTableDataStore, comes with the TreeDatabaseTiein_ component. Another one, ezcTreePersistentObjectDataStore, is provided through the TreePersistentObjectTiein_ component. Database table ++++++++++++++ ezcTreeDbExternalTableDataStore can be used in two different modes. In the first you specify a database field that is matched against the node's ID, and another field that is used for the "data" property belonging to a node. The next example illustrates this: .. include:: tutorial_example_database_one_field.php :literal: In this example, lines 4 to 22 set up the database and database tables. Refer to the specific database back-end's documentation for full information on what the different tables should look like. In this case, for the data store we only create two fields: node_id and data_field. We can see this back in line 24, where we instantiate the store object. We specify the database object, the name of the data table ('data'), the field that is matched against the node ID ('node_id') and which field to use for data ('data_field'). In lines 27 to 30 we then insert some sample nodes and line 32 demonstrates the retrieval of data. In the second mode, we do not specify a field to fetch data from: .. include:: tutorial_example_database_multi_field.php :literal: Differences when compared with the previous example include the data table definition in lines 17 to 21. Instead of defining a specific data field to use, there are now multiple fields ('melting_temp_k' and 'boiling_temp_k'). The instantiation of the data store in line 27 now misses the fourth argument as well. The data that is specified when creating a node now consists of an array describing all the fields in the database table, except for the 'node_id' as that one is implicit. When fetching the data the whole table record is returned, minus the 'node_id' field. Persistent Object +++++++++++++++++ The ezcTreePersistentObjectDataStore brings multiple data fields even further and extends the Tree to use persistent objects as data for the tree nodes. .. include:: tutorial_example_persistent_object.php :literal: The database tables are set up just like the previous example in lines 5 to 24. Lines 26 to 32 then continue to use the DatabaseSchema_ component to write persistent definition files and class stubs. The store is set up in lines 35 and 36. ezcTreePersistentObjectDataStore uses ezcPersistentSession as the first argument and then the object's class and object's ID property as second and third arguments. Unlike the previous example, you should specify the class and property names of the persistent objects that you are storing - *not* the table name and ID field. Lines 39 to 48 then show how data is inserted into the tree, and how it is retrieved. You will most likely have to tune the classes that are generated for you in a real life situation, as the generated classes (line 29 and 31) only have private properties and the getState() and setState() methods that persistent objects are required to have. Refer to the PersistentObject_ documentation for more information. Visualization ============= Sometimes it is useful to visualize a tree structure. The Tree component has some functionality for this in the form of different visualizers. There are currently three possibilities. Text-based visualization ------------------------ The ezcTreeVisitorPlainText class implements a visitor pattern to render a tree for the console. Both latin1 and utf-8 are supported as character sets, and the utf-8 version looks much better. The following example shows how to generated a text-based representation of the tree from the first example in this tutorial: .. include:: tutorial_example_text_tree.php :literal: The output is:: Elements ├─NonMetals │ ├─H │ ├─C │ ├─N │ ├─O │ ├─P │ ├─S │ └─Se └─NobleGasses ├─F ├─Cl ├─Br └─I XHTML-based visualization ------------------------- The ezcTreeVisitorXHTML class implements another visitor that renders the trees as nested XHTML lists. In the following example we render the same tree again with the default options for this visitor: .. include:: tutorial_example_xhtml.php :literal: This outputs the following XHTML:: The ezcTreeVisitorXHTML visitor allows some options that can be set through the constructor. Options include whether links should be added (addLinks), whether the root node should be shown (displayRootNode) and others. The options are documented with the ezcTreeVisitorXHTMLOptions class. GraphViz-based visualization ---------------------------- In case you are not on the console, it is also possible to render the tree as a GraphViz .dot file that can then be used to generate an image, such as a PNG image. The next example shows the use of the ezcTreeVisitorGraphViz class: .. include:: tutorial_example_graphviz.php :literal: The generated .dot file can be converted to an image by running the following command:: dot -Tpng -o img/graphviz.png files/graphviz.dot The result of this is as follows (scaled): .. figure:: img/graphviz.png YUI based visualization ----------------------- Aside from console and graphical output, there is also a visualizer that renders the tree in a YUI_ compatible XHTML output. This can be used to automatically populate a YUI_ style menu. The code is pretty much the same, except for the addition of the xmlId argument to the constructor of ezcTreeVisitorYUI. .. include:: tutorial_example_yui.php :literal: However, in order to actually render the menu, you need to have some specific JavaScript in the head element of your HTML code. First of all, you need to include the YUI code:: Then, you need the following code to turn the generated menu into a YUI menu (the {literal} opening and closing tag is required if you use the Template_ component):: The full (but minimal) code looks then like: .. include:: tutorial_example_yui_full.php :literal: The result of this is as follows (scaled): .. figure:: img/yui1.gif The ezcTreeVisitorYUI visitor allows some options that can be set through the constructor. The options are documented with the ezcTreeVisitorYUIOptions class. .. _DatabaseSchema: introduction_DatabaseSchema.html .. _PersistentObject: introduction_PersistentObject.html .. _Template: introduction_Template.html .. _TreeDatabaseTiein: introduction_TreeDatabaseTiein.html .. _TreePersistentObjectTiein: introduction_TreePersistentObjectTiein.html .. _YUI: http://developer.yahoo.com/yui/menu/ .. Local Variables: mode: rst fill-column: 79 End: vim: et syn=rst tw=79 spell