symfonytwigsymfony-formssulu

form_errors() not displaying errors


I've run into a somewhat bizarre and frustrating problem with Symfony (6.1.4) forms. I'm also using Sulu CMS (hence the call to renderStructure() in my controller below) but the issue I'm having seems to be independent from that.

I've confirmed that form validation is working correctly. All of the right error messages show up in the web profiler. However, the errors are never actually displayed on the page.

I've tried manually displaying errors for each field using form_errors() and nothing shows up despite the web profiler detecting errors and the correct errors showing up in the response. If I call form_errors(form), all errors show up wherever I call it. It's as if error_bubbling is enabled somewhere but I haven't actually enabled it. I've also tried setting that option to false on every field and in configureOptions() when calling $resolver->setDefaults(). It's had no impact.

I've cleared my Symfony cache, browser cache, and cookies more times than I can count. I've tried reinstalling vendors while ignoring my Composer cache. I've rebuilt my assets with Webpack Encore thinking that maybe something went wrong there. No luck.

I'm currently using the Symfony Bootstrap 5 theme. I've also tried using the Bootstrap 4 theme and the default theme. Both have the same results.

All of this leads me to believe that it's likely to be a configuration problem of some sort. But I've completely run out of ideas for where to look or what to try. Unfortunately, none of the solutions I've found on this site regarding this topic have helped and the Symfony documentation hasn't either.

The code in my controller to create, handle, and render the form:

$form = $this->createForm(SomeSortOfType::class, $entity);

$form->handleRequest($request);

if ($form->isSubmitted()) {
    if ($form->isValid()) {
        $entity = $form->getData();

        // Do some stuff

        $this->addFlash('success', 'Success message');
    } else {
        // The following line correctly shows that the form has errors:
        // dump($form->getErrors()); exit;
        $this->addFlash('error', 'Error message');
    }
}

// As mentioned above, this is Sulu specific but all responses appear to be generated correctly
$response = $this->renderStructure(
    $structure,
    [
        'form' => $form->createView(),
    ],
    $preview,
    $partial,
);

return $response;

My Twig configuration:

twig:
  default_path: '%kernel.project_dir%/templates'
  form_themes: ['bootstrap_5_layout.html.twig']

Some examples from my Twig template:

{{ form_row(form.contact) }}

{# a bunch of stuff #}

{{ form_label(form.reportingPeriod) }}
<div class="input-group mb-4">
    {{ form_widget(form.reportingPeriod) }}
    {{ form_widget(form.reportingType) }}
</div>
{{ form_errors(form.reportingPeriod) }}
{{ form_errors(form.reportingType) }}

Unless I call form_errors(form), errors never show up anywhere in my form. I've even checked the page's source to make sure they weren't hidden for some reason. They simply never get rendered.

If any additional information or configuration is needed, I'm more than willing to post it. Thanks in advance!


Solution

  • I ended up solving this through a combination of other answers I found on Stack Overflow. I used Nairi Abgaryan's answer to help extract the property paths of the form fields with errors. Once extracted, I manually added errors to each property path using DonCallisto's answer.

    I still can't figure out why this isn't being done automatically by Symfony. The Form component knows exactly which fields have errors. Symfony even shows them in the web profiler. It must be removing them at some point in the process before the view is rendered.

    error_bubbling => false seems like it should do this but it doesn't appear to have any impact on whether errors bubble or not. They always seem to bubble to the top-most level. I'll need to do a lot more debugging to figure out exactly where it's falling apart and what configuration changes I'll need to make.

    While not my exact code, hopefully this will help others work out solutions for their own situations. Ideally, turning this functionality into a service would probably be best (unless one already exists I'm unaware of).

    $form = $this->createForm(SomeSortOfType::class, $entity, [
        'attr' => [
            // set this if you want to force server-side validation
            'novalidate' => 'novalidate',
        ],
    ]);
    
    $form->handleRequest($request);
    
    if ($form->isSubmitted()) {
        if ($form->isValid()) {
            $entity = $form->getData();
    
            // do stuff with the submitted data
    
            $this->addFlash('success', 'You did it!');
        } else {
            $errors = [];
    
            foreach ($form->getErrors(true) as $error) {
                $path = $error->getCause()->getPropertyPath();
    
                if (empty($path)) {
                    break;
                }
    
                $path = \preg_replace('/^(data.)|(.data)|(\\])|(\\[)|children/', '', $path);
                $message = $error->getCause()->getMessage();
                $errors[$path] = $message;
            }
    
            foreach ($errors as $path => $message) {
                $form->get($path)->addError(new FormError($message));
            }
    
            $this->addFlash('error', 'You failed!');
        }
    }
    
    $response = $this->renderStructure(
        $structure,
        [
            'form' => $form->createView(),
        ],
        $preview,
        $partial,
    );
    
    return $response;
    

    In a Twig template:

    {{ form_row(form.someField) }}
    
    {# or #}
    
    {{ form_errors(form.someField) }}
    

    form_row() and form_errors() will correctly display the errors for each property path now.