CraueFormFlowBundle
CraueFormFlowBundle copied to clipboard
How to handle unique constraints properly
Hello,
I've got a case that i can't figure out for now, it seems a common use case:
- step 1 contains a field associated with a unique constraint in the db
- the user submit a value and goes to step 2 (last step)
- the user submit the form and the record is saved in the db
If the db throws a unique constraint error, i would like to add an error message on the field in step 1 and show it to the user so that he can change the value
It is similar to #82 but instead of a flash message i'd like to have a form error.
The only way i can see to do it is to set some session data, use the same redirection as in #82, and add the error to the form if i have a specific parameter set in the session
In an ideal world i'd like to be able to do something like:
$flow->goToStep(1);
$form = $flow->getForm();
$form->addError(...)
$this->render(...)
Any suggestion on how to achieve that ? Thank you
Instead of trying to catch the database error, you should add a proper validation constraint on the entity itself using the correct validation group.
Hi and thank you for the answer. I already have a validation constraint on the form but there can be concurency issue between the check of the validation and the insert itself so i need to find a way to handle this edge case. (this is also true when using a simple form without steps).
For example, see discussion in https://github.com/symfony/symfony/issues/20692
True. So the issue is how to catch the error and add it to the form nicely? You could actually just do exactly that:
$flow->bind(...);
$form = $flow->createForm();
try {
if ($flow->isValid($form)) {
throw new \RuntimeException('test');
}
} catch (\Exception $e) {
$form->addError(new \Symfony\Component\Form\FormError($e->getMessage()));
}
This would show the error in the current step, which is not what you want. But by default, the validation for step 1 is also triggered when submitting step 2, so at least if the unique constraint is already violated when submitting step 2, you should be fine. If it happens between validating the flow (after clicking finish) and persisting the data, you need to rely on catching the exception.
As we like to say: "This should not happen!" but this piece of the system is critical so we want optimize UX
I'm pretty much stuck at the same point. Correct me if i'm wrong:
- there is no way to just tell the flow "i want step X instead of what you're doing now"
- if i redirect the user to the step url (
?instance=wxxptx_E2c&step=1
), as there will be no POST data, no error will be shown
The only idea i got so far was catching the exception, adding the error to the session, redirect to the appropriate step, have the init of the flow somehow check the session to add the error back to the form. Not a good balance between hassle and likelihood :(
As the form is really simple i could afford to just reset everything and redirect to the first step with a flash message but that's kind of disapointing ^^
Do you see any other possibilities ? Do you think it would require many changes to handle this use case ? (again, very unlikely, but valid for every unique constraint bound to a form and enforced at the db level)
thanks for the help
@craue
True. So the issue is how to catch the error and add it to the form nicely? You could actually just do exactly that:
$flow->bind(...); $form = $flow->createForm(); try { if ($flow->isValid($form)) { throw new \RuntimeException('test'); } } catch (\Exception $e) { $form->addError(new \Symfony\Component\Form\FormError($e->getMessage())); }
This would show the error in the current step, which is not what you want. But by default, the validation for step 1 is also triggered when submitting step 2, so at least if the unique constraint is already violated when submitting step 2, you should be fine. If it happens between validating the flow (after clicking finish) and persisting the data, you need to rely on catching the exception.
I am doing something very similar here but seems validation still gets set to true.
// charge the CC
if( $flow->getLastStepNumber() == $flow->getCurrentStepNumber() ){
try {
$token = $flow->getRequest()->request->get('createOrderStepFive')['token'];
$stripe->createCharge(123.00, $token);
} catch (\Exception $e){
dump($e);
$form->addError(new FormError($e->getMessage()));
}
}
dump($flow->isValid($form));
@mcgoode, this doesn't work since the errors are cleared while calling
$flow->isValid($form)
.
@craue Do you have a suggested way to add form errors to inputs?