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

91

Здесь мой код 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?

Теги:

18 ответов

100
Лучший ответ

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

  • не перенаправлять пользователя при ошибке и отображать {{ form_errors(form) }} в файле шаблона
  • массив ошибок доступа как $form->getErrors()
  • 20
    Я сделал второе, что вы предложили, но form-> getErrors () возвращает пустой массив.
  • 2
    Я также сделал первый (w / php templates <? Php echo $ view ['form'] -> errors ($ form)?>), Но все равно он пуст!
Показать ещё 7 комментариев
89

Symfony 2.3/2.4:

Эта функция получает все ошибки. Те, что похожи на "Тонер 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)

  • 1
    Это выглядит как наиболее правильный ответ для текущей версии Symfony 2.4.
  • 0
    @ Flip It отлично работает на 2.5
Показать ещё 3 комментария
46

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

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;
}
  • 5
    Улучшенный gist.github.com/2011671 но все еще не то, что я хочу. Я хочу, чтобы ключи массива были именами полей, но это не так.
  • 4
    И они все еще говорят, что Symfony не привязан к Twig. Вот Это Да!
Показать ещё 7 комментариев
35

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

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:

  • 0
    Спасибо, что мне нужно +1
  • 1
    Я люблю тебя! Спасибо!
Показать ещё 3 комментария
18

Чтобы получить правильные (переводимые) сообщения, в настоящее время использующие 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) на true. (Затем он вернет экземпляр FormError, и вам нужно будет вызвать метод getMessage() напрямую, без метода current():

 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) в его значение по умолчанию (true), он вернет экземпляры FormError, в то время как он вернет экземпляры FormErrorIterator, если установлено значение false.

  • 0
    Хороший, используя те же вещи.
  • 0
    не так ли? :) @KidBinary
Показать ещё 3 комментария
15

Функция для 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;
}
  • 0
    Я собирался опубликовать новый ответ на этот пост, но вы, кажется, опередили меня. Мне пришлось просмотреть исходный код, чтобы выяснить, почему не были найдены вызовы методов.
  • 0
    Я заметил, что это не вытягивает ошибки из элементов, для которых ошибка пузыривания установлена в true. SF2.4
Показать ещё 1 комментарий
13

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

Изменить (от Benji_X80): Для использования SF3 $form->getErrors(true, false);

  • 3
    Я знаю, что это старый ответ, но для дальнейшего использования: This method should only be used to help debug a form. ( источник )
  • 0
    Я не знал, что это предназначено только для отладки, спасибо!
Показать ещё 1 комментарий
4

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

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

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

Вы можете изменить цикл 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 разных сообщений:

  • 0
    Только что попробовал вашу версию, и она вышла из-за Fatal Error: Call to undefined method Symfony\Component\Form\FormError::getMessagePluralization() . Я подозреваю, что это только для Symfony 2.1?
  • 0
    Вот так. Я обновил свой ответ. Спасибо
3

Сообщения об ошибках переведенной формы (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/Controller/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();
        }
    }
3

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

$errors = $this->get('validator')->validate($user);
  • 6
    Это подтвердит объект, но не форму. Если, например, токен CRSF был причиной ошибки, сообщение не будет включено.
2

СИМФОНИЯ 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."
}
2

Основываясь на ответе @Jay Seth, я сделал версию класса FormErrors, особенно для Ajax Forms:

// 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"
    }]
}

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

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

2

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

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

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

Вот вспомогательный класс для Symfony 2.3, который вы можете использовать для извлечения всех ошибок из любой формы. (Он основан на коде из комментария yapro по связанному биту в учетной записи gifub 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.
        }
    }

}
1

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

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

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

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

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

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

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

Использование некоторых предложенных методов с эксклюзивным выражением IF

Некоторые ответы, предложенные другими авторами, содержат взаимоисключающие 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;
}

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

  • 0
    @weaverryan, не могли бы вы взглянуть на мое решение, пожалуйста? Это действительно, или есть недостатки или какие-то заблуждения? Спасибо!
1

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

1

Для 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);
                }
            }
        }
    }

}
0

СИМФОНИЯ 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;
}

В надежде помочь

0

Для 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;
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню