Navigation Menu

Skip to content
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

[Form] Created FormErrorBag #9099

Closed
wants to merge 3 commits into from
Closed

[Form] Created FormErrorBag #9099

wants to merge 3 commits into from

Conversation

wouterj
Copy link
Member

@wouterj wouterj commented Sep 22, 2013

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

The only BC break in this PR is checking it with is_array. Iterating
over the array works just the same as previously:

  • Normally iterating over the RecursiveIterator results in only
    iterating over the errors of the current form.
  • Iterating using RecursiveIteratorIterator results in iterating over
    all errors recursively.

Todo

  • Adding __toString() support and deprecating Form::getErrorsAsString().
  • Use this class in Form:getErrors()
  • Fixing tests
  • Use field name as key

*/
protected $errors = array();

private $_invalid = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the leading underscore. We don't use it in Symfony

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used it because invalid is not a real property of the object, it's just a helper property. But I'll remove it

@wouterj
Copy link
Member Author

wouterj commented Sep 24, 2013

I've fixed the tests, added some things for BC (see below) and added the last missing features. I think it's ready for review @stof @bschussek

  • The FormErrorCollection now also implements ArrayAccess, to be BC with accessing the errors using $errors[1]
  • All fixes in the Validator extension tests are not a proof of BC break, because nobody is going to match the return value of Form::getErrors() with some expected stuff.
  • The ButtonTypeTests were failing because they passed an array to Form::addError(), I fixed this by converting this array to a FormErrorCollection

*
* @author Wouter J <wouter@wouterj.nl>
*
* @since v2.4.0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if this is needed...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but you can change v2.4.0 to 2.4.

@stof
Copy link
Member

stof commented Sep 24, 2013

I think your ArrayAccess is not BC as the offset does not concern only errors but also child error collections. So getting errors[1] can give your a FormErrorCollection while it was giving you the second error previously

*/
public function addCollection($formName, $collection)
{
// BC with using arrays with errors
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to support arrays here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some tests are failing without this. BTW, do you know why the tests are failing now? It seems there is another BC bug...

@wouterj
Copy link
Member Author

wouterj commented Oct 1, 2013

fixed all comments. I know I'm one day after the feature freeze, but I'd like to see this included in 2.4...

@fabpot
Copy link
Member

fabpot commented Oct 1, 2013

@wouterj You're lucky as I'm still working on merging the last PRs for 2.4... let's wait for @bschussek thoughts first.

return array_keys($this->errors);
}

public function __toString()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing phpdoc for many methods

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really want to have PHPdoc for PHP build-in methods, like __toString etc?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes because otherwise it's not documented what the string representation actually returns

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this is one of the main points of FormErrorBag

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also its not doccumented that it accepts a parameter

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the string representation wasn't documented on getErrorsAsString() too, I don't really know why we should do it here. And the parameter is only for internal use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is the parameter used internally?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used to indent the children forms with 4 spaces

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok as long as its not used outside the class

2.4.0
-----

* changed getErrors() to return a FormErrorBag, which is countable,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing bc break info

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what i also mean is that it should be tagged with [BC BREAK], see other changelog entries

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also getErrors now returns all errors, whereas it previously returned only direct child errors, or?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tobion no it does not. It returns a RecusiveIterator instead of an array. So you can have all errors if you wrap it in a RecursiveIteratorIterator, but the default iteration still gives you only the form errors only (and not the direct child errors as you said in your comment)

@asm89
Copy link
Contributor

asm89 commented Oct 11, 2013

@fabpot @bschussek Any chance of getting this in 2.4?

}

return $errors;
return (string) $this->getErrors();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be $this->getErrors()->__toString($level)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? Level should be the default, 0, and explicitly calling a magic method seems wrong to me. But if there is any reason why this is better or if this is the CS, I'm happy to change this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wouterj because if you ignore the $level argument, you are not providing BC totally (even if it was considered as an internal argument, I'm sure some people were using it)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@hugohenrique
Copy link
Contributor

ping

@asm89
Copy link
Contributor

asm89 commented Nov 27, 2013

ping @fabpot @bschussek I guess this won't make 2.4...

@Koc
Copy link
Contributor

Koc commented Dec 11, 2013

is it possible to convert this bag to array like

'metal_companiesbundle_company[companyPhones][1][phone]' => 
    array (size=1)
      0 => string 'Error description.'

?

Where key is form field full name and value is list of the errors for field. It is useful for ajax responses (bind tooltips with errors to fields)

@wouterj
Copy link
Member Author

wouterj commented Dec 25, 2013

@Koc no, that's not possible with the method itself. But with some array changer functions, you can create something like that.

}

return $errors;
}

/**
* @deprecated Deprecated since version 2.4, to be removed in 3.0. Covert
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be 2.5 and Convert instead of Covert.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, can you put everything on the same line?

@fabpot
Copy link
Member

fabpot commented Dec 28, 2013

@wouterj Besides some small typos, looks good to me. Can you also rebase on current master and add a ticket for the documentation? Thanks.

This is fully BC with returning an array and it deprecates
Form::getErrorsAsString()
@wouterj
Copy link
Member Author

wouterj commented Dec 28, 2013

@fabbot everything is now fixed and rebased

@wouterj
Copy link
Member Author

wouterj commented Dec 28, 2013

doc issue: symfony/symfony-docs#3397

@fabpot
Copy link
Member

fabpot commented Dec 28, 2013

apparently, tests are broken

@wouterj
Copy link
Member Author

wouterj commented Dec 28, 2013

I'm now stuck... Maybe someone can help me?

When using the RecursiveIterator directly, it should only return the FormError instances in the root of the array and not the FormError instances in the FormErrorBag. This all works fine in the current method, we just skip the value when it's an instance of FormErrorBag and see if the next one is a FormError instance.

However, in the valid method, we can't do it that way. If we skip the FormErrorBag instances in that method and only return true when the next element exists and it is a FormError instance, we can't recursively traverse over the child form errors.

Imagine FormErrorBag::$errors contains this:

array(
    [0] => FormError instance (...),
    [child] => FormErrorBag instance (...)
)

When iterating, the following things happen:

  1. FormErrorBag::valid() is called and returns true, [0] exists
  2. FormErrorBag::current() is called and returns [0] element
  3. FormErrorBag::next() is called and sets the pointer to the next element
  4. FormErrorBag::valid() is called and returns true, [child] exists
  5. FormErrorBag::current() is called and returns [child]

In the 5th step it should not have returned the FormErrorBag object, but it can't do nothing else. The valid method already said the element exists and the current method has to return a FormError object. It can't go to the next element, as there is no next element.

How can we fix this, so that the 5th step isn't executed? We can't add a test on instance in the valid method...

@igorw
Copy link
Contributor

igorw commented Dec 28, 2013

From what I can tell, you really have two different cases. And you should not be trying to make both work with the same iterator instance.

The original iterator is a FormErrorBag that may recursively contain other FormErrorBag instances. I'll call it $errors. Here's the two cases:

Case A is looking at the top level errors only. In simple terms, this means filter('isFormError', $errors), where isFormError checks for instanceof FormError.

Case B is recursively walking over over the nested FormErrorBag instances (which happen to implement Iterator) and as such you can use a RecursiveIterator. Or in a different sense, you are flattening the nested list-of-lists to a list of FormError elements.

So what you really want is FormErrorBag to contain two methods. One getTopLevelErrors() that returns itself wrapped inside a FilterIterator. And a getAllErrors() that returns itself wrapped inside a flattening RecursiveIterator.

@igorw
Copy link
Contributor

igorw commented Dec 28, 2013

After looking at the current implementation I can also pinpoint the problem. It is calling next() from within valid(). It is using recursion on a mutable object which is going to cause lots of problems because it actually already advances the iterator while you are checking the validity.

What it comes down to is this. Currently, you are trying to do the recursion inside FormErrorBag directly, instead of wrapping it inside a RecursiveIterator, which would keep that responsibility separate.

@wouterj
Copy link
Member Author

wouterj commented Dec 30, 2013

I think you right @igorw we can't mix them into one class. It would be nice if we could though...

However, we have to be BC with an array, so I propose to have a FormErrorBag which extends ArrayIterator (or we keep the current implementation). And it has a method getAllErrors() which returns a RecursiveArrayIterator.

@bschussek I'd love to hear your opinion about this.

🎆

@wouterj
Copy link
Member Author

wouterj commented Dec 31, 2013

Closing in favor of #9914

@wouterj wouterj closed this Dec 31, 2013
@wouterj wouterj deleted the fix_7205 branch December 31, 2013 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet