Problem/Motivation
After renaming a layout, or after switching to a branch where a given layout does not exist, any attempt to fix the layout configuration will fail.
Steps to reproduce
Create a custom layout.
Create a layout builder configuration using that custom layout.
Remove or rename the custom layout.
Attempt to edit the layout builder configuration form, OR try to use "drush cim" to revert to a configuration where this layout was not used.
Expected: Layout builder config can be repaired.
Actual: Error.
The "layout_xyz" plugin does not exist. Valid plugin IDs for Drupal\Core\Layout\LayoutPluginManager are: ...
Proposed resolution
Use try/catch in strategic places to recover from this.
Remaining tasks
See tags
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #42 | 3204271-42.diff | 12.72 KB | herved |
| #15 | interdiff-14-15.txt | 1.18 KB | seanb |
| #14 | 3204271-14.patch | 1.42 KB | seanb |
| #3 | D9-3204271-2-missing-layout-exception.patch | 1.65 KB | donquixote |
Issue fork drupal-3204271
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
Comment #2
donquixote commentedPlaces where we could insert try/catch:
core/modules/layout_builder/src/Section.php, function getDefaultRegion()
the exception occurs in
$this->layoutPluginManager()->getDefinition($this->getLayoutId()), if the layout is missing.core/modules/layout_builder/src/Section.php, function getLayout()
the exception occurs in
$this->layoutPluginManager()->createInstance($this->getLayoutId(), $this->layoutSettings), if the layout is missing.core/lib/Drupal/Core/Entity/EntityDisplayBase.php, function init()
the exception occurs in
$default_region = $this->getDefaultRegion(), if the layout for the first section is missing.Adding a try/catch is easy, but we need to define a sensible fallback behavior that works in all cases where the layout is used:
- in the view mode or layout builder admin form.
- in the front-end, when displaying the layout.
- when importing or exporting settings.
- other?
Comment #3
donquixote commentedProof of concept patch.
Perhaps there are other implications to this, we should be careful before we commit.
Comment #4
donquixote commentedBtw about the
@throwstag:PhpStorm will show an inspection warning "Redundant @throws tag".
I started a discussion here:
https://youtrack.jetbrains.com/issue/WI-59821
Comment #5
tim.plunkettI think this would be better handled directly in
LayoutBuilderEntityViewDisplay::getDefaultRegion()Or even
hasSection()...Layout Builder can break in a lot of different ways if you remove the plugin from your system while still using it (though that's true of most things in PHP? I don't know that we want to babysit this too much...)
Comment #6
donquixote commentedBut these functions cannot produce a reasonable fallback value, if the layout is missing.
We could return NULL or 'content', but this would just produce wrong behavior down the line.
I think it is fine to throw exceptions if something is broken, and no clean fallback behavior can be provided.
I only want to fix the case where we deploy a new version of a website with new layouts and new config, so that a "drush cim" does not break. The site itself can remain broken until the configuration is fixed.
Ideally the display object should be put into a "failed init" state, where rendering will fail, but overwriting the config still works.
In an ideal world, the raw config should be handled by a different object or layer than the rendering, so that the config could be updated even if the layout does not exist. But this would be a lot more work than this try/catch.
Comment #7
donquixote commentedTo clarify:
We should catch and handle exceptions in strategic places where we can provide a reasonable fallback behavior and logging / reporting capability.
A fallback behavior when trying to display a broken, missing or misconfigured layout could be to display nothing, or to display all the elements in a single region. And log the exception, of course.
When trying to edit broken layout configuration in the layout builder UI, a fallback behavior is to show all the form elements in a default state, and show a message: "The stored configuration is broken with the currently available layouts, and saving this form will wipe it."
When trying to overwrite broken config with drush cim, I think it is fine to simply wipe the broken config.
Comment #9
herved commentedPatch #3 works as expected for me and solves the config:import issue on deployment after removing a layout that is now unused.
I can't see any issues so far.
So +1 on this! Very useful, thanks.
Comment #10
tim.plunkettRegardless of implementation, this needs automated tests that follow the "steps to reproduce".
Comment #14
seanbJust ran into this. Attached patch might be a subtle approach to not break when a layout is removed. Thoughts?
Comment #15
seanbSorry, ran into an error while saving as well, I guess the checks need to be just a bit more.
Comment #16
seanbReroll for 10.1.x
Comment #17
smustgrave commentedThis issue is being reviewed by the kind folks in Slack, #need-reveiw-queue. We are working to keep the size of Needs Review queue [2700+ issues] to around 200, following Review a patch or merge require as a guide.
Moving back to NW for the test cases
Also tagging for IS update for the proposed solution and if any steps are different from D8 when this was reported.
Comment #18
quietone commentedRemoving 'needs review' tag.
Comment #20
alex.skrypnykThis is potentially related to https://www.drupal.org/project/drupal/issues/2860346
The definitions are cached during a theme install.
If a theme provides custom layouts and has configuration objects that use those layouts (like `entity_view_display`), the layouts plugin definitions will already be cached and newly installed layouts will not have a chance to be discovered during the process of the theme installation.
Adding a cache tag that depends on installed extensions resolve this issue.
In LayoutPluginManager:
$this->setCacheBackend($cache_backend, $type, ['config:core.extension']);Patch attached.
Comment #21
herved commentedFor me #20 doesn't work.
I have a theme that is already installed, which declares some layouts, and some configs using those layouts (in config sync).
When I change a layout machine name and attempt to clear cache and config import, it fails.
#16 and #3 are working but a clear cache is required it seems before config import.
Comment #22
boregar commented#20 worked for me on a fresh install with CivicTheme 1.6.2 on Drupal Core 10.2.3. Thanks!
Comment #23
kristen polI used patch #20 on Drupal 10.3.0 and CivicTheme 1.7.1 and the fatal error went away.
Comment #24
kristen polIt would be great if someone wanted to review the different approach in #20, update the issue summary, and add tests :)
Comment #28
anjaliprasannan commented@joshua1234511 The test is failing in the pipeline. I tried it in the local by following the test steps where I could not find a message as in the test case. Is the test written as per requirement? Should the test be rewrorked on?
Comment #29
joshua1234511Reverted the added test. Mock tests are complex to integrate into the layout build scenario for scenario-based execution. Manual testing is required for this fix.
Comment #30
smustgrave commentedGoing to disagree that we should have test coverage added but will leave in review.
Comment #31
danielvezaThis absolutely needs test coverage.
I haven't thoroughly looked into this issue yet, would it work to have a test module that provides a layout, add that layout to a page, then uninstall that module and verify everything still works as expected? If I get a chance I'll look into this.
Comment #32
smustgrave commentedFor the tests
Comment #33
danielvezaI've added test coverage, and also fixed issues where this wasn't working on the default layout or on nodes that did not have overridden layouts (Yay for tests)
Lets see how the pipeline goes here. I'm a little iffy of how many places we are calling
::hasLayout.I wonder if we can put that in a central place instead.Comment #34
danielvezaTest coverage added and tests are green. I'd be interested to get a second set up eyes on this one. Mainly around this comment.
Comment #35
solideogloria commentedI get this error when installing the new Navigation module. (I'm not using any patches for this issue, just commenting that this is a way the issue occurs.) The error occurs for every single route I visit.
This issue is prohibiting me from converting my site to the new module. I'm currently on Drupal 10.4
Comment #36
solideogloria commentedI applied the MR diff as a patch to my Drupal 10.4 site, and I still get the error.
Comment #37
smustgrave commentedPer #36
Comment #38
solideogloria commentedLet me know if you need me to provide a stack trace or anything else.
Comment #40
grimreaperHi,
I made an addition in getLayoutSettings to avoid it to fail if the layout plugin does not exist.
This is helpful in case of update like for UI Patterns 1 to UI Patterns 2 upgrade.
Attaching patch for Composer usage.
Comment #42
herved commentedMR rebased, attaching current snapshot.
Some performance tests are failing and affected by the added cache tag in LayoutPluginManager.
I tried to reproduce the issue mentioned in #20 and #35 but without success.
Could you please provide steps to reproduce?
I tried this in LayoutBuilderMissingLayoutTest, but it passes without the added cache tag.