Symfony2:将请求绑定到表单后如何获取表单验证错误


110

这是我的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

Answers:


117

您有两种可能的方法:

  • 错误时不要重定向用户并{{ form_errors(form) }}在模板文件中显示
  • 访问错误数组为 $form->getErrors()

22
我做了您建议的第二件事,但是form-> getErrors()返回一个空数组。
putolaruan

2
我也做了第一个(带有php模板<?php echo $ view ['form']-> errors($ form)?>),但它仍然是空的!
putolaruan 2011年

59
@mives您必须error_bubbling通过显式设置每个字段的选项来将其设置为true。
kgilden 2011年

5
如果您使用自定义验证器,Symfony不会在$ form-> getErrors()中返回这些验证器生成的错误。
杰伊·谢思

13
您也$form->getErrors(true)可以添加子表格的错误
克里斯,

103

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

文件:
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,这似乎是最正确的答案。
Slava Fomin II

@Flip它在2.5上完美工作
iarroyo 2014年

1
很好的答案$errors[$child->getName()] = $this->getErrorMessages($child);是抛出异常,因为Symfony \ Bundle \ FrameworkBundle \ Controller \ Controller组件中缺少getErrorMessages。因此,我将其替换为$form_errors[$child->getName()] = $child->getErrorsAsString();
Ahad Ali

3
@AhadAli是一个递归函数,因此,当您将代码片段放入需要此功能的类中时,它将能够自行调用。您的“修复程序”将阻止您访问嵌套表格。它对其他37个人有用,也应该对您有用;)
Flip 2015年

@Flip不好意思,我只是在看,$this->getErrorMessages()我以为它直接在控制器内部和Symfony api的一部分中调用。
Ahad Ali 2015年

47

以下是对我有用的解决方案。此函数在控制器中,并将返回所有错误消息的结构化数组以及导致它们的字段。

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,但仍然不是我想要的。我希望数组键是字段名,但不是。
umpirsky'3

9
上面的代码中未引用@SalmanPK Twig。我不相信您的评论。
Icode4food 2012年

1
这是针对先前要点的修复,可在Symfony 2.1.7下使用。gist.github.com/WishCow/5101428
K. Norbert

看起来在Symfony2.1的样本中$this->getFormErrors应该有错别字$this->getErrorMessages
Mick

@umpirsky要获取字段名称,我得到了这个:$ child-> getConfig()-> getOptions()['label']让我永远找出来...
jsgoupil

35

使用验证器获取特定实体的错误

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参考:


谢谢,我需要+1
Phill Pafford 2012年

4
我不确定这是分别验证每个实体的好方法。如果您具有复杂的层次结构形式怎么办?第二个问题是验证发生两次。
Slava Fomin II

3
@SlavaFominII-“第二个问题是验证发生两次”-好点,什么都没有刷新!同样的错误列表!
BentCoder 2014年

20

为了获得当前使用的是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实例,而设置为false时将返回FormErrorIterator实例。


不错,使用相同的东西。
损坏的有机

是不是 :) @KidBinary
Cedo

绝对华丽,队友:)
损坏的有机

更好的选择是:$ errors = array_map(function($ item){return $ item-> current()-> getMessage();},$ campaignForm-> getErrors(true,false));
Enrique Quero

Symfony 2.7的良好解决方案
Yann Chabot,

16

对于Flash消息,我很满意 $form->getErrorsAsString()

编辑(来自Benji_X80):对于SF3使用 $form->getErrors(true, false);


3
我知道这是一个古老的答案,但以供将来参考:This method should only be used to help debug a form.
cheesemacfly

getErrorsAsString()在3.0中已弃用,请使用:$ form-> getErrors(true,false);
Benji_X80 '16

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

我打算对这个帖子发布一个新的答案,但是您似乎击败了我。我必须仔细查看源代码以找出为什么找不到方法调用。
Dr.Knowitall

我注意到这不会从错误冒泡设置为true的项目中提取错误。SF2.4
kinghfb 2014年

@stwe第一条IF语句的目的是什么?为什么它是互斥的?据我所知:窗体可以有它自己的错误以及子窗体。
Slava Fomin II 2014年

4

翻译的表单错误消息(Symfony2.1)

我一直在努力寻找这些信息,因此我认为绝对值得添加有关转换格式错误的注释。

@Icode4food答案将返回表格的所有错误。然而,返回的数组不考虑任一消息的复数翻译

您可以修改@Icode4food答案的foreach循环以使用组合:

  • 获取特定形式的所有错误
  • 返回翻译后的错误
  • 必要时考虑多元化

这里是:

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个不同的帖子:


刚尝试了您的版本,它就成功了Fatal Error: Call to undefined method Symfony\Component\Form\FormError::getMessagePluralization()。我怀疑这仅适用于Symfony 2.1吗?
Czar Pino

4

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

3

您还可以使用验证器服务来获取约束违规:

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

6
这将验证对象,但不会验证表单。例如,如果CRSF令牌是错误的原因,则不会包含该消息。
Icode4food

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

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

如果要摆脱每个错误描述文本中令人讨厌的“ Error: ”文本,请使用str_replace

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

2

如果您使用的是自定义验证器,则Symfony不会在中返回这些验证器生成的错误$form->getErrors()$form->getErrorsAsString()将返回您需要的所有错误,但不幸的是,其输出格式为字符串,而不是数组。

获取所有错误(无论来自何处)的方法取决于所使用的Symfony版本。

大多数建议的解决方案都涉及创建一个递归函数,该函数扫描所有子窗体,并将相关错误提取到一个数组中。Symfony 2.3没有该$form->hasChildren()功能,但是具有$form->all()

这是Symfony 2.3的帮助程序类,可用于从任何形式提取所有错误。(它基于yapro在Symfony的github帐户中的相关bug票上的注释中的代码。)

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

}

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元素的ID,因此您可以轻松地填充错误消息。

如果您在父表单中有子表单,请不要忘记cascade_validation在父表单的中添加该选项setDefaults


1

对于用于Twig错误显示的Symfony 2.1及更高版本,我更改了功能以添加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);
                }
            }
        }
    }

}


1

我想出了这个解决方案。它与最新的Symfony 2.4紧密结合。

我将尝试给出一些解释。

使用单独的验证器

我认为像其他作者所建议的那样,使用单独的验证来验证实体并返回违反约束的消息是一个坏主意。

  1. 您将需要手动验证所有实体,指定验证组等。对于复杂的层次结构形式,这根本不实用,并且会很快失去控制。

  2. 这样,您将两次验证表单:一次使用表单,一次使用单独的验证器。从性能角度来看,这是一个坏主意。

我建议对其子级递归地迭代表单类型以收集错误消息。

使用一些建议的方法和专有的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;
}

希望对您有所帮助。


@weaverryan您能看看我的解决方案吗?它是有效的,还是有缺点或任何误解?谢谢!
Slava Fomin II

1

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

希望能帮助


1

Symfony 3及更高版本

我最近做了一个创建表单错误树的函数。这将有助于将错误列表返回到前端。这是基于具有以下形式的表单类型:

'error_bubbling' => false

码:

public static function getFormErrorsTree(FormInterface $form): array
{
    $errors = [];

    if (count($form->getErrors()) > 0) {
        foreach ($form->getErrors() as $error) {
            $errors[] = $error->getMessage();
        }
    } else {
        foreach ($form->all() as $child) {
            $childTree = self::getFormErrorsTree($child);

            if (count($childTree) > 0) {
                $errors[$child->getName()] = $childTree;
            }
        }
    }

    return $errors;
}

输出:

Array
(
    [name] => Array
        (
            [0] => This value is not valid.
        )

    [emails] => Array
        (
            [0] => Array
                (
                    [0] => Given e-mail is not valid.
                    [1] => Given e-mail is not valid #2.
                )
            [1] => Array
                (
                    [0] => Given e-mail is not valid.
                    [1] => Given e-mail is not valid #2.
                )

        )

)

注意:我知道,如果高层有错误,则可以覆盖更深层字段中的错误,但这是我的用意。


非常适合var_dump,谢谢
ReaperSoon

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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.