Invalid form submits fail silently when the controller redirects after addFlashFormErrors()
Describe the bug
PrestaShopAdminController::addFlashFormErrors() collects form errors without the deep flag:
// src/PrestaShopBundle/Controller/Admin/PrestaShopAdminController.php
protected function addFlashFormErrors(FormInterface $form): void
{
/** @var FormError $formError */
foreach ($form->getErrors() as $formError) {
$this->addFlash('error', $formError->getMessage());
}
}The deprecated FrameworkBundleAdminController::addFlashFormErrors() that it supersedes uses $form->getErrors(true). Without $deep, Symfony returns only the violations attached to the root form, so every error raised by a constraint declared on a field (the overwhelming majority) is left out. That part is intentional: a controller that re-renders the form displays those child errors inline, and flashing them too would duplicate them.
Every current caller also returns a RedirectResponse on the invalid branch, so the submitted form and its view are discarded too and inline field errors cannot render either. The net result for the merchant is a page that comes back with no success message, no error message, and the value not saved.
Call sites, all extending PrestaShopAdminController, and what each one returns on the invalid branch:
| File | Action | Invalid branch |
|---|---|---|
Improve/Design/ImageSettingsController.php:102 |
saveSettingsAction |
redirectToRoute('admin_image_settings_index') |
Improve/Design/MailThemeController.php:101,194 |
generateMailsAction, and the translation form |
redirectToRoute('admin_mail_theme_index') right after the call |
Sell/Order/OrderController.php:1007,1041,1075,2087 |
order edit sub-forms | redirectToRoute('admin_orders_view') |
Configure/ShopParameters/OrderStateController.php:168,258 |
editAction for order status and order return status |
redirectToRoute('admin_order_states'), inside the isSubmitted() block |
Not one of them renders the form again, so the inline error channel is never reached either. These call sites come from reading the code, only the order status case below was executed in the back office.
The helper was introduced in 1265a61e963 with the copy losing the true argument. Present on 9.2.x and develop.
Steps to reproduce
- Go to Shop Parameters > Order Settings > Statuses
- Edit any status, for instance "Awaiting check payment"
- Set the status name to
Awaiting check payment <test. The<is rejected by theTypedRegex(TYPE_GENERIC_NAME)constraint thatOrderStateTypedeclares on the translated child field, and that constraint has no HTML counterpart, so the browser lets the form through - Click Save
Expected behavior
The rejected value is reported, either as an inline error on the field or as an error flash message, the same way the deprecated base controller did with getErrors(true).
Actual Result
Redirected to the Statuses list with no error message and no success message, and the status name unchanged in ps_order_state_lang. Nothing tells the merchant that the submission was rejected.
Verified on a local docker install (9.2.0, develop): the POST /configure/shop/order-states/1/edit returns a 302, the list page that follows renders no alert at all, and ps_order_state_lang.name still holds the original value.
Note on picking a field to reproduce with
Not every invalid value reaches the server. On Design > Image settings for example, "JPEG compression" carries an HTML max="100", so setting 200 is blocked by native browser validation before the request is sent, even though ImageSettingsType declares a matching Range constraint server side. The bug shows up on fields whose constraint is server side only, such as TypedRegex or Length without a maxlength attribute.
How this was found
Reviewing PrestaShop/PrestaShop#41414, where the migrated Stores contact details form adds field level constraints and hits the same helper, producing the same silent rejection. The mechanism is the base controller helper, not that PR, hence this dedicated issue.
Suggested fix
Direction agreed with @jolelievre in the comments below: the shallow default stays, and making the controllers render the form instead of redirecting is out of scope here, since a redirect after an action can be a deliberate UX choice.
So the fix belongs to the helper: give it an explicit way to also collect child errors, and use it at the call sites that redirect, which today means all 9 of them.
One caveat on the shape. Adding an optional parameter to the existing protected method breaks any subclass that overrides it, PHP fatals on load:
PHP Fatal error: Declaration of C::addFlashFormErrors(FormInterface $form): void must be compatible with PrestaShopAdminController::addFlashFormErrors(FormInterface $form, bool $deep = false): voidPrestaShopAdminController is the base we recommend for module controllers in 9.x, so a separate method, something like addFlashFormErrorsDeep(), reaches the same result without that risk.
PrestaShop version where the bug happens
develop and 9.2.x
How have you installed PrestaShop
git clone, docker environment shipped in the repository
Source: PrestaShop/PrestaShop