【问题标题】:symfony2 : form errors not displayed in twig despite a non null getErrorsAsString()symfony2:尽管 getErrorsAsString() 为非空,但 Twig 中未显示表单错误
【发布时间】:2014-12-23 11:53:39
【问题描述】:

我正在使用 symfony 2,并且我有一个表单,我在上面放置了 @Assert\NotBlank() 注释。 我自愿填写字段,我的表单没有通过 isValid 和 isSubmitted 测试,在这些行之后我得到了一个非空值 exit(var_dump($recipeForm->getErrors()));

  private 'errors' => 
    array (size=4)
      0 => 
        object(Symfony\Component\Form\FormError)[4119]
          private 'message' => string 'Cette valeur doit être vide.' (length=29)
          protected 'messageTemplate' => string 'This value should be blank.' (length=27)
          protected 'messageParameters' => 
            array (size=1)
              ...
          protected 'messagePluralization' => null
          private 'cause' => 
            object(Symfony\Component\Validator\ConstraintViolation)[4062]
              ...
          private 'origin' => null

在我的 twig 模板中,我使用一个简单的表单(表单)呈现表单。 form_errors(form) 或 form_errors(form.field) 不会呈现错误。为什么?为什么我要进一步了解问题出在哪里?

我有一个相当复杂的类型。对于我网站的其他一些形式,错误会正确显示。 我的类型:

<?php
//src/AppBundle/Form/FoodAnalytics/RecipeType.php
namespace AppBundle\Form\FoodAnalytics;

use AppBundle\Form\Core\MediaType;
use AppBundle\Repository\FoodAnalytics\UnitRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class RecipeType extends AbstractType
{
    protected $recipeIngredientQueryBuilder;
    protected $recipeSubrecipeQueryBuilder;
    protected $unitRepository;
    protected $action;

    /**
     * @return string
     */
    public function getName()
    {
        return 'appbundle_foodanalytics_recipe' . $this->action;
    }

    public function __construct(UnitRepository $unitRepository, $recipeIngredientQueryBuilder=null, $recipeSubrecipeQueryBuilder=null, $action = null)
    {
        $this->recipeIngredientQueryBuilder = $recipeIngredientQueryBuilder;
        $this->recipeSubrecipeQueryBuilder = $recipeSubrecipeQueryBuilder;
        $this->unitRepository = $unitRepository;
        $this->action = $action == null ? null : '_' . $action;
    }

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('recipeCategories', 'genemu_jqueryselect2_entity',array(
                    'multiple' => true,
                    'class' => 'AppBundle:FoodAnalytics\RecipeCategory',
                    'label' => 'Catégories',
                    'required' => false,
                    'by_reference' => false,
                    'attr'=>array(
                        'data-toggle'=>"tooltip",
                        'data-placement'=>"top",
                        'title'=>"Indiquez les catégories dans lesquelles enregistrer la recette pour un recherche future plus facile",
                    )))
            ->add('isProduct', null, array(
                    'label'=>'Correspond à un produit fini',
                    'required'=>false,
                    'attr'=>array(
                        'data-toggle'=>"tooltip",
                        'data-placement'=>"top",
                        'title'=>"La recette correspond-elle à un produit fini qui peut être mis en vente ?",
                    )))
            ->add('name', null, array(
                    'label'=>'Nom détaillé',
                    'attr'=>array(
                        'data-toggle'=>"tooltip",
                        'data-placement'=>"top",
                        'title'=>"Indiquez le nom détaillé de la recette. Par exemple : 'millefeuilles praliné ganache vanille sur feuilletage inversé'",
                    )))
            ->add('nickName', null, array(
                    'label'=>'Nom raccourci',
                    'attr'=>array(
                        'data-toggle'=>"tooltip",
                        'data-placement'=>"top",
                        'title'=>"Indiquez un nom raccourci pour la recette. Par exemple : 'millefeuilles'",
                    )))
            ->add('recipeIngredients', 'collection', array(
                    'type' => new RecipeIngredientType($this->unitRepository, $this->recipeIngredientQueryBuilder),
                    'by_reference' => false,
                    'label'=>'Ingrédient',
                    'allow_add' => true,
                    'allow_delete' => true,
                    'cascade_validation' => true,
                ))
            ->add('subrecipes', 'collection', array(
                    'type' => new RecipeSubrecipeType($this->unitRepository, $this->recipeSubrecipeQueryBuilder),
                    'by_reference' => false,
                    'label'=>'Sous-recette',
                    'allow_add' => true,
                    'allow_delete' => true
                ))
            ->add('recipeSteps', 'collection', array(
                    'type' => new RecipeStepType(),
                    'by_reference' => false,
                    'label'=>'Etape de production',
                    'allow_add' => true,
                    'allow_delete' => true
                ))
            ->add('portions', null, array(
                    'label'=>'Nombre de parts / de pièces',
                    'required' => false,
                    'attr'=>array(
                        'data-toggle'=>"tooltip",
                        'data-placement'=>"top",
                        'title'=>"Indiquez le nombre d'éléments disponibles dans la recette. Cela peut permettre d'utiliser l'unité 'U' dans les recettes parentes qui l'utiliseront",
                    )))
            ->add('shortDescription', null, array(
                    'label'=>'Description courte',
                    'required'=>false,
                    'attr'=>array(
                        'data-toggle'=>"tooltip",
                        'data-placement'=>"top",
                        'title'=>"Décrivez succinctement la recette",
                    )))
            ->add('medias', 'collection', array(
                    'type' => new MediaType(),
                    'by_reference' => false,
                    'label'=>'Medias',
                    'allow_add' => true,
                    'allow_delete' => true,
                    'required' => false,
                    'attr'=>array(
                        'data-toggle'=>"tooltip",
                        'data-placement'=>"top",
                        'title'=>"Ajoutez des images ou vidéos pour décrire la recette",
                    )))
            ->add('content', 'textarea', array(
                    'label'=>'Instructions générales',
                    'required'=>false,
                    'attr' => array(
                        'data-toggle'=>"tooltip",
                        'data-placement'=>"top",
                        'class' => 'summernote',
                        'title'=>"Ajoutez du contenu supplémentaire pour détailler la recette",
                    )))
            ->add('workingDuration', 'timepicker', array(
                        'label'=>'Temps total de travail',
                        'required' => false,
                        'attr'=>array
                        (
                            'class' => 'timepicker',
                            'data-toggle'=>"tooltip",
                            'data-placement'=>"top",
                            'title'=>"Indiquez le temps total de travail consacré à la recette si il diffère du temps de travail cumulé des étapes de production",
                        )))
            ->add('sleepDuration', 'timepicker', array(
                    'label'=>'Temps total de repos',
                    'required' => false,
                    'attr'=>array
                    (
                        'data-toggle'=>"tooltip",
                        'class'=>'timepicker',
                        'data-placement'=>"top",
                        'title'=>"Indiquez le temps total de repos consacré à la recette si il diffère du temps de repos cumulé des étapes de production",
                    )))
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\FoodAnalytics\Recipe',
//            'cascade_validation' => true,
        ));
    }
}

编辑:仅保留此表单的一个简单字段不会改变任何内容,不会显示错误

【问题讨论】:

    标签: php forms validation symfony twig


    【解决方案1】:

    看看这里的错误是如何呈现的:http://symfony.com/doc/current/cookbook/form/form_customization.html#customizing-error-output

    使用{{ form_errors(form) }} 的问题在于它显示全局表单错误,而不是单个字段,而getErrorsAsString() 将向下钻取所有字段。如果您希望通过{{ form_errors(form) }} 访问所有单个字段错误,则需要编辑表单中的每个字段并添加选项error_bubbling =&gt; true

    如果您没有将我们所有字段的错误冒泡设置为 true,那么您将需要单独呈现每个字段的错误 - 例如:{{ form_errors(form.name) }},或者仅使用呈现标签的{{ form_row(form.name) }},表单元素和错误都在一个镜头中。

    【讨论】:

    • 不幸的是,error_bubbling => true 不起作用。如果我使用 form(form) 或 form_errors(form.field) 或 form_row(form.field) 呈现表单,则没有任何变化。对于这些值,我还有一个空转储,而 getErrorsAsString 不为空。我网站上的其他一些表单工作正常...您知道如何跟踪错误,从它们被捕获的位置到它们显示的位置吗?我想我需要额外的调试技巧......
    • 您是否确保在每个单独的字段中添加error_bubbling =&gt; true?当我第一次开始使用 Symfony 表单时,我以为我可以为整个表单设置该选项,但事实并非如此。它必须是每个字段。也许您可以发布添加错误冒泡的配置,以及用于显示表单的 Twig 代码?
    • 好吧,如果我将 error_bubbling 放在 nickName 上并且还放置了 Assert\NotBlank(),我会得到相同的结果:没有错误显示,但我可以在 getErrorsAsString 中看到错误。我将放置一个更简单的表单错误示例来尝试缩小范围
    【解决方案2】:

    天哪!

    对不起,我让人们为此浪费了时间!问题是我作为响应传递了一个数组$response = array(form-&gt;createView()),但是这个数组响应是在我处理请求之前生成的。因此,为什么我可以在 vardump 中看到它,而不是在 twig 中。

    【讨论】:

    • 这几乎总是很简单的事情。很高兴你明白了。
    【解决方案3】:

    似乎该错误是由属于您的Recipe 实体但未添加到RecipeType 的属性的验证规则引起的,因此该错误无法链接到任何子表单。

    尝试检查您的实体并找到具有验证规则但未添加到 RecipeType 的属性,或者为该属性添加默认值或使用 Validation Groups

    【讨论】:

    • 嗯,错误实际上是由添加到 recipetype 的字段引起的,我知道是哪一个,因为我自愿输入错误。 form->geterrorsasstring 显示正确的错误但不显示树枝模板,这是奇怪的部分。
    猜你喜欢
    • 2013-03-12
    • 2012-05-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-18
    • 1970-01-01
    • 2019-02-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多