Repository navigation
#160 - Full control over the launch form fields (type, options, constraints, default) through the new form UI option - #168
clever-age-gtonon wants to merge 1 commit into
Conversation
…raints, default) through the new form UI option Add the `form` UI option declaring the `input` field and each context field of the launch form (type alias or FQCN, form type options with their constraints, default value), whether free context pairs are allowed, and the constraints of the whole form. Typed values are converted into process values by the new ContextValueNormalizer. Deprecate the `entrypoint_type`, `default` and `constraints` UI options (folded into `form` until 4.0). Reserve the `execution_user` context key, validate the format of the free context keys (launch form and schedules), resolve only the requested process option in ProcessConfigurationsManager and the UI options once per launch request. Require symfony/doctrine-bridge. Add documentation, an upgrade guide and tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
njoubert-cleverage
left a comment
There was a problem hiding this comment.
Thanks for this work: the feature covers the whole scope of #160, the deprecated options are cleanly folded into form, the deprecation messages name the process, and the docs, upgrade guide and tests (403 → 588) are thorough. CI is green and the CHANGELOG conflict of #161 is solved.
Requesting changes for the points below (details in the inline comments).
Blocking
- Scope. The PR bundles three changes that are not part of #160 and deserve their own issue/PR (and changelog entry):
execution_useralways set to the connected user and reserved (security-related behaviour change, previously a context value could override it);- the format rule on the keys of the free context pairs, including the schedules;
ProcessConfigurationsManagerresolving only the requested option (ui/notification/logs), which changes how invalidnotification/logsoptions are reported.
- Key format rule = BC break in a minor. Free keys such as
api.token,a/bora bused to work and are now refused at launch, and existing schedules can no longer be saved from the UI. The root cause is in process-bundle:ContextualOptionResolverbuilds its regex from the context keys withoutpreg_quote()(that is what thezz)|x|(?:zz/a/btests show). It should be fixed there; here the naming rule should only apply to the keys declared inform.context, which must be valid form field names anyway. - Form type: FQCN only.
FORM_TYPE_ALIASESintroduces a second naming scheme for the form types: a closed list of 35 entries to maintain, while any type (including a project-specific one) can already be given by its FQCN. It also requiressymfony/doctrine-bridgeonly for theentityalias. I'd remove it and accept FQCNs only, as the Symfony form builder does and as in the draft of #160:resolveFormType()then only checksclass_exists/FormTypeInterface/ not a button, andsymfony/doctrine-bridgecan be dropped fromcomposer.json(docs, changelog and tests to update). This also solves theentityissue: its data is the entity itself, converted through__toString(), i.e. its label rather than its identifier, and its default value cannot be written in YAML.
The short names of the constraints (Choice,File...) are not concerned: that is the existingConstraintLoadersyntax. A test with a constraint given by its FQCN (e.g. a project-specific constraint) is missing though: it is documented but not covered.
Important
- Default values of object types: the docs cover
date/dateinterval(input: string), but notenum(a YAML scalar where the field expects an enum case). Either refuse it at resolution or document it. - Late configuration errors: an invalid field option gives a 500 on the launch page, and a non-convertible value gives a 500 after the user filled and submitted the form. At least the latter should be reported as a form error.
- Process list:
list.html.twigresolves the UI options of every process, so one invalidformconfiguration breaks the whole page, and every rendering triggers one deprecation per process using the deprecated options.
Minor
ProcessConfigurationsManagergrows to ~700 lines, mostly the launch form resolution: it could move to a dedicated (internal) resolver.- Public API surface:
getLaunchFormDefaults()is only used by the tests;buildLaunchFormDefaults(), theui_optionsoption ofLaunchTypeandContextValueNormalizer(instantiated withnewin the controller) should be@internal. - The declared context fields are
required: trueby default (Symfony default): acheckboxmust then be checked (thedry_runexample of #160 needs an explicitrequired: false). Afalsedefault for the declared context fields would be safer. - The CHANGELOG entry is a single ~2000-character paragraph: it would be easier to read split into bullets.
- One 3k-line commit makes the review hard: splitting per point 1 would help.
🤖 Generated with Claude Code
| // The rule of FormConfigBuilder::isValidName() applied to the declared context keys of the launch form, and | ||
| // not only digits (PHP casts such an array key to an integer) | ||
| new Regex( | ||
| pattern: '/^(?![0-9]+$)[a-zA-Z0-9_][a-zA-Z0-9_\-:]*$/D', |
There was a problem hiding this comment.
This rejects keys that used to work (api.token, a/b, a b...) in the launch form and the schedules: BC break in a minor. The underlying problem is ContextualOptionResolver in process-bundle, which puts the context keys into a regex without preg_quote(); it should be fixed there. Here, the naming rule is only needed for the keys declared in form.context (form field names).
| * | ||
| * @var array<string, class-string> | ||
| */ | ||
| private const FORM_TYPE_ALIASES = [ |
There was a problem hiding this comment.
I'd drop this alias table and accept FQCNs only for type (as the Symfony form builder and the draft of #160): one naming scheme, nothing to maintain, a project-specific type works the same way, and symfony/doctrine-bridge is no longer needed.
| 'ulid' => Type\UlidType::class, | ||
| 'collection' => Type\CollectionType::class, | ||
| 'repeated' => Type\RepeatedType::class, | ||
| 'entity' => EntityType::class, |
There was a problem hiding this comment.
The data of an EntityType is the entity: through ContextValueNormalizer it becomes its __toString() (a label, not the identifier), and its default value cannot be written in YAML. Removing the aliases (see above) removes this one too.
| "symfony/console": "^6.4 || ^7.4 || ^8", | ||
| "symfony/dependency-injection": "^6.4 || ^7.4 || ^8", | ||
| "symfony/deprecation-contracts": "^2.5 || ^3", | ||
| "symfony/doctrine-bridge": "^6.4 || ^7.4 || ^8", |
There was a problem hiding this comment.
Only required for the entity alias: can be removed with FQCN-only types.
| * | ||
| * @return LaunchFormData | ||
| */ | ||
| public function getLaunchFormDefaults(string $processCode): array |
There was a problem hiding this comment.
Only used by the tests: new public API without a caller. Could be removed (or @internal), same question for buildLaunchFormDefaults().
| if (null === $recipients) { | ||
| return null; | ||
| $resolver->setDefaults(['notification' => [], 'logs' => [], 'ui' => []]); | ||
| foreach (['notification', 'logs', 'ui'] as $name) { |
There was a problem hiding this comment.
Resolving only the requested option is a sensible change, but it is not part of #160 and changes how invalid notification / logs options are reported (no longer by the process list): it deserves its own issue/PR.
| $context | ||
| ) | ||
| // The connected user always wins over a context value | ||
| [ProcessExecuteMessage::EXECUTION_USER_KEY => $this->getUser()?->getEmail()] + $context |
There was a problem hiding this comment.
Good change, but it is a security-related behaviour change unrelated to #160 (a context value could override execution_user before): it deserves its own issue/PR and changelog entry.
| return (string) $value; | ||
| } | ||
|
|
||
| throw new \LogicException(\sprintf('The value of "%s" ("%s") cannot be used as a process context value: use a form type returning a scalar (e.g. "choice" with scalar choices), or make the class implement \Stringable.', $name, get_debug_type($value))); |
There was a problem hiding this comment.
This is raised after the user filled and submitted the form, and ends as a 500. It would be friendlier as a form error (or detected earlier). Also, this class is instantiated with new in the controller: @internal?
Description
Implements #160 (replaces #161, rebased on
main): the process configuration now has full control over the UI launch form (ui_launch_mode: form). For theinputfield and each context key, it declares the form type, its options (with the field constraints) and its default value. Example: thedelimitercontext value becomes a "Delimiter" select instead of two free text inputs.The data of the form keeps its shape (
{input, context: {key => value}}), so the dispatched message is unchanged.Changes
New
formUI option (ProcessConfigurationsManager):form.inputandform.context.<key>:type,options,default.typeis a closed list of aliases (text,choice,checkbox,integer,date,datetime,dateinterval,enum,entity…) or a form type FQCN.FormTypeInterface, and a button type are each refused with an explicitInvalidOptionsException.options.constraintsuses the same syntax as the global constraints (ConstraintLoader).data,mapped,property_pathandinherit_dataoptions are refused;default/multipleare refused on a file input;extraandexecution_userare reserved keys.form.allow_extra_context(defaulttrue): keeps the free key/value collection for the undeclared keys. Withfalse, an undeclared key in the defaults is refused at resolution.form.constraints: constraints of the whole form, as the formerconstraints.getUiOptions()returns the effectiveformoption: the deprecated format is folded into it.getLaunchFormDefaults()/buildLaunchFormDefaults().getUiOptions,getNotificationOptions,getLogsOptions) now resolves only its own option:Launch form (
LaunchType,ProcessContextType,LaunchAction):contextis now a compoundFormType: one child per declared key, then theextraCollectionTypeof the free pairs. It is merged back into{key => value}on submit, and the declared keys come first.form.constraintson[context][key]are mapped to the declared field.requiredof the declared fields is respected; the rendering of the old format is unchanged.reserved_keysoption ofProcessContextType.execution_useris always the email of the connected user:LaunchAction::dispatch()no longer lets the context override it;form.context;ProcessExecuteMessage::EXECUTION_USER_KEY, constraintProcessContextType::createExecutionUserConstraint()._,-,:, not only digits):key_constraintsoption ofProcessContextType. This also applies to the schedules (ProcessScheduleCrudController), which reserveexecution_usertoo.ui_optionsoption ofLaunchType.ContextValueNormalizer, applied after the validation. It converts the typed values into process values:BackedEnum→ value,UnitEnum→ name;DateInterval→ ISO 8601;Stringable→ string;LogicExceptionwith an explicit message.Deprecations (removal in 4.0):
entrypoint_type→form.input.type,default→form.*.default,constraints→form.constraints.InvalidOptionsException.Dependencies: require
symfony/doctrine-bridge(^6.4 || ^7.4 || ^8) for theentityalias.Documentation:
docs/reference/02-process_ui_options.md: form fields, type aliases, value conversion, free pairs, recommendation ofallow_extra_context: falsewhenform.contextis declared;docs/reference/04-process_executions_and_logs.md,05-scheduler.md,09-notifications.md;docs/cookbooks/form_file_upload.mdrewritten withform:;UPGRADE.mdv3.3 section (before/after example);Tests
403 → 588 tests (1842 assertions), with no deprecation, warning or notice (
--display-deprecations --display-warnings --display-notices).ProcessConfigurationsManagerTest:form: aliases, FQCN, invalid types, constraints, forbidden options, keys, file fields;getLaunchFormDefaults;ui/notification/logsresolutions.ContextValueNormalizerTest(new): every conversion rule and the exceptions.ProcessContextTypeTest:reserved_keys,key_constraints, key format (zz)|x|(?:zz,a/b,a b,{{x}},123… refused).LaunchTypeTest:execution_user;required;allow_extra_context: false;ui_options.Functional/LaunchActionTest:.txtrefused,,and;CSV accepted);Functional/ProcessScheduleCrudTest:execution_userrefused (422, nothing persisted);Controller/Admin/Process/LaunchActionTest(new):dispatch()keeps the email of the connected user asexecution_user.Checks
PHP 8.2 + lowestjob could not be run locally: to be confirmed by the CI.Requirements
Breaking changes
None for a configuration that keeps working with the deprecated options (minor, 3.3). Points to check when upgrading (detailed in
UPGRADE.md):launch[context][<index>]tolaunch[context][extra][<index>].contextis now aFormType(form theme blocks_launch_context_extra_entry_*).default.contextkeys: merged, the last one wins.execution_useris reserved. Existing schedules keep running, but must be renamed to be saved from the UI.notification/logsoptions: they are no longer reported by the process list, only when used (worker, purge command).🤖 Generated with Claude Code