How do you validate/save an embedded form based on a value in the parent form in symfony?
I have a symfony 1.4 form with 2 embedded forms.
The parent form has a drop down which determines which of the embedded forms you fill in (hidden/shown on the frontend using JavaScript).
The problem is, when I submit the form, the validation and save is being run on both of the embedded forms, which I obviously don't want.
What is the best way to alter the parent form so that it only validates and saves the relevant开发者_高级运维 embedded form based on the selection made in the parent form?
Thanks.
Note: Please see jeremy's answer as mine is based on his.
Thank you for your answer jeremy. Your code had a few issues, so I thought I'd post my implemented solution explaining what I did differently.
1. Override doBind()
The override of doBind() had an issue where an uncaught sfValidatorError would be thrown if the parent value didn't return clean from the validator. I wrapped it in a try/catch to suppress this.
I also altered it to work with multiple embedded forms, not just the two I specified.
protected $selectedTemplate;
public function getTemplateToEmbeddedFormKeyMap()
{
// An array of template values to embedded forms
return array(
'template1' => 'templateform1',
'template2' => 'templateform2',
'template3' => 'templateform3',
'templateN' => 'templateformN'
);
}
protected function doBind(array $values)
{
// Clean the "template" value
try
{
$this->selectedTemplate = $this->validatorSchema['template']->clean(array_key_exists('template', $values) ? $values['template'] : NULL);
}
catch(sfValidatorError $e) {}
// For each template embedded form
foreach($this->getTemplateToEmbeddedFormKeyMap() as $template => $form_key)
{
// If there is no selected template or the embedded form is not for the selected template
if ($this->selectedTemplate == NULL || $this->selectedTemplate != $template)
{
// Don't validate it
$this->validatorSchema[$form_key] = new sfValidatorPass();
}
}
// Parent
parent::doBind($values);
}
2. NEW STEP Override updateObjectEmbeddedForms()
Because I've disabled validation on some or all of my embedded forms, we now have some uncleaned data in the $values array. I don't want this data being passed to my model objects within the embedded forms, so I've overridden updateObjectEmbeddedForms()
to remove any data related to an embedded form that isn't validated.
public function updateObjectEmbeddedForms($values, $forms = null)
{
// For each template embedded form
foreach($this->getTemplateToEmbeddedFormKeyMap() as $template => $form_key)
{
// If there is no selected template or the embedded form is not for the selected template
if ($this->selectedTemplate == NULL || $this->selectedTemplate != $template)
{
// Remove the data
unset($values[$form_key]);
}
}
// Parent
parent::updateObjectEmbeddedForms($values, $forms);
}
3. Override saveEmbeddedForms()
And finally, I didn't like I had to copy and paste the entire base saveEmbeddedForms()
method and then alter it, so I refactored it to remove the embedded forms I don't want to save before passing them to the parent.
public function saveEmbeddedForms($con = null, $forms = null)
{
// Get the embedded forms
if ($forms === NULL)
{
$forms = $this->getEmbeddedForms();
}
// For each template embedded form
foreach($this->getTemplateToEmbeddedFormKeyMap() as $template => $form_key)
{
// If there is no selected template or the embedded form is not for the selected template
if ($this->selectedTemplate == NULL || $this->selectedTemplate != $template)
{
// Remove the form so it isn't saved
unset($forms[$form_key]);
}
}
// Parent
parent::saveEmbeddedForms($con, $forms);
}
Thanks again for the answer jeremy, it got me to this which works for my use case.
Below is a generalized way of doing this. All of these methods can be added to BaseFormDoctrine
except doBind
1. Add method to skip forms.
/**
* @param string $name Adds $name to an array of form names to ignore when saving/updating.
*/
protected function skipSavingForm($name)
{
$this->skipSavingForms[$name] = $name;
$this->validatorSchema[$name] = new sfValidatorPass();
}
2. Override doBind so that the form you aren't saving isn't validated
This way even if the form that is not being saved is submitted with errors, the form still validates. This is safe since these values are not being saved. Alternatively, you could clear the values with a preValidator. However, I prefer this solution so that if the user submits the form with an error, his values for both forms are still there.
/**
* Override doBind to skip validation on the form not being saved
* @param array $values
* @see sfForm::doBind
*/
protected function doBind(array $values)
{
try
{
$formDecidingValue = $this->validatorSchema[$values['form_deciding_field']]->clean();
} catch (sfValidatorError $e) {
//either create an sfValidatorErrorSchema and throw it or call through to parent here and let parent::doBind throw the error
return; //either way, we want to stop processing
}
$this->skipSavingForm($formDecidingValue ? 'Form1' : 'Form2');
return parent::doBind($values);
}
3. Add getFormsToSave method
/**
*@return array An array of forms to be saved
*/
public function getFormsToSave()
{
return array_diff_key($this->getEmbeddedForms(), $this->skipSavingForms);
}
4. Override saveEmbeddedForms and updateObjectEmbeddedForms
So that the skipped form is not saved. Alternatively, if you don't need to continue to display the form on an error, you can simply unset the embeddedForm in doBind.
public function saveEmbeddedForms($con = null, $forms = null)
{
if (null === $con)
{
$con = $this->getConnection();
}
if (null === $forms)
{
$forms = $this->getFormsToSave();
}
foreach ($forms as $form)
{
if ($form instanceof sfFormObject)
{
$form->saveEmbeddedForms($con);
$form->getObject()->save($con);
}
else
{
$this->saveEmbeddedForms($con, $form->getFormsToSave());
}
}
}
public function updateObjectEmbeddedForms($values, $forms = null)
{
if (null === $forms)
{
$forms = $this->getFormsToSave();
}
foreach ($forms as $name => $form)
{
if (!isset($values[$name]) || !is_array($values[$name]))
{
continue;
}
if ($form instanceof sfFormObject)
{
$form->updateObject($values[$name]);
}
else
{
$this->updateObjectEmbeddedForms($values[$name], $form->getFormsToSave());
}
}
}
精彩评论