Development

/plugins/sfPropelActAsNestedSetBehaviorPlugin/README

You must first sign up to be able to contribute.

root/plugins/sfPropelActAsNestedSetBehaviorPlugin/README

Revision 4700, 11.1 kB (checked in by tristan, 6 years ago)

sfPropelActAsNestedSetBehaviorPlugin :

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