Symfony2:将请求绑定到表单后,如何获取表单validation错误
这是我的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
我该如何得到错误?
你有两种可能的方式来做到这一点:
- 不要在错误时redirect用户,并在模板文件中显示
{{ form_errors(form) }}
- 访问错误数组为
$form->getErrors()
Symfony 2.3 / 2.4:
这个函数得到了所有的错误。 “CSRF令牌无效,请尝试重新提交表单”。 以及在没有错误冒泡的forms的孩子的其他错误。
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; }
以stringforms获取所有错误:
$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
)
以下是为我工作的解决scheme。 该函数位于控制器中,将返回所有错误消息的结构化数组以及导致它们的字段。
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; }
使用validation器来获取特定实体的错误
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参考:
- ConstraintViolationList
- ConstraintViolation
为了获得正确的(可翻译的)消息,目前使用SF 2.6.3,这里是我的最终function(因为上面没有任何东西似乎工作了):
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实例。
symfony 2.1和更新的function,没有任何弃用的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; }
对于我的Flash消息,我很高兴$form->getErrorsAsString()
编辑(从Benji_X80):对于SF3使用$form->getErrors(true, false);
翻译的表单错误信息(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个不同的职位:
- Symfony2 – 获取控制器中的validation错误
- 如何使用没有窗体的Symfony2validation器组件(实体和数据数组) 。
- Symfony2 – 从控制器的表单中获取所有错误
您也可以使用validation器服务来获取约束违规:
$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/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(); } }
如果使用自定义validation器,Symfony不会返回$form->getErrors()
validation器生成的错误。 $form->getErrorsAsString()
将返回所有你需要的错误,但是不幸的是它的输出被格式化为一个string,而不是一个数组。
你用来获取所有错误的方法(不pipe它来自何处)取决于你正在使用哪个版本的Symfony。
大多数build议的解决scheme都涉及创build一个recursion函数来扫描所有子窗体,并将相关的错误提取到一个数组中。 Symfony 2.3没有$form->hasChildren()
函数,但它有$form->all()
。
这里是Symfony 2.3的帮助类,您可以使用它来从任何forms提取所有错误。 (它基于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. } } }
基于@Jay Seth的回答,我特别为Ajax Forms制作了一个FormErrors类的版本:
// 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" }] }
错误对象包含“key”字段,它是inputDOM元素的id,因此您可以轻松地填充错误消息。
如果父窗体中有子窗体,则不要忘记在父窗体的setDefaults
内添加cascade_validation
选项。
SYMFONY 3.X
这里给出的其他SF 3.X方法不适合我,因为我可以向表单提交空数据(但我有NotNull / NotBlanck约束)。 在这种情况下,错误string将如下所示:
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的函数,而不是简单地检索它们,这样你就可以更好地控制错误,而不必在每个input上使用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()适用于我。
我想出了这个解决scheme。 它与最新的Symfony 2.4完美结合 。
我会试着给出一些解释。
使用单独的validation器
我认为使用单独的validation来validation实体并返回违反约束的消息是一个坏主意,就像其他作者所build议的那样。
-
您将需要手动validation所有实体,指定validation组等等。对于复杂的等级forms,这是不实际的,并且会很快失控。
-
通过这种方式,您将对表单进行两次validation:一次使用表单,一次使用单独的validation器。 从性能的angular度来看,这是一个糟糕的主意。
我build议recursion迭代与它的孩子的表单types收集错误消息。
使用一些build议的方法与独家IF语句
其他作者提出的一些答案包含这样的互斥IF语句: if ($form->count() > 0)
或if ($form->hasChildren())
。
据我所知,每一种forms都可能和孩子一样有错误。 我不熟悉Symfony Forms组件,但实际上你不会得到一些表单本身的错误 ,比如CSRF保护错误或额外的域错误。 我build议删除这个分离。
使用非规范化的结果结构
一些作者build议把所有的错误放在一个普通的数组中。 因此,表单本身及其子节点的所有错误消息将被添加到具有不同索引策略的相同数组中:基于数字的types本身的错误和基于名称的子节点错误。 我build议使用格式规范化的数据结构:
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"
这样的结果可以很容易地在以后迭代。
我的解决scheme
所以这是我对这个问题的解决scheme:
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 2.1:
这是我最后的解决scheme,将许多其他解决scheme放在一起
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; }
希望能提供帮助