Skip to content

Add FieldsetElement::validatePartial() to fix required errors#191

Open
jrauh01 wants to merge 4 commits intomainfrom
fix/partially-validate-fieldset-elements
Open

Add FieldsetElement::validatePartial() to fix required errors#191
jrauh01 wants to merge 4 commits intomainfrom
fix/partially-validate-fieldset-elements

Conversation

@jrauh01
Copy link
Contributor

@jrauh01 jrauh01 commented Mar 19, 2026

When a form is submitted via autosubmit, only partial validation runs. For fieldset elements, a single child having a value was enough to trigger full validation of the entire fieldset, causing required errors to appear for fields that had just been revealed and were still empty.

Add a validatePartial() method to FieldsetElement that checks each child element individually for whether it has a value before validating it, and recurses into nested fieldsets. Form::validatePartial() is updated to detect fieldset elements and call validatePartial() on them.

fix #190

When a form is submitted via autosubmit, only partial validation runs. For
fieldset elements, a single child having a value was enough to trigger full
validation of the entire fieldset, causing required errors to appear for fields
that had just been revealed and were still empty.

Add a `validatePartial()` method to `FieldsetElement` that checks each child
element individually for whether it has a value before validating it, and
recurses into nested fieldsets. `Form::validatePartial()` is updated to detect
fieldset elements and call `validatePartial()` on them.
@jrauh01 jrauh01 self-assigned this Mar 19, 2026
@cla-bot cla-bot bot added the cla/signed label Mar 19, 2026
@nilmerg
Copy link
Member

nilmerg commented Mar 19, 2026

There were recent attempts to properly reflect functionality like this in interfaces. Now ipl\Html\Contract\FormElements exists due to this. Please consider extending this instead, given it's fairly new and we're about to introduce other equally fundamental changes.

When a form is submitted via autosubmit, it is only partially validated. For sub
elements implementing the `FormElements` interface it was enough for one child
to have a value to trigger full validation of the entire container, causing
"field is required" errors on fields that had just appeared and were still
empty.

`validatePartial()` is declared to the interface to future implementors are
required to provide it as well, and added to the trait to satisfy the
declaration from the interface with a default implementation.
@jrauh01
Copy link
Contributor Author

jrauh01 commented Mar 19, 2026

I'm not quite sure about the move to the trait as well, but as both 'validatePartial()` implementations were the same it seemed reasonable to me.

@nilmerg
Copy link
Member

nilmerg commented Mar 20, 2026

You've got a point there. My first thought yesterday was because \ipl\Html\Contract\FormElements describes something that well, has multiple form elements, why shouldn't it require to validate them. But without the possibility to validate them as a whole, this doesn't make much sense. But that cannot be required by this interface. \ipl\Html\Form also has multiple elements, but requires isValid(), so there is already a clear distinction between validation and element possession. \ipl\Html\Form::validate() isn't even itself part of an interface, it's an implementation detail. \ipl\Html\Form::validatePartial() has been one as well until now.

  • \ipl\Html\Contract\Form describes a form and how to interact with it
  • \ipl\Html\Contract\FormElements describes something that has multiple form elements and how to interact with them

validatePartial() is somewhat related to both, isn't it?

\ipl\Html\FormElement\FieldsetElement implements \ipl\Html\Contract\FormElements but should never need to implement \ipl\Html\Contract\Form. Though, the reason for partial validation is part of \ipl\Html\Contract\Form:

  • \ipl\Html\Contract\Form::hasBeenSent()
  • \ipl\Html\Contract\Form::hasBeenSubmitted()

Only in case a form is sent but not submitted, partial validation kicks in. Now delegated by the form to the fieldset.

So let's take another look at this. To partially validate a fieldset, which is a \ipl\Html\Contract\FormElement but also has \ipl\Html\Contract\FormElements, why shouldn't it be able to access the elements and validate them?

We could easily support this using a recursive \ipl\Html\Contract\FormElements::yieldElements() which is recursive because if it detects an element of its own, it yields from $element->yieldElements(). Though, I'm not quite sure either if requiring this in the interface is the right approach.

Let me know what you think :)

@jrauh01
Copy link
Contributor Author

jrauh01 commented Mar 20, 2026

I tried implementing the approach and if I understand it right, personally I like it a lot. Separating the concerns of validating and yielding through the form elements tree seems reasonable to me. What exactly are your concerns of adding such a yield function to the interface?

Instead of requiring `validatePartial()` in the `Contract\FormElements`
interface (and thus implementing it in the `FormElements` trait), introduce a
recursive `yieldElements()` generator that yields all sub form elements,
delegating into nested `FormElements` containers.

Compared to the previous approach, `validatePartial()` no longer needs to be
part of `Contract\FormElements`. The earlier implementation checked
`instanceof FormElements` to recurse manually. `yieldElements()` makes that
recursion reusable and separates traversal from validation logic.
@jrauh01 jrauh01 force-pushed the fix/partially-validate-fieldset-elements branch from e960f05 to 4e52f84 Compare March 20, 2026 11:23
@nilmerg
Copy link
Member

nilmerg commented Mar 20, 2026

For one, extending the interface. But also that yieldElements() adds nothing, except the recursion part, that isn't there already in the form of getElements().

I already mentioned that validatePartial() is an implementation detail of \ipl\Html\Form to me. So we can also make the recursion part an implementation detail.

What about \ipl\Html\Form::(protected)yieldElements(FormElements $from): Generator what allows validatePartial() to access all nested elements?

@jrauh01
Copy link
Contributor Author

jrauh01 commented Mar 20, 2026

Sounds even better and would avoid interface changes completely.

@jrauh01 jrauh01 force-pushed the fix/partially-validate-fieldset-elements branch from 8749c4e to b2a6bee Compare March 20, 2026 14:00
Partial validation is driven solely by `Form`, via `hasBeenSent()` and
`hasBeenSubmitted()`. Exposing `yieldElements()` on the `FormElements` interface
would force every implementor to provide a method that currently only makes
sense in a form's validation context.

Moving it to `Form` as a protected helper method keeps traversal an
implementation detail of the form. Unlike the trait based approach,
`FormElements` implementors are no longer required to satisfy a contract that
has no relevance to them.
@jrauh01 jrauh01 force-pushed the fix/partially-validate-fieldset-elements branch from b2a6bee to dbeb0cb Compare March 20, 2026 14:02
* @return $this
*/
public function validatePartial()
public function validatePartial(): static
Copy link
Member

Choose a reason for hiding this comment

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

Not sure actually that this belongs here. It's either already done by a dedicated PR or not possible due to high risk for a bc.

Copy link
Member

@nilmerg nilmerg left a comment

Choose a reason for hiding this comment

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

Otherwise it looks fine I think.

@nilmerg nilmerg added this to the v0.10.0 milestone Mar 20, 2026
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.

Required fields of fieldsets are invalid on autosubmit

2 participants