Symfony2: как получить ошибки проверки формы после привязки запроса к форме

вот мой saveAction код (куда форма передает данные)

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();
}

мой вопрос: как я могу получить ошибки, если $form->isValid() возвращает false?

19 ответов


у вас есть два возможных способа сделать это:

  • не перенаправлять пользователя при ошибке и отображении {{ form_errors(form) }} в файле шаблона
  • массив ошибок доступа как $form->getErrors()

Symfony 2.3 / 2.4:

эта функция get-это все ошибки. По форме как "CSRF токен недействителен. Пожалуйста, попробуйте повторно отправить форму."а также дополнительные ошибки в форме детей,которые не имеют ошибок.

private function getErrorMessages(\Symfony\Component\Form\Form $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()] = $this->getErrorMessages($child);
        }
    }

    return $errors;
}

чтобы получить все ошибки в виде строки:

$string = var_export($this->getErrorMessages($form), true);

Symfony 2.5 / 3.0:

$string = (string) $form->getErrors(true, false);

Docs:
https://github.com/symfony/symfony/blob/master/UPGRADE-2.5.md#form https://github.com/symfony/symfony/blob/master/UPGRADE-3.0.md#form (внизу: The method Form::getErrorsAsString() was removed)


Ниже приведено решение, которое сработало для меня. Эта функция находится в контроллере и возвращает структурированный массив всех сообщений об ошибках и поле, которое их вызвало.

Symfony 2.0:

private function getErrorMessages(\Symfony\Component\Form\Form $form) {
    $errors = array();
    foreach ($form->getErrors() as $key => $error) {
        $template = $error->getMessageTemplate();
        $parameters = $error->getMessageParameters();

        foreach($parameters as $var => $value){
            $template = str_replace($var, $value, $template);
        }

        $errors[$key] = $template;
    }
    if ($form->hasChildren()) {
        foreach ($form->getChildren() as $child) {
            if (!$child->isValid()) {
                $errors[$child->getName()] = $this->getErrorMessages($child);
            }
        }
    }

    return $errors;
}

версия Symfony 2.1 и новее:

private function getErrorMessages(\Symfony\Component\Form\Form $form) {      
    $errors = array();

    if ($form->hasChildren()) {
        foreach ($form->getChildren() as $child) {
            if (!$child->isValid()) {
                $errors[$child->getName()] = $this->getErrorMessages($child);
            }
        }
    } else {
        foreach ($form->getErrors() as $key => $error) {
            $errors[] = $error->getMessage();
        }   
    }

    return $errors;
}

используйте валидатор, чтобы получить ошибки для определенного объекта

if( $form->isValid() )
{
    // ...
}
else
{
    // get a ConstraintViolationList
    $errors = $this->get('validator')->validate( $user );

    $result = '';

    // iterate on it
    foreach( $errors as $error )
    {
        // Do stuff with:
        //   $error->getPropertyPath() : the field that caused the error
        //   $error->getMessage() : the error message
    }
}

ссылка на API:


чтобы получить правильные (переводимые) сообщения, в настоящее время используя SF 2.6.3, вот моя конечная функция (поскольку ни один из вышеперечисленных, похоже, больше не работает):

 private function getErrorMessages(\Symfony\Component\Form\Form $form) {      
    $errors = array();
    foreach ($form->getErrors(true, false) as $error) {
        // My personnal need was to get translatable messages
        // $errors[] = $this->trans($error->current()->getMessage());
        $errors[] = $error->current()->getMessage();
    }

    return $errors;
}

как метод Form:: getErrors () теперь возвращает экземпляр FormErrorIterator, если вы не переключите второй аргумент ($flatten) на правда. (Затем он вернет FormError экземпляр, и вам придется вызвать метод getMessage () напрямую, без текущего() метод:

 private function getErrorMessages(\Symfony\Component\Form\Form $form) {      
    $errors = array();
    foreach ($form->getErrors(true, true) as $error) {
        // My personnal need was to get translatable messages
        // $errors[] = $this->trans($error->getMessage());
        $errors[] = $error->getMessage();
    }

    return $errors;
}

)

самое главное на самом деле установить первый аргумент в true, чтобы получить ошибки. Оставляя второй аргумент ($flatten) на его значение по умолчанию (правда) возвращает FormError экземпляры, в то время как он вернется FormErrorIterator случаях, когда установлено значение false.


функция для symfony 2.1 и новее, без какой-либо устаревшей функции:

/**
 * @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;
}

для моих флэш-сообщений я был доволен $form->getErrorsAsString()

редактировать (из Benji_X80): Для SF3 используйте $form->getErrors(true, false);


Переведенные Сообщения Об Ошибках Формы (Symfony2.1)

Я много боролся, чтобы найти эту информацию, поэтому я думаю, что определенно стоит добавить примечание о переводе ошибок формы.

@Icode4food answer вернет все ошибки формы. Однако возвращаемый массив не учитывает ни плюрализации сообщение или перевод.

вы можете изменить цикл foreach @Icode4food ответ есть комбо:

  • получить все ошибки определенной формы
  • возврат переведенной ошибки
  • при необходимости учитывайте плюрализацию

вот это:

foreach ($form->getErrors() as $key => $error) {

   //If the message requires pluralization
    if($error->getMessagePluralization() !== null) {
        $errors[] = $this->container->get('translator')->transChoice(
            $error->getMessage(), 
            $error->getMessagePluralization(), 
            $error->getMessageParameters(), 
            'validators'
            );
    } 
    //Otherwise, we do a classic translation
    else {
        $errors[] = $this->container->get('translator')->trans(
            $error->getMessage(), 
            array(), 
            'validators'
            );
    }
}

этот ответ был собран из 3 разных сообщений:


вы также можете использовать службу валидатора для получения нарушений ограничений:

$errors = $this->get('validator')->validate($user);

Переведенные Сообщения Об Ошибках Формы (Symfony2.3)

моя версия решения проблемы:

/ 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/контроллер / DefaultController.в PHP

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

В Symfony 2.5 вы можете получить все ошибки поля очень легко:

    $errors = array();
    foreach ($form as $fieldName => $formField) {
        foreach ($formField->getErrors(true) as $error) {
            $errors[$fieldName] = $error->getMessage();
        }
    }

если вы используете пользовательские валидаторы, Symfony не возвращает ошибки, созданные этими валидаторами в $form->getErrors(). $form->getErrorsAsString() вернет все ошибки, которые вам нужны, но его вывод, к сожалению, отформатирован как строка, а не массив.

метод, который вы используете для получения всех ошибок (независимо от того, откуда они пришли), зависит от того, какую версию Symfony вы используете.

большинство предлагаемых решений включают создание рекурсивной функции, которая сканирует все дочерние формы, и извлекает соответствующие ошибки в один массив. Symfony 2.3 не имеет $form->hasChildren() функция, но она имеет $form->all().

вот вспомогательный класс для Symfony 2.3, который вы можете использовать для извлечения всех ошибок из любой формы. (Он основан на коде из комментария yapro на соответствующем билете ошибки в учетной записи GitHub Symfony.)

namespace MyApp\FormBundle\Helpers;

use Symfony\Component\Form\Form;

class FormErrorHelper
{
    /**
     * Work-around for bug where Symfony (2.3) does not return errors from custom validaters,
     * when you call $form->getErrors().
     * Based on code submitted in a comment here by yapro:
     * https://github.com/symfony/symfony/issues/7205
     *
     * @param Form $form
     * @return array Associative array of all errors
     */
    public function getFormErrors($form)
    {
        $errors = array();

        if ($form instanceof Form) {
            foreach ($form->getErrors() as $error) {
                $errors[] = $error->getMessage();
            }

            foreach ($form->all() as $key => $child) {
                /** @var $child Form */
                if ($err = $this->getFormErrors($child)) {
                    $errors[$key] = $err;
                }
            }
        }

        return $errors;
    }
}

телефонный код:

namespace MyApp\ABCBundle\Controller;

use MyApp\FormBundle\Helpers;

class MyController extends Controller
{
    public function XYZAction()
    {
        // Create form.

        if (!$form->isValid()) {
            $formErrorHelper = new FormErrorHelper();
            $formErrors = $formErrorHelper->getFormErrors($form);

            // Set error array into twig template here.
        }
    }

}

основываясь на ответе @Jay Seth, я сделал версию класса FormErrors специально для форм Ajax:

// src/AppBundle/Form/FormErrors.php
namespace AppBundle\Form;

class FormErrors
{

    /**
     * @param \Symfony\Component\Form\Form $form
     *
     * @return array $errors
     */
    public function getArray(\Symfony\Component\Form\Form $form)
    {
        return $this->getErrors($form, $form->getName());
    }

    /**
     * @param \Symfony\Component\Form\Form $baseForm
     * @param \Symfony\Component\Form\Form $baseFormName
     *
     * @return array $errors
     */
    private function getErrors($baseForm, $baseFormName) {
        $errors = array();
        if ($baseForm instanceof \Symfony\Component\Form\Form) {
            foreach($baseForm->getErrors() as $error) {
                $errors[] = array(
                    "mess"      => $error->getMessage(),
                    "key"       => $baseFormName
                );
            }

            foreach ($baseForm->all() as $key => $child) {
                if(($child instanceof \Symfony\Component\Form\Form)) {
                    $cErrors = $this->getErrors($child, $baseFormName . "_" . $child->getName());
                    $errors = array_merge($errors, $cErrors);
                }
            }
        }
        return $errors;
    }
}

использование (например, в вашем действии):

$errors = $this->get('form_errors')->getArray($form);

версия Symfony: 2.8.4

пример ответа JSON:

{
    "success": false,
    "errors": [{
        "mess": "error_message",
        "key": "RegistrationForm_user_firstname"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_lastname"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_email"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_zipCode"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_user_password_password"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_terms"
    }, {
        "mess": "error_message2",
        "key": "RegistrationForm_terms"
    }, {
        "mess": "error_message",
        "key": "RegistrationForm_marketing"
    }, {
        "mess": "error_message2",
        "key": "RegistrationForm_marketing"
    }]
}

объект error содержит поле "key", которое является идентификатором входного элемента DOM, поэтому вы можете легко заполнять сообщения об ошибках.

если у вас есть дочерние формы внутри родительского, не забудьте добавить cascade_validation внутри родительской формы setDefaults.


в Symfony 3.X

другие SF 3.Методы X, приведенные здесь, не работали для меня, потому что я мог отправить пустые данные в форму (но у меня нет ограничений NotNull/NotBlanck). В этом случае строка ошибки будет выглядеть так:

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.
"

что не очень полезно. Поэтому я сделал следующее:

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;
}

который вернет это:

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."
}

для Symfony 2.1 и далее для использования с дисплеем ошибок Twig я изменил функцию, чтобы добавить FormError вместо того, чтобы просто извлекать их, таким образом, у вас больше контроля над ошибками и не нужно использовать error_bubbling на каждом отдельном входе. Если вы не установите его следующим образом {{ form_errors (form) }} останется пустым:

/**
 * @param \Symfony\Component\Form\Form $form
 *
 * @return void
 */
private function setErrorMessages(\Symfony\Component\Form\Form $form) {      

    if ($form->count() > 0) {
        foreach ($form->all() as $child) {
            if (!$child->isValid()) {
                if( isset($this->getErrorMessages($child)[0]) ) {
                    $error = new FormError( $this->getErrorMessages($child)[0] );
                    $form->addError($error);
                }
            }
        }
    }

}

$form - >getErrors () работает для меня.


Я придумал это решение. Он работает с последнимSymfony 2.4.

Я попытаюсь дать некоторые объяснения.

использование отдельного валидатора

Я думаю, что это плохая идея использовать отдельную проверку для проверки сущностей и возвращать сообщения о нарушении ограничений, как предложено другими авторами.

  1. вам нужно будет вручную проверить все объекты, указать группы проверки и т. д. С сложные иерархические формы это вообще не практично и быстро выйдет из-под контроля.

  2. таким образом, вы будете проверять форму дважды: один раз с формой и один раз с отдельным валидатором. Это плохая идея с точки зрения производительности.

Я предлагаю рекурсивно перебирать тип формы с его детьми для сбора сообщений об ошибках.

использование некоторых предлагаемых методов с exclusive IF statement

ответы на некоторые вопросы предложенные другими авторами содержат взаимоисключающие утверждения типа IF:if ($form->count() > 0) или if ($form->hasChildren()).

насколько я вижу, каждая форма может иметь ошибки, а также детей. Я не специалист по Symfony Forms компонент, но на практике вы не получите некоторые ошибки самой форме, как ошибка защиты CSRF или дополнительные поля ошибка. Предлагаю убрать это разделение.

использование денормализованного результата структура

некоторые авторы предлагают поместить все ошибки внутри простого массива. Таким образом, все сообщения об ошибках самой формы и ее дочерних элементов будут добавлены в один и тот же массив с различными стратегиями индексирования: на основе чисел для собственных ошибок типа и на основе имен для ошибок дочерних элементов. Предлагаю использовать нормализованную структуру данных вида:

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"

таким образом, результат можно легко повторить позже.

мое решение

вот мое решение такая проблема:

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;
}

Я надеюсь, что это поможет кому-то.


на Symfony 3.2 и выше этого,

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;
}

использовать как str_replace если вы хотите избавиться от надоедливого 'ошибка:' текст в каждом тексте описания ошибки.

$errors[$child->getName()] = str_replace('ERROR:', '', (string) $child->getErrors(true, false));

Для Symfony 2.1:

Это мое окончательное решение, объединяющее многие другие решения:

protected function getAllFormErrorMessages($form)
{
    $retval = array();
    foreach ($form->getErrors() as $key => $error) {
        if($error->getMessagePluralization() !== null) {
            $retval['message'] = $this->get('translator')->transChoice(
                $error->getMessage(), 
                $error->getMessagePluralization(), 
                $error->getMessageParameters(), 
                'validators'
            );
        } else {
            $retval['message'] = $this->get('translator')->trans($error->getMessage(), array(), 'validators');
        }
    }
    foreach ($form->all() as $name => $child) {
        $errors = $this->getAllFormErrorMessages($child);
        if (!empty($errors)) {
           $retval[$name] = $errors; 
        }
    }
    return $retval;
}

SYMFONY 3.1

Я просто реализовал статический метод для обработки отображения ошибок

static function serializeFormErrors(Form\Form $form)
{
    $errors = array();
    /**
     * @var  $key
     * @var Form\Form $child
     */
    foreach ($form->all() as $key => $child) {
        if (!$child->isValid()) {
            foreach ($child->getErrors() as $error) {
                $errors[$key] = $error->getMessage();
            }
        }
    }

    return $errors;
}

надеюсь помочь