A while ago, I wrote a post entitled “Faux Form Serialization“. It was an overly-complex solution to a very easy problem. Today, I am writing about a much better implementation for solving the same problem.
What is this concept? You’ve heard of MongoDB right? CouchDB? BeullerDB…? Of course you have. You’ve been tired of writing migrations to add and remove columns that are constantly changing, and at some point have had the thought
“By golly, I should just slap an options field on this honkey!”
You’re exactly right, and this is what you should do.
Doctrine already has a lovely array field type. Similarly, it serializes an array into a longtext database column and unserializes upon hydration. Well that sounds useful!
Exposing these fields can be tricky though. We have this lovely form framework with all its pretty widgets and validators, and the ability to embed them! Yet, since this isn’t a true related object, we can’t do this out of the box… or can we?!
Example Time
Your sfGuardUser object has a laundry list of silly auxiliary fields, including items such as “how often do you zumba?” and “what animal produces your favorite milk?”. You refuse to write migrations for these things. Instead, you’ll save them all to a single field of type array called extra_profile_fields. Good for you! Create a form:
// lib/form/sfGuardUserExtraProfileFieldsForm.class.php
class sfGuardUserExtraProfileFieldsForm extends BaseForm
{
public function configure()
{
$this->setWidgets(array(
'zumba_frequency' => new sfWidgetFormChoice(array('choices' => array('Not at all', 'Sometimes', 'Every Single Day', 'I NEVER STOP'))),
'favorite_animal_milk' => new sfWidgetFormInput(),
));
$this->setValidators(array(
'zumba_frequency' => new sfValidatorChoice(...),
'favorite_animal_milk' => new sfValidatorString(...),
));
}
}
Now, use my very special sfWidgetFormEmbeddedForm and sfValidatorEmbeddedForm
// lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends PluginsfGuardUserForm
{
public function configure()
{
....
$form = new sfGuardUserExtraProfileFieldsForm();
$this->widgetSchema['extra_profile_fields'] = new sfWidgetFormEmbeddedForm(array('form' => $form));
$this->validatorSchema['extra_profile_fields'] = new sfValidatorEmbeddedForm(array('form' => $form));
}
Wow, is it that easy? YES IT IS. The form will render expectedly, with validators and all. However, make sure you pass the same form object to both the widget and the validator! If you, for instance, pass a new sfGuardUserExtraProfileFieldsForm instance to each, you’ll have two separate forms, and errors in your form will not be rendered.
Extra Fun: Using Archivers
Let’s say you wanted the options to save as YAML, JSON, or a PHP-serialized string. As long as you use the “array” column type in doctrine, none of this is necessary. But if you need this flexibility, I’ve got your back.
The ArchiverInterface specifies a class with three methods: sleep, wake, and isAsleep. Essentially, it will convert from a certain array to a format and back again. Here are some sample archivers for your pleasure alone. Pass the class name as the “archiver” option to widget and validator, and you’re all set!
Hopefully you guys will enjoy this and use this as much as I have. And please don’t hesitate to submit comments, suggestions, and questions.
- All the classes in this blog post can be found on github.
RSS Feed