Problem/Motivation
Contrib modules extend Display Builder: they add islands, derive existing ones, add buildables for new integrations, add sources, and change instances from their own code. display_builder_ai already adds an island and changes instances. Four families of interfaces carry that contract:
- Islands:
IslandInterfaceandIslandEventSubscriberInterface, withIslandWithFormInterface,IslandConfigurationFormInterface,RenderableAltererInterfaceandThirdPartySettingsInterface. Extended throughIslandPluginBaseand its traits. - Buildables:
DisplayBuildableInterfaceandDisplayBuildableOverrideInterface. Extended throughDisplayBuildablePluginBase, orConfigBuildablePluginBasefor a buildable storing its sources in a config entity. - Instances:
InstanceInterface, theHistoryInterfaceandPublishableInterfaceit extends, theProfileInterfaceandPublicationStatusthey return. - Sources:
SourceWithSlotsInterface,SourceProcessingDataInterfaceandEmptyPlaceholderHelpInterface, implemented by the source plugins that take part in the builder.
Three hooks are part of the same contract: hook_island_info_alter(), hook_display_buildable_info_alter(), and hook_display_builder_page_displays() with its PageDisplays. So are the DisplayBuilderEvents.
Once RC1 freezes the API, every flaw below becomes a BC break to fix. Nothing marks what is API and what is internal, and the instance contract is not consistent.
Each finding with a child issue is detailed there. The ones without one yet:
DisplayBuildableInterface
- 29 methods of its own, 4 of them static:
checkAccess(),checkInstanceId(),getUrlFromInstanceId(),getDisplayUrlFromInstanceId(). #3616313: Check static methods on DisplayBuildableInterface owns them, and #3573905: Simplify DisplayBuildableInterface ownscheckAccess()asAccessibleInterface::access(). They exist because the instance ID carries meaning, and that meaning leaks:display_builder_aiparses instance ID prefixes by hand, gets the override one wrong,entity_view_override__forentity_override__, and does not knowpattern_preset__. - The prefix is read from the attribute by reflection in
getPrefix(), and from the altered definition everywhere else, sohook_display_buildable_info_alter()cannot change it consistently. - Nothing public leads from a display entity to its buildable.
display_builder_aiguesses the plugin ID from the entity type and callsgetInstanceId()andinitInstanceIfMissing()behindmethod_exists(). Per #3573905-30: Simplify DisplayBuildableInterface,initInstanceIfMissing()stays, andgetInstanceId()goes with #3616313: Check static methods on DisplayBuildableInterface, whose summary does not list it yet. Removing it would switch that code off without an error. buildInstanceForm(bool $mandatory, ?TranslatableMarkup $title, bool $link)grows one boolean flag per need, and its three callers pass them by position.getTranslationLanguages($include_default = TRUE)is an untyped flag with no default inDisplayBuildablePluginBase: since beta8, a contrib buildable extending that base fails to load.isTranslationSynchronized()has no caller, and its docblock names adatafield that issources.DisplayBuildablePluginManageris final and has no interface. The islands manager has one.- No page of the docs explains how to write a buildable.
Array shapes
getNode(),getSources(),getPathIndex(),getPast()andgetFuture()return arrays of undocumented shape, and so do the$dataand$third_party_settingsarguments.getUsers()describes its keys in prose only.DisplayBuilderEventdocumentsstringfor three getters returning?string, and two of its getters dereference a nullable profile.
Proposed resolution
One child issue per numbered item, in this order. Rules for all of them:
- A new public method needs a known caller: a submodule or a known contrib module doing it by hand today. Adding one after RC1 is BC-safe, removing one is not. Callers may walk
getSources(), whose node shape item 8 documents. - Break before RC1, with a change record, where a deprecation cannot express the change: a return type, a thrown exception. Deprecate everywhere else,
@deprecated in display_builder:X.Y.Z and is removed from display_builder:X+1.0.0plus@trigger_error(). - Queries:
has*()andis*()answerFALSEfor an unknown node.get*()returnsNULLwhen the value is absent. WhereNULLalready means something, asgetParentId()for a root node, an unknown node throws. - Mutators throw
InvalidNodeExceptionfor an unknown or invalid node and\OutOfRangeExceptionfor a full root or slot, both in@throws, and returnstaticunless they create something. - No new static method on a contract, no new boolean flag, every array with an
array{...}shape, one name per concept. - Renaming a method parameter is free: named arguments are not API. Renaming a constructor parameter of an attribute or a value object is not, since callers use them by name:
#[Island]renameddefault_regiontoregionin beta7. - A new method on an extended contract ships with a default in its base class.
- #3627027: InstanceInterface: list the nodes of a slot
- #3627028: InstanceInterface: one failure style for the tree methods
- #3627285: Profile, page layout, override and display interfaces: setters return static, revert() split, docblocks and #3627284: InstanceInterface: setSources() instead of setNewPresent(), one save per change, the hashes
- Dropped:
InstanceStorageInterface, no caller outside Display Builder. - #3627256: IslandInterface: name the instance $instance, and settle the island API before RC1
- #3627260: Island forms: one pattern, through PluginWithFormsInterface, with one build info
- DisplayBuildableInterface. Replace the
buildInstanceForm()flags with a documented$optionsarray. GivegetTranslationLanguages()a default in the base class and a typed flag, and dropisTranslationSynchronized()unless something calls it. GiveDisplayBuildablePluginManageran interface, where #3616313: Check static methods on DisplayBuildableInterface adds its resolver, and read the prefix from the definition. Document how to write a buildable, on either base class. The statics stay with #3616313: Check static methods on DisplayBuildableInterface and #3573905: Simplify DisplayBuildableInterface. - Array shapes. Document every array left in the contracts, the node shape once, the island
build()arguments and configuration keys included. Fix the event docblocks. After 1 to 7, so nothing is documented twice. - #3627266: Mark the API boundary: @api on what contrib extends, @internal on the rest
Also before RC1: #3627272: Rename the island alter hook and the service IDs before RC1, #3627279: Relay every change of an instance to open builders, not only the builder's own.
Fixed on the way: #3626658: An instance translation can save the default language's tree over its own, #3626659: Paste, duplicate and preset insert end in a server error when the target is full or gone, #3626660: Inserting a preset makes one undo step per island setting, #3626661: Reading the group of a pattern preset saves the preset. Landed since the first summary: #3623326: Consistent display lists: operations, publication state, columns, sorting, #3555110: Symmetric translation, #3617547: Make pattern presets buildable.
Follow-ups, separate issues, fine after RC1:
ProfilewithEntityWithPluginCollectionInterface, so profiles depend on the modules providing their islands.PageLayoutalready does it for its conditions.ContextProviderInterface:InstanceInterfaceandDisplayBuildableInterfaceextend it, are registered as no provider, and ignore$unqualified_context_ids. Document the deviation, or replace it with a plaingetContexts().IslandType: instance methods instead of statics taking a string, andIslandInterface::getType()returning the enum.
Remaining tasks
- Open the child issues for items 7 and 8.
- Close #3621008: Move DisplayBuildable form logic to a dedicated class, per #3621008-7: Move DisplayBuildable form logic to a dedicated class.
- Raise on #3616313: Check static methods on DisplayBuildableInterface: the buildable loaded through the
buildablefield, the prefix read from the definition, and the removal ofgetInstanceId().
User interface changes
None.
API changes
In each child issue. Item 7 deprecates the buildInstanceForm() flags and gives DisplayBuildablePluginManager an interface.
Data model changes
None.
Comments
Comment #2
mogtofu33 commentedComment #3
mogtofu33 commentedUpdated the summary after a second pass and an adversarial last pass. Every claim was checked against 1.0.x and against the translation branch, and the paste, preset, cardinality and
getParentId()problems were reproduced in kernel tests.Main changes:
HistoryInterface,PublishableInterface,ProfileInterface, the island companion interfaces, the source interfaces, the alter hooks and the events.DisplayBuildableInterfacealready belong to #3616313: Check static methods on DisplayBuildableInterface and #3573905: Simplify DisplayBuildableInterface. This issue keeps only the rule.getParentId()answersNULLfor a missing node, undo and redo live on a storage class with no interface, the in-builder island forms receive different build info on render and on submit, API-driven changes skip the events and the SSE stream, and renaming an attribute parameter is a BC break, asdefault_regionshowed in beta7.Turning this into a plan with nine child issues, plus three bug issues for what the tests found. The order and the rules are in the summary.
Comment #4
mogtofu33 commentedComment #5
mogtofu33 commentedNo code but in review just for a global agreement or not on this plan and decide if it is sound.
Comment #6
mogtofu33 commentedComment #7
mogtofu33 commentedComment #8
mogtofu33 commentedComment #9
mogtofu33 commentedComment #10
mogtofu33 commentedComment #11
mogtofu33 commentedComment #12
mogtofu33 commentedComment #13
mogtofu33 commentedComment #14
mogtofu33 commented