Skip to content

#160 - Full control over the launch form fields (type, options, constraints, default) through the new form UI option - #168

Open
clever-age-gtonon wants to merge 17 commits into
mainfrom
#160-execution-form
Open

clever-age-gtonon wants to merge 17 commits into
mainfrom
#160-execution-form

Conversation

@clever-age-gtonon

@clever-age-gtonon clever-age-gtonon commented Oct 15, 2026 •

Copy link
Copy Markdown

Description

Implements #160 (rebased on main): the process configuration has full control over the UI launch form (ui_launch_mode: form). For the input field and each context key, it declares the form type (FQCN), its options (with the field constraints) and its default value. Example: the delimiter context value becomes a "Delimiter" select instead of two free text inputs.

options:
    ui:
        ui_launch_mode: form
        form:
            input:
                type: Symfony\Component\Form\Extension\Core\Type\FileType
                options:
                    label: CSV file
                    constraints:
                        - File: { extensions: { csv: ['text/csv', 'text/plain'] } }
            context:
                delimiter:
                    type: Symfony\Component\Form\Extension\Core\Type\ChoiceType
                    default: ','
                    options:
                        label: Delimiter
                        choices: { 'Comma (,)': ',', 'Semicolon (;)': ';' }
                        required: true
            allow_extra_context: false     # default true: free key/value pairs for the undeclared keys
            constraints: []                # constraints of the whole {input, context} data (replaces ui.constraints)

The data of the form keeps its shape ({input, context: {key => value}}), so the dispatched message is unchanged.

Changes

  • form UI option:
    • form.input / form.context.<key>: type (FQCN of a form type, not a button), options (with constraints, same syntax as the global constraints), default;
    • an EnumType default is an enum case (!php/enum);
    • the declared context fields are not required by default.
  • Free pairs: form.allow_extra_context (default true). A free pair whose key is a declared field is a validation error (reserved_keys option of ProcessContextType).
  • Values: the typed values are converted into process values after the validation (internal ContextValueNormalizer). A value that cannot be converted is reported as an error of its field.
  • Resolution:
    • the launch form is resolved by the internal LaunchFormOptionsResolver (ProcessConfigurationsManager::getLaunchFormOptions()), only on the launch page;
    • getUiOptions() keeps its 3.2 shape: the process list and the executions list do not depend on the launch form, and trigger no deprecation.
  • Checks when the container is built (internal LaunchFormsCacheWarmer, not optional, also run on the automatic rebuild in debug mode): each launch form is resolved, then built with the form factory of the application (form type options and type extensions, default values, constraint validators). An invalid launch form fails cache:clear with a message naming the process.
  • Deprecations (removal in 4.0): entrypoint_type → form.input.type, default → form.*.default, constraints → form.constraints.
    • They are folded into form, and mixing a deprecated option with its replacement is refused.
    • They are triggered when the launch form is displayed.
  • Documentation: reference (02-process_ui_options.md), cookbook (form_file_upload.md), upgrade guide, changelog.

Tests

PHPUnit (also with pcov and strict coverage metadata, as in the CI), PHPStan level 10, PHP-CS-Fixer, Rector, twig-cs-fixer: OK.

Breaking changes

None for a configuration that keeps working with the deprecated options (minor, 3.3). See UPGRADE.md:

  • the free context pairs of the launch form are renamed launch[context][extra][<index>], as context is now a compound FormType (form theme blocks _launch_context_extra_entry_*);
  • the duplicated default.context keys are merged (the last one wins).

🤖 Generated with Claude Code

@njoubert-cleverage njoubert-cleverage left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

  1. Scope. The PR bundles three changes that are not part of #160 and deserve their own issue/PR (and changelog entry):
    • execution_user always 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;
    • ProcessConfigurationsManager resolving only the requested option (ui / notification / logs), which changes how invalid notification / logs options are reported.
  2. Key format rule = BC break in a minor. Free keys such as api.token, a/b or a b used 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: ContextualOptionResolver builds its regex from the context keys without preg_quote() (that is what the zz)|x|(?:zz / a/b tests show). It should be fixed there; here the naming rule should only apply to the keys declared in form.context, which must be valid form field names anyway.
  3. Form type: FQCN only. FORM_TYPE_ALIASES introduces 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 requires symfony/doctrine-bridge only for the entity alias. I'd remove it and accept FQCNs only, as the Symfony form builder does and as in the draft of #160: resolveFormType() then only checks class_exists / FormTypeInterface / not a button, and symfony/doctrine-bridge can be dropped from composer.json (docs, changelog and tests to update). This also solves the entity issue: 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 existing ConstraintLoader syntax. 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

  1. Default values of object types: the docs cover date / dateinterval (input: string), but not enum (a YAML scalar where the field expects an enum case). Either refuse it at resolution or document it.
  2. 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.
  3. Process list: list.html.twig resolves the UI options of every process, so one invalid form configuration breaks the whole page, and every rendering triggers one deprecation per process using the deprecated options.

Minor

  • ProcessConfigurationsManager grows 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(), the ui_options option of LaunchType and ContextValueNormalizer (instantiated with new in the controller) should be @internal.
  • The declared context fields are required: true by default (Symfony default): a checkbox must then be checked (the dry_run example of #160 needs an explicit required: false). A false default 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

Comment thread src/Form/Type/ProcessContextType.php Outdated
// 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',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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).

@clever-age-gtonon clever-age-gtonon Oct 16, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reverted in eba7505: the free context pairs (launch form and schedules) accept any non-empty key again, as on main. The naming rule only applies to the keys declared in form.context (form field names).

*
* @var array<string, class-string>
*/
private const FORM_TYPE_ALIASES = [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@clever-age-gtonon clever-age-gtonon Oct 16, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 399d67b: the alias table is removed, type is the FQCN of a form type only (class_exists, FormTypeInterface, not a button), the short names are refused with an explicit message.

'ulid' => Type\UlidType::class,
'collection' => Type\CollectionType::class,
'repeated' => Type\RepeatedType::class,
'entity' => EntityType::class,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@clever-age-gtonon clever-age-gtonon Oct 16, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Solved by 399d67b: no entity alias anymore. An EntityType can still be given by its FQCN; the docs describe the object data (must be \Stringable) and recommend a ChoiceType with scalar choices.

Comment thread composer.json Outdated
"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",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Only required for the entity alias: can be removed with FQCN-only types.

@clever-age-gtonon clever-age-gtonon Oct 16, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed in 399d67b.

*
* @return LaunchFormData
*/
public function getLaunchFormDefaults(string $processCode): array

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Only used by the tests: new public API without a caller. Could be removed (or @internal), same question for buildLaunchFormDefaults().

@clever-age-gtonon clever-age-gtonon Oct 16, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed in ef621a2: getLaunchFormDefaults() and buildLaunchFormDefaults() are gone, the initial data is part of the launch form resolved by the new internal getLaunchFormOptions() / LaunchFormOptionsResolver; the launch_form option of LaunchType is internal.

if (null === $recipients) {
return null;
$resolver->setDefaults(['notification' => [], 'logs' => [], 'ui' => []]);
foreach (['notification', 'logs', 'ui'] as $name) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@clever-age-gtonon clever-age-gtonon Oct 16, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reverted in f299867: the getters resolve the ui, notification and logs options together again, as on main. Moreover, since ef621a2 getUiOptions() no longer resolves form (see the review summary).

Comment thread src/Controller/Admin/Process/LaunchAction.php Outdated
Comment thread src/Form/ContextValueNormalizer.php Outdated
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)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

@clever-age-gtonon clever-age-gtonon Oct 16, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done: e63af6a reports a value that cannot be converted as an error of its field (input, declared field or free pairs), the form is displayed again and nothing is dispatched. ContextValueNormalizer is @internal (b66b4b4). Besides, the launch forms are now checked each time the container is built, by a cache warmer that resolves them and builds them with the form factory of the application: form type options, default values, constraint validators (eef9ada, 8883fe1).

clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
…er nor reserved (out of scope)

Out of the scope of #160 (review of #168): back to the behaviour of main.

- LaunchAction::dispatch(): the context is merged over execution_user again
  (array_merge), as before.
- Remove ProcessExecuteMessage::EXECUTION_USER_KEY,
  ProcessContextType::createExecutionUserConstraint() and the
  key_constraints option of ProcessContextType.
- execution_user is no longer reserved in the free context pairs of the
  launch form, in the schedules, nor in form.context ("extra" stays
  reserved: it is the child of the free pairs).
- Tests and documentation updated accordingly.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
…nd of the schedules (out of scope)

Out of the scope of #160 and a BC break in a minor (review of #168): keys
such as "api.token", "a/b" or "a b" are accepted again in the free context
pairs of the launch form and in the schedules, as on main.

- ProcessContextType: remove the Regex constraint of the key (NotBlank and
  the reserved_keys option of #160 are kept).
- The naming rule still applies to the keys declared in form.context, which
  are form field names.
- ProcessScheduleCrudTest and docs/reference/05-scheduler.md back to main;
  tests, reference documentation, upgrade guide and changelog updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
…tion and logs options together again (out of scope)

Out of the scope of #160 (review of #168): as on main, the getters resolve
the ui, notification and logs options in one pass, so invalid notification
or logs options are reported again by the process list.

- resolveOptions() resolves the three options again (notification and logs
  normalizers inlined as on main, the ui one delegating to the resolution
  of the launch form).
- Remove the tests of the independent resolutions.
- docs/reference/04-process_executions_and_logs.md and 09-notifications.md
  back to main; upgrade guide and changelog updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
…-bridge

Review of #168: one naming scheme for the form types, as the Symfony form
builder and the draft of #160.

- ProcessConfigurationsManager: remove the FORM_TYPE_ALIASES table; the
  default type is TextType::class; resolveFormType() only checks that the
  class exists, implements FormTypeInterface and is not a button type.
  The short names ("text", "choice"...) are refused with an explicit message.
- composer.json: symfony/doctrine-bridge is no longer required (it was only
  needed by the "entity" alias). An EntityType can still be given by its
  FQCN when the bridge is installed.
- Tests, reference documentation, cookbook, upgrade guide and changelog use
  FQCNs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
Review of #168: documented but not covered. A project-specific constraint
(tests/App/Validator/Even) given by its FQCN in form.context.<key>.options
.constraints:

- ProcessConfigurationsManagerTest: built by ConstraintLoader, with and
  without options;
- Functional/LaunchActionTest (new test.form_custom_constraint process): the
  violation is displayed under the field and nothing is dispatched, a valid
  value is dispatched.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
…aunch page (process and executions lists unaffected)

Review of #168: the process list resolved the UI options of every process,
so one invalid "form" option broke the whole page (as the executions list,
through the source/target), and each rendering triggered the deprecations of
every process using the deprecated options. ProcessConfigurationsManager also
grew to ~700 lines, mostly for the launch form.

- New internal LaunchFormOptionsResolver: resolves the "form" UI option,
  checks its conflicts with the deprecated "entrypoint_type", "default" and
  "constraints" options, folds them into the launch form, builds the
  initial data of the form, and triggers the deprecations (same messages).
- ProcessConfigurationsManager::getUiOptions() is back to its shape of main:
  "form" is accepted as is, not resolved; no deprecation is triggered.
- New internal ProcessConfigurationsManager::getLaunchFormOptions(), used by
  LaunchAction; LaunchType receives the resolved launch form through its
  internal "launch_form" option (was "ui_options").
- getLaunchFormDefaults() and buildLaunchFormDefaults() are removed: the
  initial data is part of the resolved launch form.
- Tests: the launch form tests move to LaunchFormOptionsResolverTest; the
  manager and Twig runtime tests are back to main (plus "form" not
  resolved); the process list triggers no deprecation.
- Reference documentation, upgrade guide and changelog updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
Review of #168: no new public API without a caller.

- ContextValueNormalizer (instantiated by LaunchAction) is @internal.
- The other launch form internals are already internal since the previous
  commit: LaunchFormOptionsResolver, getLaunchFormOptions() and the
  "launch_form" option of LaunchType; getLaunchFormDefaults() and
  buildLaunchFormDefaults() (only used by the tests) were removed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
Review of #168: the default value of an EnumType field written as a YAML
scalar cannot be transformed by the field, which expects an enum case.

- LaunchFormOptionsResolver: the default value of an EnumType field must be
  a case of the enum of options.class (a list of cases with "multiple"),
  otherwise an InvalidOptionsException suggests the "!php/enum" YAML tag.
- The "!php/enum" tag goes through the process bundle configuration and the
  container: functional test with a backed enum field (pre-selected default,
  value of the case given to the process).
- Reference documentation (default values of the fields) and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
Review of #168: an invalid launch form configuration was only reported when
the launch page was opened (500).

- New internal CheckLaunchFormsPass, registered by the bundle: for each
  process declaring the "form" UI option, the launch form is resolved (the
  deprecated options folded into it) when the container is compiled; an
  invalid launch form fails the compilation with a message naming the
  process (type, forbidden options, file fields, keys, enum default,
  constraints, conflicts with the deprecated options).
- Only the UI options are checked: the tasks and the other options of the
  process are not concerned. A process without "form" is not checked (no
  BC break), a launch form using an environment variable is left to the
  launch page, and the parameters are resolved. The deprecations stay
  triggered on the launch page only.
- The options of the form types themselves are still resolved by the form
  factory when the form is built, and the submitted values are converted
  after the submission.
- Tests: the pass and its registration; the tests booting the test kernel
  declare the classes used at compile time (strict coverage metadata).
- Reference documentation and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
…tead of a 500

Review of #168: a value that cannot be converted into a process value was
raised as a LogicException (500) after the user filled and submitted the
form.

- LaunchAction converts the input and the context before saving the
  uploaded file and dispatching; a value that cannot be converted is
  reported as an error of its field (the input, a declared context field,
  or the free context pairs), the form is displayed again and nothing is
  dispatched.
- The message of ContextValueNormalizer names a ChoiceType instead of the
  removed "choice" alias.
- Functional tests for the input and a context field; reference
  documentation and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
Review of #168: the declared context fields followed the Symfony default
(required: true), so a CheckboxType had to be checked (the dry_run example
of #160 needed an explicit required: false).

- LaunchType: the declared context fields are not required by default, an
  explicit "required" option of the field takes precedence (the input is
  still required when the process defines an entry_point).
- A select that is not required has an empty choice: the Delimiter examples
  (reference, cookbook, upgrade guide) set required: true.
- Tests, reference documentation and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon added a commit that referenced this pull request Oct 16, 2026
Review of #168: the entry was a single ~2000-character paragraph. It is split
into bullets, and only describes the scope of #160.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clever-age-gtonon and others added 17 commits October 16, 2026 12:58
…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 <[email protected]>
…er nor reserved (out of scope)

Out of the scope of #160 (review of #168): back to the behaviour of main.

- LaunchAction::dispatch(): the context is merged over execution_user again
  (array_merge), as before.
- Remove ProcessExecuteMessage::EXECUTION_USER_KEY,
  ProcessContextType::createExecutionUserConstraint() and the
  key_constraints option of ProcessContextType.
- execution_user is no longer reserved in the free context pairs of the
  launch form, in the schedules, nor in form.context ("extra" stays
  reserved: it is the child of the free pairs).
- Tests and documentation updated accordingly.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…nd of the schedules (out of scope)

Out of the scope of #160 and a BC break in a minor (review of #168): keys
such as "api.token", "a/b" or "a b" are accepted again in the free context
pairs of the launch form and in the schedules, as on main.

- ProcessContextType: remove the Regex constraint of the key (NotBlank and
  the reserved_keys option of #160 are kept).
- The naming rule still applies to the keys declared in form.context, which
  are form field names.
- ProcessScheduleCrudTest and docs/reference/05-scheduler.md back to main;
  tests, reference documentation, upgrade guide and changelog updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…tion and logs options together again (out of scope)

Out of the scope of #160 (review of #168): as on main, the getters resolve
the ui, notification and logs options in one pass, so invalid notification
or logs options are reported again by the process list.

- resolveOptions() resolves the three options again (notification and logs
  normalizers inlined as on main, the ui one delegating to the resolution
  of the launch form).
- Remove the tests of the independent resolutions.
- docs/reference/04-process_executions_and_logs.md and 09-notifications.md
  back to main; upgrade guide and changelog updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…-bridge

Review of #168: one naming scheme for the form types, as the Symfony form
builder and the draft of #160.

- ProcessConfigurationsManager: remove the FORM_TYPE_ALIASES table; the
  default type is TextType::class; resolveFormType() only checks that the
  class exists, implements FormTypeInterface and is not a button type.
  The short names ("text", "choice"...) are refused with an explicit message.
- composer.json: symfony/doctrine-bridge is no longer required (it was only
  needed by the "entity" alias). An EntityType can still be given by its
  FQCN when the bridge is installed.
- Tests, reference documentation, cookbook, upgrade guide and changelog use
  FQCNs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review of #168: documented but not covered. A project-specific constraint
(tests/App/Validator/Even) given by its FQCN in form.context.<key>.options
.constraints:

- ProcessConfigurationsManagerTest: built by ConstraintLoader, with and
  without options;
- Functional/LaunchActionTest (new test.form_custom_constraint process): the
  violation is displayed under the field and nothing is dispatched, a valid
  value is dispatched.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…aunch page (process and executions lists unaffected)

Review of #168: the process list resolved the UI options of every process,
so one invalid "form" option broke the whole page (as the executions list,
through the source/target), and each rendering triggered the deprecations of
every process using the deprecated options. ProcessConfigurationsManager also
grew to ~700 lines, mostly for the launch form.

- New internal LaunchFormOptionsResolver: resolves the "form" UI option,
  checks its conflicts with the deprecated "entrypoint_type", "default" and
  "constraints" options, folds them into the launch form, builds the
  initial data of the form, and triggers the deprecations (same messages).
- ProcessConfigurationsManager::getUiOptions() is back to its shape of main:
  "form" is accepted as is, not resolved; no deprecation is triggered.
- New internal ProcessConfigurationsManager::getLaunchFormOptions(), used by
  LaunchAction; LaunchType receives the resolved launch form through its
  internal "launch_form" option (was "ui_options").
- getLaunchFormDefaults() and buildLaunchFormDefaults() are removed: the
  initial data is part of the resolved launch form.
- Tests: the launch form tests move to LaunchFormOptionsResolverTest; the
  manager and Twig runtime tests are back to main (plus "form" not
  resolved); the process list triggers no deprecation.
- Reference documentation, upgrade guide and changelog updated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review of #168: no new public API without a caller.

- ContextValueNormalizer (instantiated by LaunchAction) is @internal.
- The other launch form internals are already internal since the previous
  commit: LaunchFormOptionsResolver, getLaunchFormOptions() and the
  "launch_form" option of LaunchType; getLaunchFormDefaults() and
  buildLaunchFormDefaults() (only used by the tests) were removed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review of #168: the default value of an EnumType field written as a YAML
scalar cannot be transformed by the field, which expects an enum case.

- LaunchFormOptionsResolver: the default value of an EnumType field must be
  a case of the enum of options.class (a list of cases with "multiple"),
  otherwise an InvalidOptionsException suggests the "!php/enum" YAML tag.
- The "!php/enum" tag goes through the process bundle configuration and the
  container: functional test with a backed enum field (pre-selected default,
  value of the case given to the process).
- Reference documentation (default values of the fields) and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review of #168: an invalid launch form configuration was only reported when
the launch page was opened (500).

- New internal CheckLaunchFormsPass, registered by the bundle: for each
  process declaring the "form" UI option, the launch form is resolved (the
  deprecated options folded into it) when the container is compiled; an
  invalid launch form fails the compilation with a message naming the
  process (type, forbidden options, file fields, keys, enum default,
  constraints, conflicts with the deprecated options).
- Only the UI options are checked: the tasks and the other options of the
  process are not concerned. A process without "form" is not checked (no
  BC break), a launch form using an environment variable is left to the
  launch page, and the parameters are resolved. The deprecations stay
  triggered on the launch page only.
- The options of the form types themselves are still resolved by the form
  factory when the form is built, and the submitted values are converted
  after the submission.
- Tests: the pass and its registration; the tests booting the test kernel
  declare the classes used at compile time (strict coverage metadata).
- Reference documentation and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…tead of a 500

Review of #168: a value that cannot be converted into a process value was
raised as a LogicException (500) after the user filled and submitted the
form.

- LaunchAction converts the input and the context before saving the
  uploaded file and dispatching; a value that cannot be converted is
  reported as an error of its field (the input, a declared context field,
  or the free context pairs), the form is displayed again and nothing is
  dispatched.
- The message of ContextValueNormalizer names a ChoiceType instead of the
  removed "choice" alias.
- Functional tests for the input and a context field; reference
  documentation and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…tainer is built

The compiler pass only checks the structure of the launch forms: the
services (form types, type extensions, constraint validators) do not exist
yet when the container is compiled. The options of the form types, the
default values and the constraint validators were still only checked on the
launch page (500).

- New internal LaunchFormsCacheWarmer, not optional: Symfony runs it after
  each build of the container (cache:clear, cache:warmup, and the automatic
  rebuild in debug mode). For each process declaring the "form" UI option,
  it builds the launch form with the form factory of the application (form
  type options and type extensions, "constraints" option), sets its default
  values (transformed by the fields), and resolves the validator of each
  constraint, nested constraints included (Collection, All...). An invalid
  launch form fails with a message naming the process.
- Registered without explicit tag nor arguments: config/services/
  cache_warmer.yaml loads src/CacheWarmer/ with autowire and autoconfigure
  (kernel.cache_warmer tag from FrameworkBundle), #[Autowire] for the
  process configurations manager and the (optional) validator factory.
- Tests: the warmer (valid forms, unknown or invalid form type options,
  default value that cannot be transformed, missing validators, without
  validator), its run with the container of the test application; the tests
  booting the test kernel declare the classes used by the warmer.
- Reference documentation and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review of #168: the declared context fields followed the Symfony default
(required: true), so a CheckboxType had to be checked (the dry_run example
of #160 needed an explicit required: false).

- LaunchType: the declared context fields are not required by default, an
  explicit "required" option of the field takes precedence (the input is
  still required when the process defines an entry_point).
- A select that is not required has an empty choice: the Delimiter examples
  (reference, cookbook, upgrade guide) set required: true.
- Tests, reference documentation and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review of #168: the entry was a single ~2000-character paragraph. It is split
into bullets, and only describes the scope of #160.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…aunchFormsCacheWarmer

LaunchFormsCacheWarmer runs after each build of the container and resolves
each launch form (same structural checks as the compiler pass: types, keys,
file fields, enum defaults, constraints, conflicts with the deprecated
options) before building it with the form factory of the application: the
compiler pass is redundant.

- Remove CheckLaunchFormsPass, its registration and its tests, and the
  metadata of the tests booting the test kernel.
- LaunchFormsCacheWarmer docblock, reference documentation and changelog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@@ -0,0 +1,5 @@
services:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

According to symfony best pratices for bundles, services should not use autowiring or autoconfiguration. Instead, all services should be defined explicitly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants