Development

HowToHandleHierarchicalDataWithPropel

You must first sign up to be able to contribute.

Version 6 (modified by Grégory Becker, 11 years ago)
--

WARNING
This code may be moved to a pear package later. Let's just be sure there aren't bugs in it anymore first.
The following source code is NOT ready for production usages. Use it at your own risk.
Method names may change in the future and won't after the first stable release (I hope so).

sfPropelTree plugin

Create in file with the following path : /lib/plugins/sfPropelTree/sfPropelTree.class.php
Paste the content from http://www.igreg.info/sfPropelTree/sfPropelTree.class.php.txt
Syntax highlighted http://www.igreg.info/sfPropelTree/sfPropelTree.class.phps

Clear the cache for symfony to find this new plugin.

symfony cc

schema.xml

Now, add two columns to the table that will store the tree.
You cannot change the names of theses two columns.

<table ....>
    ...
    <column name="lft" type="integer" required="true" />
    <column name="rgt" type="integer" required="true" />
    ...
</table>

Then rebuild DB structure

symfony propel-build-sql
symfony propel-build-model

And don't forget to apply table modification to mysql.

MyTable?.class.php

Add this methods to your class. This will keep the class clean and will make shortcuts to the real functions in sfPropelTree.

<?php

class MyTable extends BaseMyTable {
  // sfPropelTree Functions. Do not modify unless you know what you are doing
  function save($con = null)      { sfPropelTree::saveNode($this); parent::save($con); }
  function delete($con = null)    { sfPropelTree::deleteNode($this); parent::delete($con); }
  function deleteChilds()         { return sfPropelTree::deleteChilds($this); }
  function addChild($child)       { return sfPropelTree::addChildBottom($this, $child); }
  function addChildTop($child)    { return sfPropelTree::addChildTop($this, $child); }
  function addChildBottom($child) { return sfPropelTree::addChildBottom($this, $child); }
  function addLeft($child)        { return sfPropelTree::addBefore($this, $child); }
  function addBefore($child)      { return sfPropelTree::addBefore($this, $child); }
  function addRight($child)       { return sfPropelTree::addAfter($this, $child); }
  function addAfter($child)       { return sfPropelTree::addAfter($this, $child); }
  function moveBefore($refNode)   { return sfPropelTree::moveBefore($refNode, $this); }
  function moveAfter($refNode)    { return sfPropelTree::moveAfter($refNode, $this); }
  function hasChilds()            { return sfPropelTree::hasChilds($this); }
  function getChilds()            { return sfPropelTree::getChilds($this); }
  function getFirstChild()        { return sfPropelTree::getFirstChild($this); }
  function getLastChild()         { return sfPropelTree::getLastChild($this); }
  function getParent()            { return sfPropelTree::getParent($this); }
  function getRoot()              { return sfPropelTree::getRoot($this); }
  function getPath()              { return sfPropelTree::getPath($this); }
  function getLeft()              { return sfPropelTree::getLeft($this); }
  function getRight()             { return sfPropelTree::getRight($this); }
}

?>

MyTablePeer?.class.php

Do the same, but for MyTablePeer?.class.php this time.

<?php

class MyTablePeer extends BaseMyTablePeer {
  // sfPropelTree Functions. Do not modify unless you know what you are doing
  static function getIdField ()   { return self::ID; }
  static function getLftField ()  { return self::LFT; }
  static function getRgtField ()  { return self::RGT; }
  static function getTable()      { return self::TABLE_NAME; }
  static function getRoot()       { return sfPropelTree::getRoot(__CLASS__); }
  static function print_r()       { return sfPropelTree::print_r(__CLASS__); } // debug function
}

?> 

Examples of use

In the following examples, I have setup a table named Page.
Each page can contains subpages.

propeltreedemo_schema.xml

<?xml version="1.0" encoding="UTF-8"?>
<database name="propel" defaultIdMethod="native" noxsd="true">
  <table name="page" phpName="Page">
    <column name="id" type="integer" required="true" primaryKey="true" autoincrement="true" />
    <column name="title" type="varchar" size="255" required="true" />
    <column name="lft" type="integer" required="true" />
    <column name="rgt" type="integer" required="true" />
  </table>
</database>

Tree structure for each example

  root page
    page 1
    page 2
    page 3
    page 5
    page 8

add a root node

$page = new Page();
$page->setTitle('Page 9');
$page->save();

results:

  root page
    page 1
    page 2
    page 3
    page 5
    page 8
    page 9 <

add a child to 'Page 1'

$c = new Criteria();
$c->add(PagePeer::TITLE, 'page 1');
$parent = PagePeer::doSelectOne($c);

$child = new Page();
$child->setTitle('Page 1.1');
$parent->addChild($child); // saves $child in database. No need to call $child->save() after
// addChild is an equivalent to addChildBottom
// you could have typed :
// $parent->addChildBottom($child)

results :

  root page
    page 1
        page 1.1 <
    page 2
    page 3
    page 5
    page 8

add a child before the other ones

We're using the result of the previous example for this one.

$c = new Criteria();
$c->add(PagePeer::TITLE, 'page 1');
$parent = PagePeer::doSelectOne($c);

$child = new Page();
$child->setTitle('Page 1.0');
$parent->addChildTop($child); // saves $child in database. No need to call $child->save() after

results:

  root page
    page 1
        page 1.0 <
        page 1.1
    page 2
    page 3
    page 5
    page 8

add a node after another one

$c = new Criteria();
$c->add(PagePeer::TITLE, 'page 3');
$parent = PagePeer::doSelectOne($c);

$child = new Page();
$child->setTitle('page 4');

if ($parent && $child)
  $parent->addAfter($child); // saves $child in database. No need to call $child->save() after

results :

  root page
    page 1
    page 2
    page 3
    page 4 <
    page 5
    page 8