I thought some of you symfony developers out there might appreciate the following form validators. They have been incredibly convenient, and were incredibly simple to write. I hope they come in handy:

  1. sfValidatorPhone: Validates a phone number using regex
  2. sfValidatorZip: Validates a zip code using regex
  3. sfValidatorUrl: Validates a url using regex
  4. sfValidatorCCExpirationDate (1.2, 1.4): validates the month/year on a credit card. Goes well with sfWidgetFormCCExpirationDate.
  5. sfValidatorCreditCardNumber: validates a credit card number with an optional card type parameter.
  6. sfValidatorCoupon: Used in payment forms to validate a coupon

What other useful validators should I include?

***** UPDATE *****

Symfony versions 1.3 and 1.4 broke backwards compatibility with some of these validators. I’ve added a link to each symfony version to fix this.

I just posted to the Centresource code blog about form testing in Symfony. Check it out:

Testing forms in Symfony can be a pain. The form framework form tester is an awesome tool, but it is a pain to fill out each form field for every form test you want to run. It is tedius, looks messy, and can be hard to maintain. I wrote a helper class to put filling in of unimportant form fields out of site and out of mind

Read the rest here.

Symfony makes good use of the flat-string array syntax, a sudo-serialization technique.  This is great for objects that may require different options based on other column values (such as a “type” column). The synax works like this:

this=that

key=value

something=Something Else

This flat string is then parsed into an associative array with the sfToolkit::stringToArray() method.  Using this, we can translate these values into the form framework by creating an OptionsForm class.  The class will parse the values into individual fields, and provide widgets for the users to edit.  When bound, the form will collapse these values back into a flat string.

The first step is to create an option form that extends the sfForm class:

  class sfOptionsForm extends sfForm

Next, allow the options string (or array) to be passed in via the constructor:

  public function __construct($defaults)
  {
    // convert options string to array
    if(!is_array($defaults))
    {
      $defaults = sfToolkit::stringToArray($defaults);
    }
    $this->option_defaults = $defaults;
    return parent::__construct();
  }

Then, override the setup method to create basic widgets and validators.

  public function setup()
  {
    foreach ($this->option_defaults as $field => $default)
    {
      $this->widgetSchema[$field] = new sfWidgetFormInput();
      $this->validatorSchema[$field] = new sfValidatorString(array('required' => false));
    }
    $this->setDefaults($this->option_defaults);
  }

That’s it! I would recommend extending this form in order to customize your widgets, but this is enough to get you started. Now, you need to add some methods to the form in order for the binding to be successful. In this example, we are using sfOptionsForm for an object field called “options”. In our parent form, we need to set this same field to our new form.

  $this->embedForm('options', new sfOptionsForm($this->getObject()->getOptions());

The trickiest part of this whole process occurs when the form is bound. In order for this to function properly, we need to override our parent form’s bind() method.

  public function bind(array $taintedValues = null, array $taintedFiles = null)
  {
     // do special binding for options if options exist
     if (array_key_exists('options', $taintedValues) && array_key_exists('options', $this->embeddedForms))
     {
       // Flattens the option form into a string
       $taintedValues['options'] = $this->embeddedForms['options']->getFlattenedValues($taintedValues['options']);

       // unset imbedded form, replace field with a string widget / validator
       unset($this['options']);
       $this->widgetSchema['options'] = new sfWidgetFormInput();
       $this->validatorSchema['options'] = new sfValidatorString();
     }
     $ret = parent::bind($taintedValues, $taintedFiles);
     return $ret;
  }

There are a few snags with this method. One occurs when a form does not validate on save. Since we unset the embedded form, we have to re-embed the form if the form is invalid. This can be rectified by adding code after the parent::bind() method in our code above.

  public function bind(array $taintedValues = null, array $taintedFiles = null)
  {
    // ...
    $ret = parent::bind($taintedValues, $taintedFiles);
    {
       $this->isBound = false;  // form must be unbound in order to embed a form
       $this->embedForm('options', new sfOptionsForm($this->getObject()->getOptions());  // re-embed the form
    }
    return $ret;
  }

And there you have it! You now have a dynamic form-building interface. In order for your dynamic forms to be ironclad, there are still a few hidden snags you should be aware of. First, you need to make sure default options always get passed to the sfOptionsForm class. If an empty string is passed, the embedded form will be empty. Secondly, you need to take into consideration what happens when new fields are added, or the type of your object is switched. I usually implement a check to ensure the proper options are being passed for the dynamic form type.

To see these forms in action, install the csDoctrineSlideshowPlugin, or view the code straight from the repo.

If you are wondering if I develop anything other than Symfony plugins,  in the past week I have worked on projects in Rails, Drupal, Wordpress, and Cocoa/Objective C. I hope to post more on these later, as they become accessible in some form or another.

Your comments and support would be much appreciated, however, on the three plugins I have added/heavily updated in the past two weeks. They are the following:

  1. csNavigationPlugin – Quite possibly my best plugin yet (and coincidentally that with the least users). I have worked on this for months, and recently re-factored it into a production-ready state.
  2. csSettingsPlugin – Initially a port of Chris Wage’s sfDoctrineSettingsPlugin, this plugin is now almost entirely refactored. Supports great settings-integration in your project.
  3. csDoctrineSlideshowPlugin – This plugin has been heavily refactored in order to allow for the use of multiple slideshow libraries. Currently supporting JQuery Cycle and Google Slideshow2, the csDoctrineSlideshowPlugin can easily toggle between any supported library.

In exploring the plugins offered for Rails, Drupal, and Wordpress, I am able to obtain a bearing on how these plugins compare. The plugins offered for Symfony are incredibly far behind the curve, and it would be an honor for me to help change this. I can’t do that without feedback, however. Thanks for your support.

Over the last week, I have developed several plugins for CentreSource and the symfony community. The plugins have been used in several of our internal projects and client web applications, and are finally deemed worthy for public use. These plugins can be found here.

  1. csDoctrineActAsAttachablePlugin – associates various uploads with multiple models, and includes an AJAX uploading client interface.
  2. csDoctrineActAsCategorizablePlugin – associates models into nestable categories and category groups.
  3. csDoctrineActAsGeolocatablePlugin – integrate your model with the Google Maps API to pull in geocodes based on record fields. Supports radius and proximity searches.
  4. csDoctrineSlideshowPlugin – add and configure slideshows in your project.
  5. csFormTransformPlugin – give your forms a web 2.0 look within a few easy steps.
  6. csGlossaryPlugin – group your models alphabetically in glossary/directory format
  7. csSEOToolkitPlugin – A toolkit to improve your website’s search engine optimization.
  8. sfSympalSlideshowPlugin – An advanced slideshow used for the Sympal Content Management Framework. (Ported by Jonathan Wage)

You can check out my plugin list here. If you are interested in using these plugins, or have any suggestions for addition useful symfony plugins, please contact me or any of the developers at CentreSource.