README 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. = sfPropelActAsNestedSetBehaviorPlugin plugin =
  2. The `sfPropelActAsNestedSetBehaviorPlugin` is a symfony plugin that provides nested set capabilities to Propel objects.
  3. Nested sets (aka modified preorder tree traversal) is a very efficient way (in terms of performances) to browse and edit a tree like structure in an RDBMS.
  4. You can read [http://dev.mysql.com/tech-resources/articles/hierarchical-data.html a good introduction to nested sets] on MySQL developers' zone.
  5. == Features ==
  6. * Fully unit tested
  7. * Possibility to store multiple trees in the same table
  8. * Atomic operations
  9. * Also maintains adjacency list properties, making your classes compatible with code based on this tree traversal methodology
  10. == Limitations ==
  11. As of now, the plugin is known to work with MySQL and PostgreSQL (in trunk). Other RDBMS may work but without any guaranty.
  12. Patches are welcome, of course :)
  13. == Installation ==
  14. * Install the plugin
  15. {{{
  16. symfony plugin-install http://plugins.symfony-project.com/sfPropelActAsNestedSetBehaviorPlugin
  17. }}}
  18. * Add new fields to your schema.xml
  19. {{{
  20. #!xml
  21. <column name="tree_left" type="INTEGER" required="true" />
  22. <column name="tree_right" type="INTEGER" required="true" />
  23. <column name="tree_parent" type="INTEGER" required="true" />
  24. <column name="scope" type="INTEGER" required="true" />
  25. }}}
  26. `scope` and `tree_parent` columns can be of any type.
  27. * Enable Propel behavior support in `propel.ini`:
  28. {{{
  29. propel.builder.AddBehaviors = true
  30. }}}
  31. If you have to enable the behavior support, rebuild your model:
  32. {{{
  33. symfony propel-build-model
  34. }}}
  35. * Enable the behavior for one of your Propel model:
  36. {{{
  37. #!php
  38. <?php
  39. // lib/model/ForumPost.php
  40. class ForumPost
  41. {
  42. }
  43. $columns_map = array('left' => ForumPostPeer::TREE_LEFT,
  44. 'right' => ForumPostPeer::TREE_RIGHT,
  45. 'parent' => ForumPostPeer::TREE_PARENT,
  46. 'scope' => ForumPostPeer::TOPIC_ID);
  47. sfPropelBehavior::add('ForumPost', array('actasnestedset' => array('columns' => $columns_map)));
  48. }}}
  49. The ''column map'' is used by the behavior to know which columns hold information it needs :
  50. * left : Model column holding nested set left value for a row
  51. * right : Model column holding nested set right value for a row
  52. * parent : Model column holding row's parent id (this is necessary because we use adjacency list tree traversal for some methods)
  53. * scope : Model column holding row's scope id. The scope is used to differenciate trees stored in the same table
  54. == Usage ==
  55. === Simple tree creation ===
  56. {{{
  57. #!php
  58. <?php
  59. $root = new ForumPost();
  60. $root->makeRoot();
  61. $root->save();
  62. $p1 = new ForumPost();
  63. $p1->insertAsFirstChildOf($root);
  64. $p1->save();
  65. $p2 = new ForumPost();
  66. $p2->insertAsFirstChildOf($p1);
  67. $p2->save();
  68. /*
  69. * Resulting tree :
  70. *
  71. * ROOT
  72. * |- P1
  73. * |- P2
  74. */
  75. }}}
  76. === Multiple trees in a single table ===
  77. {{{
  78. #!php
  79. <?php
  80. $root1 = new ForumPost();
  81. $root1->makeRoot();
  82. $root1->setTopicId(1);
  83. $root1->save();
  84. $root2 = new ForumPost();
  85. $root2->makeRoot();
  86. $root2->setTopicId(2);
  87. $root2->save();
  88. $p1 = new ForumPost();
  89. $p1->insertAsFirstChildOf($root1);
  90. $p1->save();
  91. $p2 = new ForumPost();
  92. $p2->insertAsFirstChildOf($root2);
  93. $p2->save();
  94. /*
  95. * Resulting trees :
  96. *
  97. * ROOT1
  98. * |- P1
  99. *
  100. * ROOT2
  101. * |- P2
  102. */
  103. }}}
  104. === Lame threaded forum posts list example ===
  105. {{{
  106. #!php
  107. <?php $root = ForumPostPeer::retrieveByPk(rootnodepk); ?>
  108. <ul>
  109. <?php echo $root->getTitle() ?>
  110. <?php foreach ($root->getDescendants() as $post): ?>
  111. <li style="padding-left: <?php echo $post->getLevel() ?>em;">
  112. <?php echo $post->getTitle() ?>
  113. </li>
  114. <?php endforeach; ?>
  115. </ul>
  116. }}}
  117. === Using nested sets and sfPropelPager ===
  118. {{{
  119. #!php
  120. <?php
  121. // Decide which posts to fetch
  122. $c = new Criteria();
  123. $c->add(ForumPostPeer::TOPIC_ID, $topic_id);
  124. $c->addAscendingOrderByColumn(ForumPostPeer::TREE_LEFT); // ForumPostPeer::TREE_LEFT is the column holding nested set's left value
  125. // Create pager
  126. $pager = new sfPropelPager('ForumPost', 10);
  127. $pager->setCriteria($c);
  128. $pager->setPage($this->getRequestParameter('page', 1));
  129. $pager->init();
  130. }}}
  131. == Public API ==
  132. Enabling the behaviors adds the following method to the Propel objects :
  133. === Insertion methods ===
  134. * `void insertAsFirstChildOf(BaseObject $dest_node)` : Inserts node as first child of given node.
  135. * `void insertAsLastChildOf(BaseObject $dest_node)` : Inserts node as last child of given node.
  136. * `void insertAsNextSiblingOf(BaseObject $dest_node)` : Inserts node as next sibling of given node.
  137. * `void insertAsPrevSiblingOf(BaseObject $dest_node)` : Inserts node as previous sibling of given node.
  138. * `void insertAsParentOf(BaseObject $dest_node)` : Inserts node as parent of given node
  139. === Informational methods ===
  140. * `bool hasChildren()` : Returns true if given node as one or several children.
  141. * `bool isRoot()` : Returns true if given node is a root node.
  142. * `bool hasParent()` : Returns true if given node has a parent node.
  143. * `bool hasNextSibling()` : Returns true if given node has a next sibling.
  144. * `bool hasPrevSibling()` : Returns true if given node has a previous sibling.
  145. * `bool isLeaf()` : Returns true if given node does not have children.
  146. * `bool isChildOf(BaseObject $node)` : Returns true if given node is parent of node.
  147. * `bool isDescendantOf(BaseObject $node)` : Returns true if given node is descendant of node.
  148. * `integer getNumberOfChildren()` : Returns given node number of direct children.
  149. * `integer getNumberOfDescendants()` : Returns given node number of descendants (n level).
  150. * `integer getLevel()` : Returns given node level.
  151. === Node retrieval methods ===
  152. * `BaseObject|null getParent($peer_method = 'retrieveByPk')` : Returns node parent or null if node does not have a parent.
  153. * `array getChildren($peer_method = 'doSelect')` : Returns given node direct children.
  154. * `array getDescendants($peer_method = 'doSelect')` : Returns given node descendants (n level).
  155. * `BaseObject retrieveNextSibling()` : Returns given node next sibling.
  156. * `BaseObject retrievePrevSibling()` : Returns given node previous sibling.
  157. * `BaseObject retrieveFirstChild()` : Returns given node first child.
  158. * `BaseObject retrieveLastChild()` : Returns given node last child.
  159. * `BaseObject retrieveParent($peer_method = 'doSelectOne')` : Returns given node parent.
  160. * `array retrieveSiblings()` : Returns node siblings.
  161. * `array getPath($peer_method = 'doSelectOne')` : Returns path to a specific node as an array, useful to create breadcrumbs.
  162. === Tree modification methods ===
  163. * `void moveToFirstChildOf(BaseObject $dest_node)` : Moves node to first child of given node.
  164. * `void moveToLastChildOf(BaseObject $dest_node)` : Moves node to last child of given node.
  165. * `void moveToNextSiblingOf(BaseObject $dest_node)` : Moves node to next sibling of given node.
  166. * `void moveToPrevSiblingOf(BaseObject $dest_node)` : Moves node to previous sibling of given node.
  167. * `void deleteChildren()` : Deletes node direct children
  168. * `void deleteDescendants($peer_method = 'doSelect')` : Deletes node descendants (n level)
  169. === Helper methods ===
  170. * `void makeRoot()` : Sets node properties to make it a root node.
  171. * `BaseObject reload()` : Returns an up to date version of node
  172. * `bool isEqualTo(BaseObject $node)` : Returns true if given node is equivalent to node.
  173. == Roadmap ==
  174. === 0.10.0 ===
  175. ==== Features ====
  176. * add support for symfony's i18n capabilities
  177. * add criteria option to more methods
  178. * add `$peer_method` as an optional parameter to `getParent()` and `getPath()`
  179. * add multiple connections support
  180. * add transactional support
  181. * get rid of mysql dependency : rewrite queries "criteria-like" or implement adapter.
  182. * add a method to copy a whole tree to another scope
  183. * make it possible to delete a root node that has children
  184. == Changelog ==
  185. === 2008-06-07 | Trunk ===
  186. * feature: Added a backward compatible Criteria parameter in `getChildren()`, `getDescendants()`, `retrieveSiblings()` and `getPath()` (fzaninotto)
  187. * feature: Added a backward compatible `$peer_method` in `deleteDescendants()` method (works the same way
  188. as ->getChildren() and ->getDescendants() $peer_method parameter)
  189. * feature: Allow user to change behaviour name in app.yml (Frederic Coelho)
  190. === 2007-07-30 | 0.9.1-beta2 ===
  191. * bugfix: Implemented more reliable `deleteDescendants()` method (Alexander Alexandrov)
  192. === 2007-07-23 | 0.9.1-beta ===
  193. ==== Bugfixes ====
  194. * fixed `getLevel()` cache (gordon franke)
  195. * fixed scope handling : scope can be any type of data (Jorn.Wagner)
  196. * `retrieveFirstChild()` and `retrieveLastChild()` missing references to scope node (Olivier.Mansour)
  197. * fixed postgresql compatibility (Maciej.Filipiak & Krasimir.Angelov)
  198. * added a note about supported RDBMS (tristan)
  199. * made roadmap clearer (tristan)
  200. * removed useless Propel::getConnection (Eric.Fredj)
  201. * fixed scope handling in `deleteDescendants()` (Piers.Warmers)
  202. * fixed new `getDescendants()` implementation node level caching (tristan)
  203. ==== Enhancements ====
  204. * added new `isDescendantOf()` method (Piers.Warmers)
  205. * implemented faster getPath() method (francois)
  206. * implemented faster `getDescendants()` (Jon.Collins)
  207. === 2007-05-24 | 0.9.0-beta ===
  208. * Licence change : MIT -> LGPL
  209. * Please welcome a new maintainer : Gordan Franke :)
  210. * tree "dumper" utility method : `sfPropelActAsNestedSetBehaviorUtils::dumpTree()`
  211. * add optional select method for getPath|getParent|retrieveParent (gordon)
  212. === 2007-04-18 | 0.8.2-beta ===
  213. * added `getParent()` method (olivier mansour)
  214. * added `getLevel()` unit tests
  215. * implemented caching of level in collection retrieval methods : `getDescendants()`, `getChildren()`, `retrieveSiblings()`
  216. * defined plugin roadmap
  217. === 2007-03-22 | 0.8.1-beta ===
  218. * fixed #1480 : non-abstracted column name (paul markovitch)
  219. * fixed bug in `getStubFromPeer()`
  220. * `makeRoot()` should accept non new objects (peter van garderen)
  221. * `getDescendants()` should not try to get descendants if node is a leaf (peter van garderen)
  222. * updated unit tests
  223. * enabled syntax highlighting in README
  224. === 2007-02-19 | 0.8.0-beta ===
  225. Implemented more methods (+ unit tests) :
  226. * `insertAsParentOf`
  227. * `retrieveSiblings`
  228. * `isEqualTo`
  229. * `isChildOf`
  230. Enhanced internal API
  231. === 2007-02-19 | 0.7.0-beta ===
  232. Implemented missing methods (+ unit tests) :
  233. * `moveToPrevSiblingOf`
  234. * `moveToNextSiblingOf`
  235. * `deleteChildren`
  236. * `deleteDescendants`
  237. === 2007-02-19 | 0.6.2-beta ===
  238. Fixed a bug due to wrong usage of `rtrim`. (Thanks to Krešo Kunjas)
  239. === 2007-02-15 | 0.6.1-beta ===
  240. Fixed minor bug in `getPath()`
  241. === 2007-02-15 | 0.6.0-beta ===
  242. Implemented missing node retrieval methods :
  243. * `retrieveFirstChild`
  244. * `retrieveLastChild`
  245. * `retrieveParent`
  246. * `getPath`
  247. Updated docs and unit tests accordingly
  248. === 2007-02-14 | 0.5.1-beta ===
  249. Pear package missed plugin's config.php file.
  250. === 2007-02-14 | 0.5.0-beta ===
  251. Initial public release. The behavior is stable and fully unit-tested, but the API is not yet complete. Missing methods :
  252. * `retrieveFirstChild`
  253. * `retrieveLastChild`
  254. * `moveToPrevSiblingOf`
  255. * `moveToNextSiblingOf`
  256. * `deleteChildren`
  257. * `deleteTree`
  258. * `getPath`
  259. == Maintainers ==
  260. Tristan Rivoallan and Gordon Franke.
  261. == Contributors ==
  262. Alexander Alexandrov, Krasimir Angelov, Jon Collins, Maciej Filipiak, Eric Fredj, Peter van Garderen, Olivier Mansour, Paul Markovitch,
  263. Krešo Kunjas, Piers Warmers, Francois Zaninotto, Romain Dorgueil.
  264. Plugin's code is based on work from [http://propel.phpdb.org/trac/ticket/312 Heltem]
  265. and [http://www.symfony-project.com/forum/index.php/m/20657/ Joe Simms].
  266. Thanks to all of you !