sfForms: The Missing Component
Symfony 1.1 introduces a new Form subframework, combining Widgets and Validators to generate poweful form objects. Thatsquality.com explains how to use the new objects in a series of tutorial articles.
Symfony has always been about providing powerful and easy-to-use components to implement the best practices of web development and avoid long and repeating code. But there is a problem with the new subframework: it is incredibly powerful, but also incredibly not easy to use.
The Base Example
I won’t explain the whole thing here, but this is the main part: to define both the components and validation rules of an sfForm object, you must use the setWidgetSchema() and setValidatorSchema() methods of an sfForm extending object, in a configure() method.
Here is an example for a contact form:
class ContactForm extends sfForm
{
public function configure()
{
$this->setWidgetSchema(new sfWidgetFormSchema(array(
'email' => new sfWidgetFormInput(),
'name' => new sfWidgetFormInput(),
'body' => new sfWidgetFormTextarea(),
'captcha' => new sfWidgetFormReCaptcha(array('public_key' => sfConfig::get('app_recaptcha_public_key'))),
)));
$this->setValidatorSchema(new sfValidatorSchema(array(
'email' => new sfValidatorString(),
'name' => new sfValidatorAll(array(new sfValidatorString(array('required' => false), new sfValidatorEmail())), # not sure about the syntax there
'body' => new sfValidatorString(),
'captcha' => new sfValidatorReCaptcha(array('private_key' => sfConfig::get('app_recaptcha_private_key'))),
)));
$this->widgetSchema->setNameFormat('contact[%s]');
}
}
I won’t discuss the power of the system, it’s incredible and allows you for almost everything in no time.
Enters YAML
In no time? But what if the sfForm object had a configureFromArray() method accepting an associative array - or, in other terms, a YAML configuration? The same as above could be achieved with this YAML code:
contact: # This key is not necessary. Just an entry point for the hash
name_format: contact[%s]
fields:
email:
widget: { class: sfWidgetFormInput }
format: # 'format' is just so much faster to write than 'validator'
- { class: sfValidatorEmail }
- { class: sfValidatorString } # This is how I combine two validators with an AND
required: true
name:
widget: { type: input } # Easy to guess class from type, which is more familiar and faster to write
format: { type: string } # If only one validator, no need for an array
required: false
body:
widget: { type: textarea }
format: { type: string }
required: true
captcha:
widget: { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY% } # All keys except type and class are passed to the constructor
format: { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY% }
required: true
That’s longer than the PHP code, will you say. For now.
Convention over configuration
No need to be so verbose, symfony can have conventions for the 80% most common cases and do part of the job for you. So the configuration could reduce to:
contact:
name_format: contact[%s]
fields:
email:
widget: input # if only type, no need to use hash
format: [email, string]
# required: true # required is true by default
name:
# widget: input # widget is of type input by default
# format: string # format is of type string by default
required: false
body:
widget: textarea
# format: string # if widget is defined, format has sensible defaults (ex: input/textarea => string, date => date, etc )
# required: true
captcha:
widget: { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY% }
format: { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY% }
# required: true
Cleaning up
Once all the implied settings removed, the configuration could be shorter:
contact:
name_format: contact[%s]
fields:
email:
format: [email, string]
name:
required: false
body:
widget: textarea
captcha:
widget: { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY%, autoformat: true }
# if autoformat is true in the widget, then format takes the same params as widget (minus, of course, autoformat)
Or, if we use the inline YAML syntax:
contact:
name_format: contact[%s]
fields:
email: { format: [email, string] } # Wait a minute. Couldn't symfony guess this one for me ?
name: { required: false }
body: { widget: textarea } # And what about this one ?
captcha:
widget: { type: reCaptcha, private_key: %APP_RECAPTCHA_PRIVATE_KEY%, autoformat: true } # And this one ?
One step further
If symfony were really smart, all you should write to define the widgets and validation rules of a contact form would be:
contact:
name_format: contact[%s]
fields:
email: ~
name: { required: false }
body: ~
captcha: { private_key: %APP_RECAPTCHA_PRIVATE_KEY% }
THAT, my friend, is both powerful and very easy to use.
More use cases
The principle of YAML configuration works well on simple forms. But I believe it can be extended to most of the forms, leaving only 10% to 20% of the forms to be defined with setWidgetSchema() and setValidatorSchema(). Here are more examples:
my_form:
culture: fr
focus: message # Yep, that's for JavaScript focus()
fields:
id: ~ # let me guess: { widget: hidden, format: integer } ?
name: ~
email: ~
date_of_birth:
label: Date of birth # I know it belongs to the widget, but it's just more natural like that
widget: date
format: { type: date, min: 1900/01/01, max: 2005/31/12 }
message:
widget: textarea
format: string or number # Why not use the wonderful text validators here ?
want_to_be_spammed:
widget: { type: checkbox, default: true, tabindex: 3, class: foo } # How do you define default values with the widgets?
subject:
widget: { type: select, choices: [administrative, legal, commercial], default: administratif }
recipient:
widget: { type: selectMany, populator: [this, getPossibleRecipients] } # Populator expects a callable to populate choices
title:
type: input
format: not in Article::title # That's a text validator for the sfPropelUniqueValidator
What do you think? Should symfony implement this system by default?

I think symfony should definitely allow a short yaml syntax!
I think this would be great to shorten development cycles!
Of course this should be added into the core.
Fantastic! =)
Don’t know what are you waiting? all this should already be done
I think that it follows the symfony way ( remindes me of admin generator yaml files), and it will shorten deveopment time and its easier to write (and maintain) a yaml file than a php code.
It should be on the core !!!
This seems so obvious… I can’t imagine this wouldn’t be included into the core by default…
Yes! Yes yes yes! This is much nicer.
[…] sfForms: The Missing Component […]
Yes, your yaml looks very similar to existing yaml in Symfony. I think this might be a nice way to bridge people over to the new form system. Symfony could create and cache form classes from the above code - I like it!
[…] sfForms: The Missing Component […]
Symfony should supplement this into core as early as posible.
this will be very useful for creating and validating form.
Hum… I’ll may be the devil’s advocate but I’m wondering if this would not be only a “magic” layer that would just avoid people to dive into the code. For sure it’d be faster to write, but I fear people that only rely on magic when it comes to coding. Well anyway that could be a great enhancement but imho developpers should keep in mind that it’s only a shortcut, not a code replacement…
This solution would be awesome in core, it would standardize generator.yml and forms in symfony…very powerful.
Yes! Smart fields and shortcuts is the best way to create quick and extend in future!
Yes! Smart fields and shortcuts is the best way to create quick and extend in future!
Hello!
$this->setValidatorSchema(new sfValidatorSchema(array(
‘email’ => new sfValidatorString(),
‘name’ => new sfValidatorAll(array(new sfValidatorString(array(’required’ => false), new sfValidatorEmail())), # not sure about the syntax there
‘body’ => new sfValidatorString(),
‘captcha’ => new sfValidatorReCaptcha(array(’private_key’ => sfConfig::get(’app_recaptcha_private_key’))),
)));
this example is bad…
corrected:
$this->setValidatorSchema(new sfValidatorSchema(array(
‘email’ => new sfValidatorString(),
‘name’ => new sfValidatorAll(array(new sfValidatorString(array(’required’ => false), new sfValidatorEmail()))),
‘body’ => new sfValidatorString(),
‘captcha’ => new sfValidatorReCaptcha(array(’private_key’ => sfConfig::get(’app_recaptcha_private_key’))),
)));