Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
lyrixx opened this issue Feb 27, 2013 · 31 comments
Closed

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

lyrixx opened this issue Feb 27, 2013 · 31 comments

Comments

@lyrixx
Copy link
Member

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
Copy link
Contributor

tystr commented Feb 27, 2013

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

@raziel057
Copy link
Contributor

+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
Copy link
Contributor

vicb commented Feb 27, 2013

Does this really belong to the core ?

Would using the JMSSerializerBundle solve your issue ?

@Koc
Copy link
Contributor

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
Copy link
Member

fabpot commented Feb 28, 2013

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

@vicb
Copy link
Contributor

vicb commented Feb 28, 2013

@fabpot my point is that this would probably be used in the context of a ws (web service) and then you probably want to include jms or fosrestbundle.

Anyway I am not opposed to the change.

Fabien Potencier notifications@github.com wrote:

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


Reply to this email directly or view it on GitHub:
#7205 (comment)

@sstok
Copy link
Contributor

sstok commented Feb 28, 2013

+1

@raziel057
Copy link
Contributor

@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
Copy link
Member Author

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
Copy link
Contributor

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
Copy link
Contributor

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

@lyrixx
Copy link
Member Author

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
Copy link
Contributor

@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
Copy link
Contributor

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
Copy link
Member

stof commented Mar 7, 2013

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

@Koc
Copy link
Contributor

Koc commented Mar 7, 2013

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

@stof
Copy link
Member

stof commented Mar 7, 2013

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

@Koc
Copy link
Contributor

Koc commented Mar 7, 2013

View data, sorry bro.

@Koc
Copy link
Contributor

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
Copy link
Member

stof commented Mar 7, 2013

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

@stof
Copy link
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
Copy link
Contributor

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
Copy link

tijuan commented May 2, 2013

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

@apfelbox
Copy link
Contributor

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
Copy link
Contributor

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
Copy link

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
Copy link
Contributor

This is THE missing feature of the Form Component

Like so many others... :)

Feel free to work on this, if you want.

@wouterj
Copy link
Member

wouterj commented Sep 22, 2013

I'm going to work on this one.

@webmozart
Copy link
Contributor

@wouterj Great, thank you! :)

@webmozart
Copy link
Contributor

replaced by #9099

@yapro
Copy link
Contributor

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 added a commit that referenced this issue Mar 11, 2014
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests