Problem/Motivation

While working on #3555110: Symmetric translation, we reached level 62 of PHPMD's ExcessiveClassComplexity, whcih is more than:

See: https://phpmd.org/rules/codesize.html

Proposed resolution

By moving form logic to a dedicated PluginFormInterface implementation, we can go back to level 47.

This may also be the opportunity to move EntityViewDisplayFormTrait::buildOverridesForm() to this PluginForm, or another PluginFormInterface implementation, to make #3594228: Remove explicit references to Layout Builder easier.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

pdureau created an issue. See original summary.

pdureau’s picture

Title: Move DisplayBuildable form logic to a dedicated clas » Move DisplayBuildable form logic to a dedicated class

pdureau’s picture

Assigned: pdureau » mogtofu33
Status: Active » Needs review
Issue tags: +AI-accelerated

My first AI-accelerated MR :)

What do you think about isolating the logic there? It make sense in my humble opinion, but i am not familiar with PluginWithFormsInterface so I am not 100% sure.

We are not using PluginFormInterface::submitConfigurationForm() but this is opening opportunities we can use in #3594228: Remove explicit references to Layout Builder for example.

mogtofu33’s picture

mogtofu33’s picture

Assigned: mogtofu33 » pdureau
Status: Needs review » Needs work

Reviewed MR !381 with contrib in mind: DisplayBuildableInterface now extends PluginWithFormsInterface, so the form becomes an extension point other modules will rely on. Worth getting right before beta8 freezes it.

  1. getFormClass() ignores its argument. It returns DisplayBuildableForm::class for any operation and never reads the plugin definition, so a contrib buildable has to override the method to change the form. The docblock says PluginWithFormsTrait cannot work because definitions are attribute objects, but they are arrays: DisplayBuildable extends AttributeBase, whose get() returns an array, and DisplayBuildablePluginBase::label() already reads $definition['label']. Adding a forms property to #[DisplayBuildable], with DisplayBuildableForm as the default, lets the core trait do the job, and lets hook_display_buildable_info_alter() swap the form without a subclass.
  2. The operation is an empty string. createInstance($this, '') works, but a named operation is what forms keys on and what a contrib module would target. The class docblock example already uses DisplayBuildableInterface::INSTANCE_FORM, which does not exist.
  3. The assert() limits the swap to subclasses. buildInstanceForm() asserts $form_object instanceof DisplayBuildableForm, then calls setMandatory(), setTitle(), setLink(). Any form class that does not extend DisplayBuildableForm fails there, or on the setter call when assertions are off.
  4. The boolean flags are duplicated instead of going away. buildInstanceForm(bool $mandatory, ?TranslatableMarkup $title, bool $link) keeps its signature, and the three flags also become three setters on the concrete form, documented in its class docblock as the way to use it. EntityViewDisplayFormTrait already does: it repeats the factory call through \Drupal::service(), the assert() and the setters, where $buildable->buildInstanceForm(FALSE, $title, FALSE) gives the same result. #3624396: Plan: Harden InstanceInterface, IslandInterface and DisplayBuildableInterface as public API before RC1 replaces the flags with a documented $options array across the public interfaces; fine to leave that part to it, as long as this MR does not make the setters the contract.
  5. The extension point is build-only. The form is built against a detached new FormState(), validateConfigurationForm() never runs, is empty, and the host form readsswap the form without a subclass.
  6. The operation is an empty string. createInstance($this, '') works, but a named operation is what forms keys on and what a contrib module would target. The class docblock example already uses DisplayBuildableInterface::INSTANCE_FORM, which does not exist.
  7. The assert() limits the swap touildInstanceForm() asserts $form_objectinstanceof DisplayBuildableForm, then calls setMandatory(), setTitle(), setLink(). Any form class
    that does not extend DisplayBuildableForm fcall when assertions are off.
  8. The boolean flags are duplicated instead of going away.
    buildInstanceForm(bool $mandatory, ?TranslatableMarkup
    $title, bool $link)

    keeps its signature, and the setters on the concrete form, documented in its classdocblock as the way to use it. EntityViewDisplayFormTrait already does: it repeats the factory call through \Drupal::service(), the assert() and the setters, where

    $buildable->buildInstanceForm(FALSE, $title,
    FALSE)

    gives the same result. #3624396: Plan: Harden InstanceInterface, IslandInterface and DisplayBuildableInterface as public API before RC1 replaces$options array across the public interfaces;fine to leave that part to it, as long as this MR does not make the setters the contract.

  9. The extension point is build-only. The form is built against a detached new FormState(), validateConfigurationForm() never runs, submitConfigurationForm() is empty, and the host form reads
    profile, override_status and t state. A replacement form can change the markup, but must keep those element names: they are the real contract, not the form class.
  10. Behavior change on the override sub-form.isAllowed() on the display buildable, so against the main profile. They now use isAllowed() on the override buildable, whose getProfile() returns the
    override profile. Arguably more correct, but a user alloe other now sees the opposite of before. Intended?
  11. No test changes. DisplayBuildablePluginTest only calls buildInstanceForm() with defaults. The
    override sub-form, getOverrideField(), and point 6 are uncovered.

BC, fine in beta but worth a release note: buildSek(), buildDisabledSelect() andisCurrentUserAllowedToAdministrate() leave DisplayBuildablePluginBase, buildOverridesForm() and its helpers leave EntityViewDisplayFormTrait, and DisplayBuildableOverrideInterface gains two methods.

Minor: DisplayBuildableForm is neither final nor @internal. If contrib is meant to extend it, then it is API and should be documented as such. If not, mark it.

Two ways to get this in:

Given point 5, I would take the second.

pdureau’s picture

Assigned: pdureau » mogtofu33
Status: Needs work » Postponed (maintainer needs more info)

Hi Jean,

I see there are many parameters to consider here.

If it is complicated, we can simply do nothing here. This proposal was only to avoid adding <violation rule="PHPMD\Rule\Design\WeightedMethodCount" file="/builds/project/display_builder/src/DisplayBuildablePluginBase.php"/>
in .phpmd.baseline.xml , which was done in #3555110: Symmetric translation

We can totally live with that and close this ticket if you are OK.