Symfony2 : How to get form validation errors after

2020-01-24 03:45发布

Here's my saveAction code (where the form passes the data to)

public function saveAction()
{
    $user = OBUser();

    $form = $this->createForm(new OBUserType(), $user);

    if ($this->request->getMethod() == 'POST')
    {
        $form->bindRequest($this->request);
        if ($form->isValid())
            return $this->redirect($this->generateUrl('success_page'));
        else
            return $this->redirect($this->generateUrl('registration_form'));
    } else
        return new Response();
}

My question is: how do I get the errors if $form->isValid() returns false?

标签: symfony
19条回答
我想做一个坏孩纸
2楼-- · 2020-01-24 03:55

I came up with this solution. It works solid with the latest Symfony 2.4.

I will try to give some explanations.

Using separate validator

I think it's a bad idea to use separate validation to validate entities and return constraint violation messages, like suggested by other writers.

  1. You will need to manually validate all the entities, specify validation groups, etc, etc. With complex hierarchical forms it's not practical at all and will get out of hands quickly.

  2. This way you will be validating form twice: once with form and once with separate validator. This is a bad idea from the performance perspective.

I suggest to recursively iterate form type with it's children to collect error messages.

Using some suggested methods with exclusive IF statement

Some answers suggested by another authors contain mutually exclusive IF statements like this: if ($form->count() > 0) or if ($form->hasChildren()).

As far as I can see, every form can have errors as well as children. I'm not expert with Symfony Forms component, but in practice you will not get some errors of the form itself, like CSRF protection error or extra fields error. I suggest to remove this separation.

Using denormalized result structure

Some authors suggest to put all errors inside of a plain array. So all the error messages of the form itself and of it's children will be added to the same array with different indexing strategies: number-based for type's own errors and name-based for children errors. I suggest to use normalized data structure of the form:

errors:
    - "Self error"
    - "Another self error"

children
    - "some_child":
        errors:
            - "Children error"
            - "Another children error"

        children
            - "deeper_child":
                errors:
                    - "Children error"
                    - "Another children error"

    - "another_child":
        errors:
            - "Children error"
            - "Another children error"

That way result can be easily iterated later.

My solution

So here's my solution to this problem:

use Symfony\Component\Form\Form;

/**
 * @param Form $form
 * @return array
 */
protected function getFormErrors(Form $form)
{
    $result = [];

    // No need for further processing if form is valid.
    if ($form->isValid()) {
        return $result;
    }

    // Looking for own errors.
    $errors = $form->getErrors();
    if (count($errors)) {
        $result['errors'] = [];
        foreach ($errors as $error) {
            $result['errors'][] = $error->getMessage();
        }
    }

    // Looking for invalid children and collecting errors recursively.
    if ($form->count()) {
        $childErrors = [];
        foreach ($form->all() as $child) {
            if (!$child->isValid()) {
                $childErrors[$child->getName()] = $this->getFormErrors($child);
            }
        }
        if (count($childErrors)) {
            $result['children'] = $childErrors;
        }
    }

    return $result;
}

I hope it'll help someone.

查看更多
该账号已被封号
3楼-- · 2020-01-24 03:56

$form->getErrors() works for me.

查看更多
Juvenile、少年°
4楼-- · 2020-01-24 03:59

The function for symfony 2.1 and newer, without any deprecated function:

/**
 * @param \Symfony\Component\Form\Form $form
 *
 * @return array
 */
private function getErrorMessages(\Symfony\Component\Form\Form $form)
{
    $errors = array();

    if ($form->count() > 0) {
        foreach ($form->all() as $child) {
            /**
             * @var \Symfony\Component\Form\Form $child
             */
            if (!$child->isValid()) {
                $errors[$child->getName()] = $this->getErrorMessages($child);
            }
        }
    } else {
        /**
         * @var \Symfony\Component\Form\FormError $error
         */
        foreach ($form->getErrors() as $key => $error) {
            $errors[] = $error->getMessage();
        }
    }

    return $errors;
}
查看更多
兄弟一词,经得起流年.
5楼-- · 2020-01-24 04:03

Translated Form Error Messages (Symfony2.3)

My version of solving the problem:

/src/Acme/MyBundle/Resources/config/services.yml

services:
    form_errors:
        class: Acme\MyBundle\Form\FormErrors

/src/Acme/MyBundle/Form/FormErrors.php

<?php
namespace Acme\MyBundle\Form;

class FormErrors
{
    public function getArray(\Symfony\Component\Form\Form $form)
    {
        return $this->getErrors($form);
    }

    private function getErrors($form)
    {
        $errors = array();

        if ($form instanceof \Symfony\Component\Form\Form) {

            // соберем ошибки элемента
            foreach ($form->getErrors() as $error) {

                $errors[] = $error->getMessage();
            }

            // пробежимся под дочерним элементам
            foreach ($form->all() as $key => $child) {
                /** @var $child \Symfony\Component\Form\Form */
                if ($err = $this->getErrors($child)) {
                    $errors[$key] = $err;
                }
            }
        }

        return $errors;
    }
}

/src/Acme/MyBundle/Controller/DefaultController.php

$form = $this->createFormBuilder($entity)->getForm();
$form_errors = $this->get('form_errors')->getArray($form);
return new JsonResponse($form_errors);

In Symfony 2.5 you can get all fields errors very easy:

    $errors = array();
    foreach ($form as $fieldName => $formField) {
        foreach ($formField->getErrors(true) as $error) {
            $errors[$fieldName] = $error->getMessage();
        }
    }
查看更多
Animai°情兽
6楼-- · 2020-01-24 04:03

SYMFONY 3.X

Other SF 3.X methods given here did not work for me because I could submit empty data to the form (but I have NotNull/NotBlanck constraints). In this case the error string would look like this :

string(282) "ERROR: This value should not be blank.
ERROR: This value should not be blank.
ERROR: This value should not be blank.
ERROR: This value should not be blank.
ERROR: This value should not be blank.
ERROR: This value should not be null.
name:
    ERROR: This value should not be blank.
"

Which is not very usefull. So I made this:

public function buildErrorArray(FormInterface $form)
{
    $errors = [];

    foreach ($form->all() as $child) {
        $errors = array_merge(
            $errors,
            $this->buildErrorArray($child)
        );
    }

    foreach ($form->getErrors() as $error) {
        $errors[$error->getCause()->getPropertyPath()] = $error->getMessage();
    }

    return $errors;
}

Which would return that :

array(7) {
  ["data.name"]=>
  string(31) "This value should not be blank."
  ["data.street"]=>
  string(31) "This value should not be blank."
  ["data.zipCode"]=>
  string(31) "This value should not be blank."
  ["data.city"]=>
  string(31) "This value should not be blank."
  ["data.state"]=>
  string(31) "This value should not be blank."
  ["data.countryCode"]=>
  string(31) "This value should not be blank."
  ["data.organization"]=>
  string(30) "This value should not be null."
}
查看更多
劳资没心,怎么记你
7楼-- · 2020-01-24 04:03

For Symfony 3.2 and above use this,

public function buildErrorArray(FormInterface $form)
{
    $errors = array();

    foreach ($form->getErrors() as $key => $error) {
        if ($form->isRoot()) {
            $errors['#'][] = $error->getMessage();
        } else {
            $errors[] = $error->getMessage();
        }
    }

    foreach ($form->all() as $child) {
        if (!$child->isValid()) {
            $errors[$child->getName()] = (string) $child->getErrors(true, false);
        }
    }
    return $errors;
}

Use str_replace if you want to get rid of the annoying 'Error:' text in each error description text.

$errors[$child->getName()] = str_replace('ERROR:', '', (string) $child->getErrors(true, false));
查看更多
登录 后发表回答