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?

17 Comments so far

  1. Dustin Whittle on December 21st, 2007

    I think symfony should definitely allow a short yaml syntax!

  2. Christoph Hautzinger on December 21st, 2007

    I think this would be great to shorten development cycles!

  3. Gustav Malbrance on December 21st, 2007

    Of course this should be added into the core.

  4. Pedro Casado on December 21st, 2007

    Fantastic! =)

  5. Kreso Kunjas on December 21st, 2007

    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.

  6. paolovas on December 21st, 2007

    It should be on the core !!!

  7. NiKo on December 21st, 2007

    This seems so obvious… I can’t imagine this wouldn’t be included into the core by default…

  8. Elliott on December 24th, 2007

    Yes! Yes yes yes! This is much nicer.

  9. […] sfForms: The Missing Component […]

  10. Ryan on December 26th, 2007

    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!

  11. […] sfForms: The Missing Component […]

  12. An Vu on December 31st, 2007

    Symfony should supplement this into core as early as posible.

    this will be very useful for creating and validating form.

  13. Hartym on January 22nd, 2008

    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…

  14. zugec on March 28th, 2008

    This solution would be awesome in core, it would standardize generator.yml and forms in symfony…very powerful.

  15. Pastor on May 6th, 2008

    Yes! Smart fields and shortcuts is the best way to create quick and extend in future!

  16. jkjk on May 6th, 2008

    Yes! Smart fields and shortcuts is the best way to create quick and extend in future!

  17. malm on June 11th, 2008

    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’))),
    )));

Leave a reply