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,我如何得到错误?

233747 次浏览

你有两种可能的方法:

  • do not redirect user upon error and display \{\{ form_errors(form) }} within template file
  • 访问错误数组为 $form->getErrors()

您还可以使用验证器服务来获得违反约束的情况:

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

使用 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
}
}

空气污染指数参考资料:

下面是对我有效的解决方案。该函数位于控制器中,将返回所有错误消息的结构化数组以及导致错误消息的字段。

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

For my flash messages I was happy with $form->getErrorsAsString()

编辑(来自 Benji _ X80) : SF3使用 $form->getErrors(true, false);

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

我一直在努力寻找这方面的信息,所以我认为这是绝对值得添加一个注释的形式错误的翻译。

@Icode4food answer will return all errors of a form. However, the array that is returned does not take into account either 信息多元化 or 翻译.

您可以修改 @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'
);
}
}

这个答案来自三个不同的帖子:

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

对于 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 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)

对于 Symfony 2.1以后的 Twig 错误显示,我改变了函数,添加了一个 FormError,而不是简单地检索它们,这样你就可以更好地控制错误,而不必在每个单独的输入上使用 error _ 冒泡。如果您没有按照\{\{ form _ error (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-> getError ()对我来说很有用。

Translated Form Error Messages (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();
}
}

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

您使用的获取所有错误的方法(不管它们来自哪里)取决于您使用的 Symfony 版本。

Most of the suggested solutions involve creating a recursive function that scans all child forms, and extracts the relevant errors into one array. Symfony 2.3 doesn't have the $form->hasChildren() function, but it does have $form->all().

下面是 Symfony 2.3的一个助手类,您可以使用它从任何表单中提取所有错误。 (它是基于 yapro 在 Symfony github 账户上的一个相关错误评论的代码。)

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


}

我想出了这个解决方案。它与最新的 Symfony 2.4牢固地工作。

我会尽量给出一些解释。

使用单独的验证器

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

  1. 您将需要手动验证所有的实体,指定验证组等等。对于复杂的分层形式,它根本不实用,而且很快就会失控。

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

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

使用一些具有独占 IF 语句的建议方法

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

在我看来,每种形式都可能有错误,就像子形式一样。我不是 Symfony 表格组件的专家,但在实践中你不会得到一些错误的形式本身,如 CSRF 保护错误额外的田地错误。我建议取消这次分居。

Using denormalized result structure

一些作者建议将所有错误放在一个普通数组中。因此,表单本身及其子表单的所有错误消息都将被添加到同一个数组中,并使用不同的索引策略: 基于数字的索引用于类型本身的错误,基于名称的索引用于子表单错误。我建议使用表单的规范化数据结构:

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"

这样以后就可以很容易地迭代结果。

我的解决办法

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

希望能帮到别人。

为了获得正确的(可翻译的)消息,目前使用的是 SF2.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: : getError ()方法现在返回 FormErrorIterator的实例,除非您将第二个参数($flatten)切换到 没错。(然后它将返回一个 形式错误实例,您必须直接调用 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)保留为默认值(没错)将返回 形式错误实例,而当设置为 false 时将返回 FormErrorIterator实例。

基于@Jay Seth 的回答,我编写了一个 FormError 类的版本,特别针对 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;
}
}

Usage (e.g. in your action):

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

如果在父窗体中有子窗体,不要忘记在父窗体的 setDefaults中添加 cascade_validation选项。

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

希望能帮上忙

交响曲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 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 _ place

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

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


)


)


注意 : 我知道如果较高级别有错误,那么来自较深级别字段的错误可能会被覆盖,但这是我有意使用的。

对于 第五交响曲,我让这个工作:

public function getErrorMessages(Form $form) {
  

$errors = [];


foreach ($form->all() as $child) {
foreach ($child->getErrors() as $error) {
$name = $child->getName();
$errors[$name] = $error->getMessage();
}
}


return $errors;
}

我得到了这样的 JSON 的东西:

errors: {
firstname: "The firstname must be between 2 and 30 characters long.",
lastname: "The lastname must be between 2 and 40 characters long."
subject: "Please choose a subject."
text: "Empty messages are not allowed."
}

对我来说,它很好,也很甜蜜,但我猜它不会在全局级别检测到错误,比如超时令牌或其他类似的东西。 不管怎样,我已经花了很长时间寻找一个适合我的情况的解决方案,我很高兴在这里贡献,希望它能帮助一些开发人员在某些时候以某种方式。