Ask your Symfony questions! Pay money and get answers fast! Comodo Trusted Site Seal
Official PayPal Seal

Nested set categories, related category items, sfDoctrineChoice Symfony


I'm using doctrine nested sets to manage a template category hierarchy, however I also need to get out the actual templates that are in those categories using the sfWidgetFormDoctrineChoice on the forms.

So, how would I go about creating a dropdown for the form that outputs the TemplateCategory and corresponding Templates, so that I can select templates to load? Ideally, template categories would not be selectable.

Here is what I want the resulting dropdown to look like:

-- Template 1
-- TemplateCategory1
---- Template 2
---- Template 3
-- Template Category2
---- Template Subcategory1
------ Template 5

id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(150) }
content: { type: clob(65532) }
template_category_id: { type: integer(4) }
TemplateCategory: { local: template_category_id, foreign: id}

hasManyRoots: true,
rootColumnName: root_id
id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(150) }
is_public: { type: boolean }

Answers (3)


Wojciech Sznapka answers:

You can do it with myWidgetFormSelect this way:
in form class:

$this->widgetSchema['template_id' = new sfWidgetFormChoice(array('choices' =>TemplateCategory::getChoices()));

you must declare myWidgetFormSelect class, to achieve disabled options for values below zero (represented by TemplateCategory):

class myWidgetFormSelect extends sfWidgetFormSelect
public function renderContentTag($tag, $content = null, $attributes = array())
if ($tag == 'option' && isset($attributes['value']) && (int)$attributes['value'] < 0) {
$attributes['disabled'] = 'disabled';
return parent::renderContentTag($tag, $content, $attributes);

in TemplateCategory class:

class TemplateCategory extends BaseTemplateCategory
public static function getChoices()
$query = Doctrine_Query::create()
->select('tc.*, t.*')
->from('TemplateCategory tc')
->leftJoin('tc.templates t');
$result = array();
$treeObject = Doctrine::getTable('TemplateCategory')->getTree();
$rootColumnName = $treeObject->getAttribute('rootColumnName');

foreach ($treeObject->fetchRoots() as $root) {
$options = array(
'root_id' => $root->$rootColumnName
foreach($treeObject->fetchTree($options) as $node) {
$result['-' . $node->id] = str_repeat('-', $node->level) . ' ' . $node->name;
foreach ($node->templates as $tpl) {
$result[$tpl->id] = str_repeat('-', $node->level + 1) . ' ' . $tpl->name;
return $result;

the result should be as in attachment.

$treeObject->setBaseQuery will decrease number of queries.

bmv82 comments:

Thank you. This works, however this is a large number of queries. Is there a way to do a custom join query to reduce the number of calls?

bmv82 comments:

Also, is there a way to disable the "categories" as non selectable, or set them as an <OPTGROUP LABEL="CATEGORY"></OPTGROUP>


Marcos Ibañez answers:

I would recommend you use sfWidgetFormChoice, passing an array with the elements you want diplayed.

Here you have two methods you can use for that:

This method should go in your TemplateCategoriesTable.class.php in /lib/model/doctrine:

public static function getTreeForSelect() {
$root = Doctrine::getTable('TemplateCategories')->getTree()->fetchRoot();
$array_tree = array();
$array_tree = $root->getArrayTree($array_tree);
return $array_tree;

And you should also add this method to your TemplateCategories.class.php in /lib/model/doctrine:

public function getArrayTree($array_tree) {
switch($this->getLevel()) {
case 0:
$array_tree[$this->getId()] = $this->getName();
$array_tree[$this->getId()] = str_repeat('-', $this->getLevel()) . $this->getName();

if(!$this->getNode()->isLeaf()) {
foreach($this->getNode()->getDescendants() as $child) {
$array_tree = $child->getArrayTree($array_tree);
return $array_tree;

The only catch I see in this approach is that if your tree is too big, the proccess of building the array to fill the combos will take some time. You can try to use some caching mecanism for that like the sfAPCCache class.

Hope this works for you.


Karol Sójko answers:

in lib/model/doctrine/Template.class.php write an method:

public function getIndentedName()
$templateCategory = $this->getTemplateCategory();
return str_repeat('- ', $templateCategory->getLevel()) . $this['name'] . '(' . $templateCategory->getName() . ')';

then in the configuration of your form:

$this->widgetSchema['template_id'] = new sfWidgetFormDoctrineChoice(array(
'model' => 'Template',
'query' => Doctrine::getTable('Template')->getTreeQuery(),
'add_empty' => 'empty',
'method' => 'getIndentedName'

and for the finale in your TemplateTable.class.php

public function getTreeQuery()
$query = $this->createQuery('t')
->leftJoin('t.TemplateCategory tc')

return $query;

I think something like this should be close to what you expect