Store each webform submission value in its own process variable

Closes #3628469. Uses the batch write of !616 (merged) (#3628468, merged).

What changes

  • Three webform handler settings, each shown with #states only when it applies:
    • Store each value in its own variable (once a values variable is named): each answer goes to <values variable>.<element key>, e.g. submission.manager_email, and the map itself is not written.
    • Split composite elements into their parts (while the above is checked): a composite holding one value is written part by part, submission.address.city. Without it the composite is one map under submission.address.
    • Split multiple values by position (while the above is checked): an element holding several answers is written from position 0, submission.colors.0; with composites split too, submission.addresses.0.city. Without it the answers stay one list.
  • On start and on every resume the submission id and all the answers are written in one setVariables() call: one audit record, whatever the number of fields. SubmissionValueSplitter builds the names.
  • One rule for a dotted name, VariablePath: the variable of exactly that name, else the shortest leading part that is a variable (the first one found decides), walked into. Read through it by the [orchestra:var:…] token, the context lines, the audiences reading a variable (Users, Email, Roles and Groups by variable now accept submission.manager_email) and the Views variable field, which loads every candidate name in its one query. FlowConditionBase reads names by the same rule written out inline: a condition is the engine's hottest read, and the call cost more than the read.
  • Every other reader of a variable name an author types follows the same rule: the relative and absolute deadlines, an action's target variable, the subprocess input and output maps and correlation key, the webform step's submission id variable, the attach task's type and id variables, and the payment resolver's amount, currency and account variables. Each used to read the exact name only, so the same name worked stored on its own and silently read nothing inside a map.
  • The Views variable filter accepts a dotted name, matching a variable stored under exactly that name.
  • The one-click signal for a flow on foo.bar writes the variable foo.bar, instead of a foo map holding only bar (which wiped the rest of foo).
  • French strings, docs/webform.md ("Answers in their own variables"), and the rule in docs/concepts.md, docs/tokens.md, docs/views.md.

Cost

Per name read, against the reading before this MR, measured with opcache on and without Xdebug (php -n):

Reader Name Before After
Flow conditions (rule written out inline) plain (decision) ~170 ns ~85 ns
an answer stored on its own (submission.manager_email) ~185 ns ~80 ns
one dot into a map (decision.outcome) ~235 ns ~233 ns
two dots into a map ~300 ns ~287 ns
a dotted name reading nothing ~177 ns ~199 ns
Deadlines, action target, subprocess maps and correlation key, submission id, attach task, payment (VariablePath::getValue()) plain name ~11 ns ($variables[$name] ?? NULL) ~48 ns

The +22 ns on a dotted name reading nothing is the exact-name lookup the rule adds. The +37 ns on the other readers is the method call, once per step execution; kept for readability. No query is added: an instance's variables are read in one query (!616 (merged)) and every name is read from that array.

Tests

  • SubmissionValueSplitterTest: every combination of the two splits, a list or an empty entry at a position staying whole; each guard checked against a mutant.
  • OrchestraInteractionHandlerTest: answers seeded one per variable with no map and in one audit record (a form with a real address element), refreshed on resume in one record; each fails against a mutant (setting ignored, one write per variable).
  • VariablePathTest, ComparisonTest: exact name first, shortest leading variable walked into, a failed walk reading nothing, NULL told apart from nothing.
  • Exact name first through each reader: ProcessTokensTest, ContextMessagesTest, InstanceVariableFieldTest, and VariableAssignmentTest (Users by variable on submission.manager_email, stored on its own and inside a map). Each fails when the rule reads a dotted name only from its first segment.
  • VariableFilterTest: a dotted name is accepted and matches the variable of that name; an engine variable is still refused.
  • One test per reader above reading a dotted name into a map (ComputedDeadlineTest, ActionTaskTest, ChildVariablesTest, SubprocessTest, FormFromSubmissionTest, AttachEntityTaskTest, CheckoutOrderingTest); each fails against that reader's exact-name read.
  • SignalOutcomesTest: a branch on order.status writes that variable and keeps the order map whole.
Edited by Frank Mably

Merge request reports

Loading
Loading