When displaying a form, you often want the user to make a choice amongst a list of possibilities.
In HTML, a choice is represented by a select tag:

You can add a multiple attribute to make it accept several choices:

The sfWidgetFormChoice
But a choice can also be represented by a list of radio button (single choice) or a list of checkboxes (multiple choices).
To unify all these possibilities, symfony 1.2 comes with a new widget called sfWidgetFormChoice.
sfWidgetFormChoice is an abstract widget in the sense that it delegates the rendering to another
widget (the renderer widget).
Let's take a simple example to illustrate all the possible combinations. In a project, we have the following schema:

// config/schema.yml
propel:
demo_article:
id: ~
author_id: { type: integer, foreignReference: id, foreignTable: demo_author, onDelete: cascade, onUpdate: cascade, required: true }
status: varchar(255)
title: varchar(255)
content: longvarchar
published_at: timestamp
demo_category:
id: ~
name: varchar(255)
demo_author:
id: ~
name: varchar(255)
demo_tag:
id: ~
name: varchar(255)
demo_tag_article:
tag_id: { type: integer, primaryKey: true, foreignReference: id, foreignTable: demo_tag, onDelete: cascade, onUpdate: cascade, required: true }
article_id: { type: integer, primaryKey: true, foreignReference: id, foreignTable: demo_article, onDelete: cascade, onUpdate: cascade, required: true }
demo_category_article:
category_id: { type: integer, primaryKey: true, foreignReference: id, foreignTable: demo_category, onDelete: cascade, onUpdate: cascade, required: true }
article_id: { type: integer, primaryKey: true, foreignReference: id, foreignTable: demo_article, onDelete: cascade, onUpdate: cascade, required: true }
This is a classic schema for a simple CMS. Articles have an author and can have many tags and categories.
Each article has also a status which value can be one of: published, draft, or deleted. The status value is stored
as plain text as no table has been created to store the statuses.
Let's play with the DemoArticle model by creating a module that provides the basic CRUD operations:
$ php symfony propel:build-all
$ php symfony propel:generate-module frontend article DemoArticle
If you navigate to the edit page, you will see something like this:

If you have a look at the generated form class for the DemoArticle model (lib/form/base/BaseDemoArticle.class.php),
you will see that symfony uses sfWidgetFormPropelChoice for the author_id widget and sfWidgetFormPropelChoiceMany
for the demo_category_article_list and demo_tag_article_list widgets. symfony has guessed the best
widget to use based on the schema definition.
sfWidgetFormPropelChoice represents a single choice widget based on a Propel object and
sfWidgetFormPropelChoiceMany represents a multiple choice widget also based on a Propel object.
Customizing the Form
The first thing we can do to customize our form is to convert the status widget to a choice:

First, we need to define the statuses in the DemoArticlePeer model class:
// lib/model/DemoArticlePeer.php class DemoArticlePeer extends BaseDemoArticlePeer { static protected $choices = array( 'published' => 'published', 'draft' => 'draft', 'deleted' => 'deleted' ); static public function getStatusChoices() { return self::$choices; } }
Then, edit the DemoArticleForm class to change the widget and the validator associated with the status
field:
// lib/form/DemoArticleForm.class.php class DemoArticleForm extends BaseDemoArticleForm { public function configure() { $this->widgetSchema['status'] = new sfWidgetFormChoice(array( 'choices' => DemoArticlePeer::getStatusChoices() )); $this->validatorSchema['status'] = new sfValidatorChoice(array( 'choices' => array_keys(DemoArticlePeer::getStatusChoices()) )); } }
The sfWidgetFormChoice takes an array of choices to use in the select tag as the choices option.
The sfValidatorChoice also takes a choices option which is the valid
values for the status column (the keys of the DemoArticlePeer::getStatusChoices() array).
Playing with the Choices
Radio button list
Time to play a bit with the sfWidgetFormChoice widget!
As you can see on the previous screenshot, the status is now represented by
a select tag. But as the number of values for status is quite low, it would
have been better to display the statuses as a list of radio buttons:

That's quite easy to achieve. The sfWidgetFormChoice takes an expanded option that changes
the output from a select tag to a list of radio buttons:
$this->widgetSchema['status'] = new sfWidgetFormChoice(array( 'choices' => DemoArticlePeer::getStatusChoices(), 'expanded' => true, ));
Checkboxes list
The list of categories is also quite small, so it would be better to display them as a list of checkboxes:

The expanded option we have used for single choices can also be used for multiple choice widgets.
As the widget has been generated in the base form class and don't need to be changed,
we can just set the expanded option to true:
$this->widgetSchema['demo_category_article_list']->setOption('expanded', true);
Summary
The following table summarizes the different configuration of sfWidgetFormChoice and the renderer widget
used by symfony:
sfWidgetFormChoice |
expanded is false |
expanded is true |
|---|---|---|
multiple is false |
sfWidgetFormSelect |
sfWidgetFormSelectRadio |
multiple is true |
sfWidgetFormSelectMany |
sfWidgetFormSelectCheckbox |
The same table with some screenshots:
sfWidgetFormChoice |
expanded is false |
expanded is true |
|---|---|---|
multiple is false |
![]() |
![]() |
multiple is true |
![]() |
![]() |
Group your Choices
One of the less-known possibility of the select tag is the way you can group your choices
with the optgroup feature:

The sfWidgetFormChoice family widgets has built-in support for groups. You just need to
pass an array of arrays for the choices options:
$choices = array( 'Europe' => array('France' => 'France', 'Spain' => 'Spain', 'Italy' => 'Italy'), 'America' => array('USA' => 'USA', 'Canada' => 'Canada', 'Brazil' => 'Brazil'), ); $this->widgetSchema['country'] = new sfWidgetFormChoice(array('choices' => $choices));
You can of course expand it to a list of radio buttons:
$this->widgetSchema['country'] = new sfWidgetFormChoice(array( 'choices' => $choices, 'expanded' => true, ));

You can also customize the layout used by the renderer widget:
$this->widgetSchema['country'] = new sfWidgetFormChoice(array( 'choices' => $choices, 'expanded' => true, 'renderer_options' => array('template' => '<strong>%group%</strong> %options%'), ));

And yes, it also works with the multiple option:


More with JavaScript
That was easy enough. Let's add some JavaScript to the mix to explore more possibilities.
Double list
If our CMS is used extensively, we will have more and more tags, and it will become more and more difficult to spot the tags associated with the current article. For such situations, a double list widget is one of the best solution:

Until now, symfony has chosen the best widget to use based on some simple configuration (multiple and expanded).
But the sfWidgetFormChoice is not able to render our select tag widget as a double list out of the box.
Luckily enough, we know that sfWidgetFormChoice delegates the rendering to another widget.
Changing the rendering widget is as simple as modifying the renderer_class option.
If you install the sfFormExtraPlugin,
you will find a lot of interesting widgets and validators that are quite useful but did not make
it into the core because they have third-party dependencies.
The sfWidgetFormSelectDoubleList widget is one of them:
$this->widgetSchema['demo_tag_article_list']->setOption('renderer_class', 'sfWidgetFormSelectDoubleList');
If you refresh the page now, it won't work as the widget relies on some JavaScript to work correctly. The widget API documentation contains all you need to know to configure it properly:
// apps/frontend/modules/article/templates/_form.php <?php use_javascript('/sfFormExtraPlugin/js/double_list.js') ?> <form action="<?php echo url_for('@article_update') ?>" onsubmit="double_list_submit(this, 'double_list_select'); return true;"> <table> <?php echo $form ?> <!-- ... --> </table> </form>
Autocomplete
We haven't played with the author_id field yet. Let's imagine that we have a lot of authors for our CMS, really a lot.
It is not very easy to find something in a very long list of names in a drop-down select tag. So, let's convert this
to an autocomplete widget.



That's impressive, isn't it? To make it work, we will have to work a bit more than before.
sfFormExtraPlugin contains two autocomplete widgets based on the JQuery library:
sfWidgetFormJQueryAutocompleter: Can be used for any autocomplete tasksfWidgetFormPropelJQueryAutocompleter: Is optimized for Propel related autocompleter
In our situation, we will use the Propel based one:
// lib/form/DemoArticleForm.class.php $this->widgetSchema['author_id']->setOption('renderer_class', 'sfWidgetFormPropelJQueryAutocompleter'); $this->widgetSchema['author_id']->setOption('renderer_options', array( 'model' => 'DemoAuthor', 'url' => $this->getOption('url'), ));
We have passed some options to the widget by setting renderer_options. In these options, you may have
noticed the url is set to a url form option ($this->getOption('url')).
When you create a form instance, the first constructor argument is the default values,
and the second one is an array of options:
public function executeEdit($request) { // ... $this->form = new DemoArticleForm($article, array('url' => $this->getController()->genUrl('article/ajax'))); // ... }
We now need to create the article/ajax action. When the widget calls this action, it passes several
request parameters:
q: The string entered by the userlimit: The maximum number of items to return
Here is the code:
// apps/frontend/modules/article/actions/actions.class.php public function executeAjax($request) { $this->getResponse()->setContentType('application/json'); $authors = DemoAuthorPeer::retrieveForSelect($request->getParameter('q'), $request->getParameter('limit')); return $this->renderText(json_encode($authors)); } // lib/model/DemoAuthorPeer.php class DemoAuthorPeer extends BaseDemoAuthorPeer { static public function retrieveForSelect($q, $limit) { $criteria = new Criteria(); $criteria->add(DemoAuthorPeer::NAME, '%'.$q.'%', Criteria::LIKE); $criteria->addAscendingOrderByColumn(DemoAuthorPeer::NAME); $criteria->setLimit($limit); $authors = array(); foreach (DemoAuthorPeer::doSelect($criteria) as $author) { $authors[$author->getId()] = (string) $author; } return $authors; } }
Now, as for every JavaScript widget, we also need to add some files to the form template to make it work properly:
// apps/frontend/modules/article/templates/_form.php <?php use_javascript('/sfFormExtraPlugin/js/jquery.autocompleter.js') ?> <?php use_stylesheet('/sfFormExtraPlugin/css/jquery.autocompleter.css') ?> <!-- ... -->
We are done. We now have an autocomplete widget which is able to display the author names, and submit the author id to the form. And thanks to the validator, we are sure that only valid ids are submitted and saved to the database.
Final Form
Here is the final form which shows all the different way to ask the user for a choice:

That's a lot of flexibility for just one widget!




wow, i like it.
Fabien that awesome, I always felt many2many was a bit of an after thought in the docs of sf1.0, so thanks for choosing such a comprehensive example.
I like those ideas very much! Looking forward :)
Only one thing to say : Ouah ouah ouah !!!
Only one word: wow!
Is there already a release date for 1.2?
Why readme of http://www.symfony-project.org/plugins/sfFormExtraPlugin don't speak of those marvelous widget :/
Very nice features, which ease one more time the form creation process :)
Now this is interesting. Fantastic work.
Excelent job and how a nice documentation. One question: in the example you use the sfWidgetFormPropelJQueryAutocompleter class to render the double_list. You are using the words Propel(maybe i want to use doctrine) and Jquery(maybe i want to use other js framework). Wouldn't be better to abstract the name of the autocompleter just to allow the developer to use some other tools?? (sfWidgetFormPropelMootoolsAutocompleter, sfWidgetFormDoctrineExtJsAutocompleter).
Would it be possible to include this(and some others post too like the "refactoring story...") in the cookbook?? Because here in the blog you don't have a search bar and there are a lot of useful information that can help many of us.
Thanks for the dedication of writing this kind of articles.
@saganxis: The widget name includes Propel and JQuery because they rely on these third party libs. But, you are more than welcome to contribute other combinations. And then, if we can abstract those in a single widget, we will definitely do it.
I have added a lot of blog posts such as this one in the cookbook. This one will be in the form book, but need some rewritting before.
Great to hear that!!
Looks nice. Is there any new way to handle Many-to-many relations with extra fields in the relation table, especially in a form created by a generator. I have been using ajax to edit the relation and its extra fields in the edit footer of the record, but it would be nice to have it handled by the framework.
Great addition! any chance to see it as a plugin for SF 1.1?
great