Skip to content

[RFC][Form] Add form::getErrorsAsArray #7205

Closed
lyrixx opened this Issue Feb 27, 2013 · 31 comments
@lyrixx
lyrixx commented Feb 27, 2013

I need in my project Form::getErrorsAsArray (like Form::getErrorsAsString), to render the error in a json response. It can be useful for a web service.

Do it make sens to add it to the form component ?

Here my code:

    public function getErrorsAsArray($form)
    {
        $errors = array();
        foreach ($form->getErrors() as $error) {
            $errors[] = $error->getMessage();
        }

        foreach ($form->all() as $key => $child) {
            if ($err = $this->getErrorsAsArray($child)) {
                $errors[$key] = $err;
            }
        }

        return $errors;
    }

If it's ok for you, I will create a PR.

@tystr
tystr commented Feb 27, 2013

+1 for having this functionality inside the form component...I've had to implement this numerous times

@raziel057

+1 I had to implement the same function in a special controller from which other controller inherit.

I was just thinking to create a PR for that.

@vicb
vicb commented Feb 27, 2013

Does this really belong to the core ?

Would using the JMSSerializerBundle solve your issue ?

@Koc
Koc commented Feb 28, 2013

Other variant of extraction errors to array:

    private function getErrorMessages(\Symfony\Component\Form\Form $form)
    {
        $errors = array();

        foreach ($form->getErrors() as $key => $error) {
            $template   = $error->getMessageTemplate();
            $parameters = $error->getMessageParameters();

            $errors[$key] = strtr($template, $parameters);
        }

        if ($form->hasChildren()) {
            foreach ($form->getChildren() as $child) {
                if (!$child->isValid()) {
                    $key = sprintf('%s[%s]', $form->getName(), $child->getName());
                    $errors[$key] = $this->getErrorMessages($child);
                }
            }
        }

        return $errors;
    }
@fabpot
Symfony member
fabpot commented Feb 28, 2013

@vicb: we already have Form::getErrorsAsString().

@vicb
vicb commented Feb 28, 2013
@sstok
sstok commented Feb 28, 2013

+1

@raziel057

@Koc The result is the same but in your variant, you use deprecated methods (like hasChildren, getChildren).

@vibs For me there is no link between this method and the usage of web services. The goal is simply to have an array representation of errors, which can be serialize to Json or not after. I think it's a common use case to be able to have the complete list of errors.

@lyrixx
lyrixx commented Feb 28, 2013

In fact, we can think this is usefull, but in this state, this is not so usefull.
The key are the formName, and generally, we want the label. so we need the form option, and more over, we need the translator. :(

private function getErrorsAsArray(\Symfony\Component\Form\Form $form)
{
    $errors = array();

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

    foreach ($form->all() as $child) {
        if ($err = $this->getErrorsAsArray($child)) {
            $options = $child->getConfig()->getOptions();
            $key = $this->get('translator')->trans($options['label'] ?: '', array(), $options['translation_domain'] ?: 'messages');
            $errors[$key] = $err;
        }
    }

    return $errors;
}
@Koc
Koc commented Feb 28, 2013

@raziel057 oh, haven't noticed that, thank you.

Big +1 for this method. We are using it for ajax responses with errors to extjs forms.

@raziel057

@lyrixx I don't see why the key should be localized. The key must be the fieldName, nothing else.

@lyrixx
lyrixx commented Feb 28, 2013

Why ? I really depends on your use case.
My use case is to show errors on the front (HTML). The end user does not care about the FieldName. Isn't it ?

@raziel057

@lyrixx Even if the end user doesn't care about the field names, your html elements needs to have a name (and it's a bad idea to set field label as name). If you have the element names in the list of error, you can easily link it to the corresponding fields. With js you can display errors bottom each fields. You can also get the the localized text of corresponding labels for each fields in error.

So for me there you shoudn't rely on localized text and the getErrorsAsArray must return a list of error which field name as key. If you really need a localized label, I suggest to iterate on the array to add the label from you controller.

@Koc
Koc commented Mar 6, 2013

It would be nice have same method for form values:

    $group = GuardGroupPeer::retrieveByPK($id);
    $form = $this->createForm(new GuardGroupType(), $group);
    $values = $form->getValuesAsArray(); // array('guardgroup[title]' => 'title', guardgroup[active_from] => DateTime)
@stof
Symfony member
stof commented Mar 7, 2013

Do you want the array representing the view data or the model data ?

@Koc
Koc commented Mar 7, 2013

yes, for bind it to extjs form for example. I can create separate ticket for this.

@stof
Symfony member
stof commented Mar 7, 2013

I'm wondering how the answer to my question can be "yes"...

@Koc
Koc commented Mar 7, 2013

View data, sorry bro.

@Koc
Koc commented Mar 7, 2013

Now we are using smth like this:

    private function getFormValues(\Symfony\Component\Form\FormView $formView)
    {
        $values = array();

        foreach ($formView->vars['form']->children as $subView)
        {
            if (count($subView->children))
            {
                $values = array_merge($values, $this->getFormValues($subView));
            }
            else
            {
                $values[$subView->vars['full_name']] = $subView->vars['value'];
            }
        }

        return $values;
    }
@stof
Symfony member
stof commented Mar 7, 2013

@Koc $form->getViewData() will give them for you.

@stof
Symfony member
stof commented Mar 7, 2013

Well, not the same formatting than the one you use here (it will be a nested array, not a flat array with foo[bar] keys)

@Koc
Koc commented Mar 7, 2013
        $childForm = $this->get('form.factory')->createNamedBuilder('subform', 'form', array('subtitle' => 'st1'))
            ->add('subtitle')
            ;

        $form = $this->createFormBuilder(array('title' => 't1'))
                ->add('title')
                ->add($childForm)
                ->getForm();

// getFormValues:
// array (size=3)
//  'form[title]' => string 't1' (length=2)
//  'form[subform][subtitle]' => string 'st1' (length=3)
//  'form[_token]' => string 'e04b43b8c0a6fd9d6095783f9e9f41613209329f' (length=40)

// getViewData:
// array (size=1)
//  'title' => string 't1' (length=2)

Maybe it should be method in FormView, not Form.

@tijuan
tijuan commented May 2, 2013

+1 for the feature
-1 for the field label as key

@apfelbox

Wouldn't it be useful to use the full form field id (as in <input id="...">) as key? At least this would make the mapping in JS from error to input element trivial.

@webmozart
Symfony member

I think that instead of adding a bunch of getErrorsAs*() methods, getErrors() should be improved to return a FormErrorCollection object. This object should

  • contain all FormError instances of the form
  • contain references to the FormErrorCollection instances of all child forms (these references should be established during submit(), because the submission also freezes the form tree)

Additionally, FormErrorCollection should implement

  • RecursiveIterator: This means that when iterated normally, the iterator produces all FormError instances (BC with the current array output of getErrors()). When iterated with a RecursiveIteratorIterator, the iterator produces all FormError instances recursively.
  • __toString(), featuring the same or a similar output as the current Form::getErrorsAsString()
  • Countable, returning the number of FormError instances contained (BC with the current getErrors()).
  • countAll(), returning the total number of error instances.

When all this is done, getErrorsAsString() can be deprecated.

@tijuan
tijuan commented Sep 17, 2013

A FormErrorCollection seems the best solution as we can then format the result the way we need.
This is THE missing feature of the Form Component

+1

@webmozart
Symfony member

This is THE missing feature of the Form Component

Like so many others... :)

Feel free to work on this, if you want.

@WouterJ
Symfony member
WouterJ commented Sep 22, 2013

I'm going to work on this one.

@webmozart
Symfony member

@WouterJ Great, thank you! :)

@WouterJ WouterJ referenced this issue Sep 22, 2013
Closed

[Form] Created FormErrorBag #9099

4 of 4 tasks complete
@webmozart
Symfony member

replaced by #9099

@webmozart webmozart closed this Oct 4, 2013
@yapro
yapro commented Nov 25, 2013

My version of solving the problem:
/src/Intranet/OrgunitBundle/Resources/config/services.yml

services:
    form_errors:
        class: Intranet\OrgunitBundle\Form\FormErrors

/src/Intranet/OrgunitBundle/Form/FormErrors.php

<?php
namespace Intranet\OrgunitBundle\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/Intranet/OrgunitBundle/Controller/DefaultController.php

$form = $this->createFormBuilder($entity)->getForm();
$form_errors = $this->get('form_errors')->getArray($form);
return new JsonResponse($form_errors);
@fabpot fabpot added a commit that referenced this issue Mar 11, 2014
@fabpot fabpot feature #9918 [Form] Changed Form::getErrors() to return an iterator …
…and added two optional parameters $deep and $flatten (webmozart)

This PR was merged into the 2.5-dev branch.

Discussion
----------

[Form] Changed Form::getErrors() to return an iterator and added two optional parameters $deep and $flatten

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | yes
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #7205
| License       | MIT
| Doc PR        | -

See the changes in the UPGRADE files for more information.

Commits
-------

6b3fbb5 [Form] Changed the default value of $flatten in Form::getErrors() to true
a9268c4 [Form] Changed Form::getErrors() to return an iterator and added two optional parameters $deep and $flatten
ce81199
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.