Closed (fixed)
Project:
Display Builder
Version:
1.0.x-dev
Component:
Code
Priority:
Normal
Category:
Bug report
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
11 Dec 2025 at 19:35 UTC
Updated:
11 Nov 2026 at 07:47 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
mogtofu33 commentedComment #3
pdureau commentedComment #4
pdureau commentedMaybe we need to implement ConfigEntityInterface::onDependencyRemoval():
Comment #5
pdureau commentedComment #6
pdureau commentedComment #9
prince.wangComment #10
pdureau commentedThanks, i will have a look
Comment #11
pdureau commentedHello,
Thanks for your proposal and sorry for my late answer (i was in a Drupal event in Greece).
So, first what I understand. You have used
ConfigEntityInterface::onDependencyRemoval()on 2 config entities:EntityViewDisplayconfig entityPageLayoutconfig entityThere, you call the newly added
DisplayBuildableInterface::removeDependency()method, implemented inDisplayBuildablePluginBase. That's nice to centralize the logic in the plugin like that.Also, I believe it was a good decision to add a
::removeDependency()method instead of implementing RemovableDependentPluginInterface::onCollectionDependencyRemoval() because the "buildable" plugins are not attached toPageLayoutandEntityViewdisplays as "plugin collections".Feedbacks:
src/SourceTree.php.SourceTreehas been created to isolate the tree alteration logic from the Drupal logic, it must stay very agnostic of what is happening CMS side. Can we do differently? (i am asking but i honestly have no clue)removeDependency()method has been implemented. How is it triggered?Comment #12
prince.wangHi pdureau ,
Thanks for your review:
1. DisplayBuilderArea (area plugin) — A Views area handler (#[ViewsArea("display_builder_area")]) implementing DependentWithRemovalPluginInterface. It renders nothing; it's a sentinel that lives in the View's header section only when Display Builder is enabled for that display.
2. display_builder_views.views.inc — hook_views_data() registers the area handler in $data['views']['display_builder_area'] so ViewsHandlerManager::getHandler() can find it. Without this, Views falls back to a broken handler.
3. DisplayExtender::submitOptionsForm() — When a user enables Display Builder on a display, it calls addDisplayBuilderArea(); when disabling, removeDisplayBuilderArea().
4. addDisplayBuilderArea() — Calls $this->displayHandler->setOverride('header', FALSE) to mark the header as overridden (otherwise getOption('header') inherits from the default display and ignores our area). Then adds the area handler config via setOption('header', ...).
5. removeDisplayBuilderArea() — Checks isDefaulted() per section first — skips if inherited from default. Otherwise removes the area from header/footer/empty.
6. Dependency handling via the area plugin:
- calculateDependencies() → Gets ViewDisplay buildable from $this->displayHandler->getExtenders(), iterates getSources(), calls SourcePluginManager::getSource() on each, merges source dependencies into the View.
- onDependencyRemoval() → Calls ViewDisplay::removeDependency() which runs SourceTree::onDependencyRemoval() to prune orphaned source nodes.
Comment #13
pdureau commentedSo is it back to review?
Comment #14
pdureau commentedComment #15
prince.wangCan't find a good way to handle views dependencies.
Comment #16
andres alvarez commentedMR !269 is 128 commits behind 1.0.x and no longer merges: it conflicts in
ViewDisplay.php, since the sources storage moved toConfigBuildablePluginBaseandsaveSources()is gone from that class.I rebased it locally.
ViewDisplay::removeDependency()now usessaveDefaultSources()instead of writing to the view storage by hand. The existingSourceTree,EntityViewDisplay,PageLayoutandDisplayExtenderkernel tests pass. The original MR did not ship tests of its own, so I will add them (entity view, page layout and views, plus a node with children in slots).To avoid rewriting @ipumpkin's history I will push it to a separate branch of the fork (
3562750-rebase) unless you would rather have a force push on the MR branch.Comment #18
andres alvarez commentedUpdate on MR !462 (the rebase of !269 on 1.0.x, on a separate branch to keep @ipumpkin's history intact). Kernel tests are in for
SourceTree, entity view, page layout and views, including a real theme uninstall and a field deletion. They exposed three flaws in the original approach, now fixed:calculateDependencies()read stale stored sources, so deleting a field still deleted the display; source plugins were created without contexts, which is a fatal error withentity_fieldsources; andgetPluginRemovedDependencies()read an unchecked array key.Checked on a real site by uninstalling a module that provides a component used by an entity view display: on 1.0.x the display is deleted; with this MR it is kept and only loses the dependent source. php-cs-fixer style is applied and the pipeline is green.
Still open: Views.
Viewhas noonDependencyRemoval()here, so uninstalling something does not prune a view's sources; onlyViewDisplay::removeDependency()is covered, by a direct call. @pdureau, one design question: when a node with a broken dependency is removed, its slot children go with it even if they are independent. Should they be kept?Comment #19
andres alvarez commentedComment #20
mogtofu33 commentedComment #21
mogtofu33 commentedThanks @andres-alvarez, the tests make this reviewable. !462 rebases cleanly on 1.0.x after #3627285: Profile, page layout, override and display interfaces: setters return static, revert() split, docblocks and the four kernel test classes pass.
1. Translation leak.
DisplayBuildablePluginBase::removeDependency()starts from$this->getSources(). For config buildables that isConfigBuildablePluginBase::getSources(), which merges the language override of the URL language. Uninstalling a theme or deleting a field from a /de/ admin page writes the German values into the default config. Reproduced in a kernel test, both with a directonDependencyRemoval()call and throughConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().Prune the sources the entity already holds in memory instead. That is also what gets saved:
Same in
EntityViewDisplayTrait, fromgetThirdPartySetting('display_builder', DisplayBuildableInterface::SOURCES_PROPERTY, []), ascalculateDependencies()already does in this MR.2. With that,
DisplayBuildableInterface::removeDependency()has no caller left. Please drop it from the interface, the base class andViewDisplay. It would be a new public contract right before RC1, and its meaning differs per implementation: the base returns sources,ViewDisplaysaves them.3. Views. Please drop
ViewDisplay::removeDependency()and its test from this MR. Nothing calls it. The View does not depend on the component providers either: the display extender declares no dependencies, so an uninstall leaves the View in place with broken sources. Saving inside a dependency removal would also write config during the dry run of the uninstall confirm form. Views needs its own follow-up.4. Draft. Only the published sources are pruned. The instance draft keeps the removed node, so the builder still shows it and the next publish writes it back. This can't happen in
onDependencyRemoval()because of the dry run, so it is a follow-up too.5. Children in slots (#18): keep the current behavior. Core does the same in
LayoutBuilderEntityViewDisplay::onDependencyRemoval(), which drops a whole section with its components when the layout plugin loses its dependency. A line in the docblock is enough.Minor:
SourceTree::onDependencyRemoval():$changedcan go,return $node_ids_to_remove !== [];getPluginRemovedDependencies()copiesEntityDisplayBase::getPluginRemovedDependencies(), add a@see.removed()test helper is copied in four test classes.Comment #22
andres alvarez commentedComment #23
andres alvarez commentedI applied the review from Oct 5 (commit 2f01763) and fixed the PHPStan errors in 0c6284a.
Changes from the review
1. Translation leak: PageLayout::onDependencyRemoval() and EntityViewDisplayTrait::onDependencyRemoval() now prune a SourceTree built from the entity's in-memory sources instead of going through the buildable. The URL language override can no longer end up in the default config. I added testDependencyRemovalInGermanKeepsDefaultConfigUntranslated to PageLayoutMultilingualPublishTest and EntityViewMultilingualPublishTest. It fails on the previous code ('Hallo' instead of 'Hello') and passes now.
2. Removed DisplayBuildableInterface::removeDependency() and its implementation in DisplayBuildablePluginBase.
3. Removed ViewDisplay::removeDependency() and its tests. Views is left for a follow-up.
4. Draft sources are still not pruned (follow-up).
5. Slot children: behavior is unchanged and now documented in the SourceTree::onDependencyRemoval() docblock.
6. Cleanups: simpler return in SourceTree, added @see to getPluginRemovedDependencies(), and the removed() helper now lives in a shared trait (DependencyRemovalTrait).
Pipeline
- The 5 PHPStan errors were pre-existing, in code this MR doesn't touch (assign.overwritten and assign.unused). I fixed them in a separate commit (0c6284a) by removing the dead assignments, with no behavior change. If you prefer to keep them out of this MR, that commit can be dropped without affecting the rest.
- phpunit still fails on 9 Views tests (ViewsSourceOptionsTest and ViewsSourceRenderTest). They were failing before this MR and are unrelated to it. No SourceTree, entity_view or page_layout test fails.
Note: LayoutSource::calculateDependencies() had a line that looked meant to record the layout_discovery dependency but never reached $dependencies. I removed the dead line without changing behavior. If the intent was to emit that dependency, it should go in a separate issue.
Comment #24
andres alvarez commentedComment #25
andres alvarez commentedComment #26
pdureau commentedThanks Andres for the update.
However, I am not able to do a proper review for 2 reasons:
Drupal\Tests\display_builder_views\Kernel\ViewsSourceOptions, can you check them locally?Comment #27
andres alvarez commentedOops, sorry—I didn't check the PHPUnit code. I'll fix it right away.
Comment #28
andres alvarez commentedComment #29
andres alvarez commentedComment #30
andres alvarez commentedComment #31
pdureau commentedThanks. I guess this review ill be easier than the previous ones because the changes are now located in only 3 places:
Comment #32
csakiistvanComment #33
csakiistvan✅ Tested and works — on 1.0.x, a Basic page view display whose Display Builder sources used a string field was listed under "Configuration deletions" on the field's delete confirm page; after applying MR !462 and rebuilding caches, the same page lists it only under "Configuration updates", and deleting the field kept the display with only the textfield source left in its stored sources. Field deletion no longer deletes the display.
Comment #34
pdureau commentedComment #36
mogtofu33 commentedRebased with #3594228: Remove explicit references to Layout Builder, which removed the entity class replacement.
Entity view displays no longer depend on their sources, so a field deletion already keeps them, and this MR now covers page layouts only.
A pruned page layout also gets disabled: it loses its theme dependency, and would otherwise show on every theme.
Follow-up for small gaps:
Comment #38
pdureau commentedShipped in beta10 release.