Skip to content

New password element#201

Open
TheSyscall wants to merge 6 commits into
mainfrom
new-password-element
Open

New password element#201
TheSyscall wants to merge 6 commits into
mainfrom
new-password-element

Conversation

@TheSyscall

@TheSyscall TheSyscall commented Jul 8, 2026

Copy link
Copy Markdown

PasswordElement previously tracked its state through the ValueCandidates interface plus form event listeners (ON_VALIDATE/ON_SENT).
That indirection was fragile and PasswordElement was its only user. This PR replaces it with an element that manages its own state directly, and adjusts the surrounding infrastructure to support that.

  • Deprecate ValueCandidates. Only PasswordElement ever used it, even though BaseFormElement implemented it for every element. The interface is kept (deprecated, no replacement) so external implementers keep working.
  • Remove the ValueCandidates implementation from BaseFormElement.
  • FormElements::registerElement() now replays every accumulated populated value through setValue() in order, instead of applying only the last value and stashing the rest as candidates. Elements that only care about their final value are unaffected (last write wins). PasswordElement uses the ordering to tell a seeded secret apart from the request's own value.
  • Add Form::hasBeenValidated(): A pure check of whether validity has already been computed, so callers can peek without triggering validation (which would otherwise risk re-entrance).

Note

Much of the complexity of this PR comes from the fact that we have no reliable way to tell the following three cases apart:

  • An initial value set by the attribute
  • An initial value set by calling populate inside a controller
  • A populate call from a form submission

closes #82

Although BaseFormElement implements ValueCandidates for every element,
PasswordElement is its only consumer, and the upcoming password rework
no longer needs it. Mark the interface deprecated before removing the
implementation so downstream code gets a heads-up. No replacement is
provided.
Add hasBeenValidated(), a pure check of whether $isValid has been set,
so callers can peek at an already-known validity without ever triggering
it.
When an element is registered after values have been populated for its
name, replay every accumulated value through setValue() in order instead
of applying only the last one and stashing the full list via the removed
ValueCandidates interface.

Elements that only care about their final value are unaffected, as the
last setValue() call wins. The reworked PasswordElement, in turn, can
observe the seeded value and the request's own value as distinct
assignments.
Replace the ValueCandidates/event-listener based tracking with an
element that manages its own state directly, now that setValue() is
replayed with every populated value in order (see the preceding two
commits):

- Preserve the real value across DUMMYPASSWORD resubmissions instead of
losing it in an array of candidates.
- Track the first value received as a baseline to tell an actual change
apart from a resubmission of the unchanged placeholder.
- Skip validators entirely for the dummy placeholder; it isn't a real
password, so a policy validator must not run against it.
- Clear the field (rather than mask it) when a changed value fails
validation on an actual submission, but keep echoing it back plainly
during partial/autosubmit rounds so live typing isn't lost.
- Only mask when there's an actual pre-existing secret seeded before the
current request's own value (see $assignments); a plain form's password
field (e.g. a login form) never has one and is now treated like an
ordinary input instead of appearing pre-filled after submission.
Stop implementing the deprecated ValueCandidates interface in
BaseFormElement and drop the $valueCandidates property along with its
getter and setter. Nothing but PasswordElement ever relied on it, and
that element is being reworked to track its own state. The interface
itself is retained so any external implementers keep working.
FormElements::registerElement() now replays every populated value
through setValue(). FileElement::setValue() moves the uploaded file to
its destination, so a name that was populated more than once before the
element is registered caused the same upload to be moved twice, throwing
"Cannot retrieve stream after it has already been moved".

Reuse the already-stored file when it is present on disk instead of
moving the (now consumed) upload again.
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.

2 participants