Last week, I was working on a Symfony2 app where I wanted to generate optgroup tags inside the select box of an Entity form type. After poking around, I ran across a StackOverflow answer explaining how to do it. Basically, it turns out what you have to do is manually return a “choices” array from a class that has access to the Entity Manager. I ended up adding a method to my custom repository and passing that repository into my form:

<?php

// My custom repository class
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityRepository;

class CategoryRepository extends EntityRepository {

    public function getCategoryOptions($studyId){
        $ret = array();
        
        $categories = $this->getEntityManager()
             ->getRepository("CTBEBundle:Category")
             ->getQuery()
             ->getResult();
        
        foreach( $categories as $cat ){
            
            if( !$cat->getParentCategory() ){
                continue;
            }
            
            if(!array_key_exists($cat->getParentCategory()->getName(), $ret) ){
                $ret[ $cat->getParentCategory()->getName() ] = array();
            }
            
            $ret[ $cat->getParentCategory()->getName() ][ $cat->getId() ] = $cat;
            
        }
        
        return $ret;
    }

}
<?php

// My form type
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class SelectCategoryType extends AbstractType
{
    
    private $formOptions;
    
    public function __construct($options = array()){        
        $this->formOptions = $options;
    }
    
    public function buildForm(FormBuilderInterface $builder, array $options){
        $builder->add("category", "entity", array("empty_value" => "Please select...", "required" => false,
                      "class" => 'CTBEBundle:Category',
                      "choices" => $this->formOptions["em"]->getReceiptCategoryOptions(),        
        ));
    }

    public function getName(){
        return 'selectCategory';
    }

}


// And then in the controller, we're creating the types with:

$this->createForm(new ReceiptItemType(array("em" => $this->getDoctrine()->getRepository("CTBEBundle:Category")), $object);

It’s a bit messy and I’m surprised there isn’t an option on the Entity Type to pass in a callback with access to the Entity Manager to generate a choice list. Looking at the source of DoctrineType it looks like you could potentially create a custom type to extend the Entity type and then access the em from your custom function. Even that though, seems like overkill to accomplish something that is reasonably common.