Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Form] Add ability to clear form errors #27580

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

Merged
merged 1 commit into from
Jun 25, 2018

Conversation

colinodell
Copy link
Contributor

@colinodell colinodell commented Jun 11, 2018

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

This PR adds the ability to manually clear form errors, thus improving the DX issue reported in #14060.

Unlike my original approach in #14233 and #27571 which break BC, this adds a new ClearableErrorInterface which Form implements. (Button does not implement it because buttons can't have errors.)

*
* @return $this
*/
public function clearErrors($deep = false);
Copy link
Member

Choose a reason for hiding this comment

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

Could true be a better default here? How frequent is to clear errors for root form only? I think most of the time it happens inside a child field.

There is an issue about the same default but getErrors($deep = false) method #25738 and was reported as confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't have a strong preference either way, but I do think the default value here should match getErrors().

@TerjeBr
Copy link

TerjeBr commented Jun 12, 2018

I have now thought about this, what about adding a FormExtendedInterface that extends both FormInterface and ClearableErrorInterface? The use case for the interface after all is to have a way to pass around form objects to functions and methods, and then you want an all inclusive interface that can cover all the methods implemented in the Form class. What do you think?

// Clear errors from children
foreach ($this as $child) {
if ($child instanceof ClearableErrorInterface) {
$child->clearErrors(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to have a clearable form type nested in a non-clearable one?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, buttons are not clearable and they could be included in a form that is clearable.

Copy link
Contributor

Choose a reason for hiding this comment

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

The question was the other way round. As the code here stops the traversal of the form tree as soon as a non-clearable form type is encountered, it will not clear clearable fields nested in non-clearable ones.

Copy link
Contributor

Choose a reason for hiding this comment

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

The only non-clearable fields we have in the symfony core are buttons that can't have children.

This could append in libraries but then it can be considered the responsibility of the library to implement this interface if needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I know. But why risk it, if the change would be fairly easy?

Copy link
Contributor

Choose a reason for hiding this comment

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

It would mean children won't be able to implement custom error cleaning logic, that's something someone might want to do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I know. But why risk it, if the change would be fairly easy?

We can't add clearErrors() to FormInterface as that would be a BC break. And even if we could, that would require us to implement clearErrors() on the Button class, which makes no sense because buttons can't have errors or children - it'll either be a pointless no-op or throw a BadMethodCallException.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wasn't hinting at implementing it everywhere, but decoupling the traversal of the form tree from the actual form field clearing.

Like the visitor pattern, if you will.
But this edge case doesn't seem too severe.

@nicolas-grekas nicolas-grekas added this to the next milestone Jun 14, 2018
*
* @return $this
*/
public function clearErrors($deep = false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add type hint to argument public function clearErrors(bool $deep = false);.
But please don't add : self, we already had discussion on this (#27190 (comment)).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added and amended! Thanks for the feedback 👍

@colinodell colinodell force-pushed the feature/clearable-forms branch 2 times, most recently from f365b92 to f86e407 Compare June 14, 2018 23:47
@vudaltsov
Copy link
Contributor

If the method is clearErrors than probably the interface might be ClearableErrorsInterface?

@colinodell colinodell force-pushed the feature/clearable-forms branch from f86e407 to 17c427e Compare June 14, 2018 23:55
@colinodell
Copy link
Contributor Author

Good idea @vudaltsov, I like that better! I have made that change.

Copy link
Contributor

@vudaltsov vudaltsov left a comment

Choose a reason for hiding this comment

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

Awesome! Looking forward to AJAX partial form reloading without errors workarounds 😃

Copy link
Contributor

@HeahDude HeahDude left a comment

Choose a reason for hiding this comment

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

Looks good, thanks!

*
* @return $this
*/
public function clearErrors(bool $deep = false);
Copy link
Contributor

Choose a reason for hiding this comment

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

You should type hint : self here

Copy link
Contributor

@vudaltsov vudaltsov Jun 16, 2018

Choose a reason for hiding this comment

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

@HeahDude , we had a discussion on return types here: #27190 (comment)
The problem will be that when using ->clearErrors()-> on actual object implementing this interface, the IDE will not show any other methods in autocomplete, because : self is not : static. So from my opinion it's much better to leave @return $this phpdoc.

Copy link
Contributor

@jvasseur jvasseur Jun 16, 2018

Choose a reason for hiding this comment

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

You can leave the @return $this phpdoc and add : self at the same time. AFAIK phpdoc takes precedence with type declaration for IDE auto completion, this means we would get both the correct auto completion and strictness at the language level (that an instance of the interface is returned).

Copy link
Contributor

@vudaltsov vudaltsov Jun 16, 2018

Choose a reason for hiding this comment

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

okay, then let's use : self 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using : self on an interface forces the Form subclass implementation to use a : ClearableErrorsInterace typehint:

image

Fatal error: Declaration of Symfony\Component\Form\Form::clearErrors(bool $deep = false): Symfony\Component\Form\Form must be compatible with Symfony\Component\Form\ClearableErrorsInterface::clearErrors(bool $deep = false): Symfony\Component\Form\ClearableErrorsInterface in src/Symfony/Component/Form/Form.php on line 61

I'm not sure this is what we want.

AFAIK phpdoc takes precedence with type declaration for IDE auto completion

I could not get this working in the latest version of PhpStorm:

image

Furthermore, I could not find any other interfaces in Symfony with a : self return type hint. For these reasons I think we should leave it as-is.

Copy link
Member

Choose a reason for hiding this comment

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

The fact that we don't have any existing interfaces with a self return type hint is probably because we couldn't make use of them before Symfony 4. So I would still use it here and use the docblock to be more precise.

/**
* Removes all the errors of this form.
*
* @param bool $deep whether to remove errors from child forms as well
Copy link
Contributor

Choose a reason for hiding this comment

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

Symfony code base uses capital style to comment params.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, amended, and force-pushed :) Thanks for catching that!

/**
* {@inheritdoc}
*/
public function clearErrors(bool $deep = false)
Copy link
Member

Choose a reason for hiding this comment

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

missing return type hint (same in the interface)

Copy link
Member

Choose a reason for hiding this comment

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

actually I am not even sure if it makes sense that this method returns anything

Copy link
Contributor

Choose a reason for hiding this comment

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

All form methods are chainable, so it makes sense to keep it here.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's time to decide this once and for all in https://symfony.com/doc/current/contributing/code/standards.html

Copy link
Member

Choose a reason for hiding this comment

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

Well, fluent methods are a pain for return types. so even if we want to have as much return types as possible for new APIs, I'm not sure we want them for fluent APIs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xabbuh @stof I have removed return $this; from the method for a few reasons:

  1. You're right, this method doesn't really need to be chainable.
  2. ClearableTokenStorageInterface (a new and similar interface introduced in SF4) does not return anything, so perhaps we should be consistent here.
  3. It seems like there's no definitive rule on how to type hint interface methods that return $this / self. I don't want to set a bad precedent here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then you should add : void in interface and class

Copy link
Contributor

Choose a reason for hiding this comment

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

But I am for consistency with FormInterface which has @return $this on methods like addError. Although I agree that fluent interfaces are pain.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've changed my mind and added return $this; back in. I could see developers wanting to call $form->clearErrors()->createView() so this definitely has value.

The last unanswered question is what to do about return types. Our ultimate goal is to have Form::clearErrors() return that original Form instance. Here's what the Return Type Declarations RFC has to say about our current situation:

The enforcement of the declared return type during inheritance is invariant; this means that when a sub-type overrides a parent method then the return type of the child must exactly match the parent and may not be omitted.

This is why using : self on the interface requires the Form method to use : ClearableErrorsInterface which is not ideal.

However, the RFC goes on to say:

If the parent does not declare a return type then the child is allowed to declare one.

Because this is a valid approach, I think omitting the return type from the interface but using : self on the Form makes the most sense in this situation:

  1. A return type on the interface is pointless because there are literally no other methods on ClearableErrorsInterface. Is somebody really going to call $form->clearErrors()->clearErrors()->clearErrors()?
  2. This gives us the functionality we want (accurate return type hint on Form) despite PHP's current limitations with invariant return types.

*
* @return $this
*/
public function clearErrors(bool $deep = false): self
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xabbuh I have added a : self return type here per your request. See #27580 (comment) for an explanation of why I didn't also add a return type to the interface. Let me know your thoughts :)

@colinodell
Copy link
Contributor Author

Tests are failing, but I don't think it's because of my changes. All tests under src/Symfony/Component/Form are passing locally.

@fabpot fabpot force-pushed the feature/clearable-forms branch from ed74efd to 9eb755c Compare June 25, 2018 09:51
@fabpot
Copy link
Member

fabpot commented Jun 25, 2018

Thank you @colinodell.

@fabpot fabpot merged commit 9eb755c into symfony:master Jun 25, 2018
fabpot added a commit that referenced this pull request Jun 25, 2018
This PR was squashed before being merged into the 4.2-dev branch (closes #27580).

Discussion
----------

[Form] Add ability to clear form errors

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #14060
| License       | MIT
| Doc PR        | symfony/symfony-docs#9916

This PR adds the ability to manually clear form errors, thus improving the DX issue reported in #14060.

Unlike my original approach in #14233 and #27571 which break BC, this adds a new `ClearableErrorInterface` which `Form` implements.  (`Button` does not implement it because buttons can't have errors.)

Commits
-------

9eb755c [Form] Add ability to clear form errors
@fabpot
Copy link
Member

fabpot commented Jun 25, 2018

Changelog added in a1eb649 (forgot to comment before merging)

javiereguiluz added a commit to symfony/symfony-docs that referenced this pull request Jun 25, 2018
…uiluz)

This PR was merged into the master branch.

Discussion
----------

[Form] Ability to clear form errors

Documenting the new feature proposed in symfony/symfony#27580

Commits
-------

f07f536 Wrap long lines and add the versionadded directive
7a4465d Clarify that clearing errors makes the form valid
d4143f1 [Form] Ability to clear form errors
@nicolas-grekas nicolas-grekas modified the milestones: next, 4.2 Nov 1, 2018
This was referenced Nov 3, 2018
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.