Problem/Motivation
In #2942661: Sections should have third-party settings, Section objects were given third-party settings so that contrib could put some data on Sections, presumably to modify how they work.
However, if a contrib module wanted to change how the section was rendered (for example, modifying the regions before they were passed to the layout, ala region styles), it wouldn't be able because there is no event or alter hook in Section::toRenderArray() in order to do so.
Also, it would make sense to have such an event, because in #2937799: Allow greater flexibility within SectionComponent::toRenderArray() a similar event is being called in SectionComponent::toRenderArray().
There's also another aspect which cannot be resolved without the ability of 3rd-party to hook-into the section render array building:
- Block module’s scope is to place blocks.
- Layout Builder module’s scope is to place blocks.
Block placements are decentralised. Each block placement is an config entity. Many modules can work together to place blocks on the same page. In other words a module providing a block placement, shouldn’t know if a different module provides also a placement on the same page or even on the same region.
Layout Builder placements are centralized. All the block placements are living in a single object (e.g. as 3rd-party settings of the entity view display). This means that the underlying object should know that the modules providing the block exists. As an effect, the object providing the block placements has hard dependencies on modules providing the blocks. Third-parties are not able to hook-in by themselves, they should be dependencies of the module that provides the object providing the placements.
Illustrating the issue
A landing page, assembling blocks, built with Layout Builder & Page Manager. Each block is provided by different modules, which are reusable units. This means that some projects might want not to use one or more of the modules that are providing the blocks. This not possible right now as the landing page config will have hard dependencies on the modules providing the blocks. This is possible with the Block module as each module is able to ship their block placements.
Proposed resolution
Call an event in Section::toRenderArray() allowing contrib modules to modify the $regions array before it's passed to $layout->build().
Remaining tasks
Review use of Drupal\Core\Cache\CacheableMetadata, see https://git.drupalcode.org/project/drupal/-/merge_requests/5866#note_814639
Review change record
User interface changes
None.
API changes
A new event - details to be determined.
Data model changes
None.
Release notes snippet
Todo.
| Comment | File | Size | Author |
|---|---|---|---|
| #66 | core-3062862-112.patch | 33.48 KB | graber |
| #59 | interdiff_58-59.txt | 1.96 KB | heddn |
| #59 | 3062862-59.patch | 33.15 KB | heddn |
Issue fork drupal-3062862
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
dsnopekHere's a patch to demonstrate what this could look like. I haven't actually tested this (manually or automatically), and I'd like to actually experiment with implementing region styles and other possible use-cases before really committing to and finishing the implementation.
One thing that changed once digging into this patch, is that I realized that the event that's (most likely) needed for region styles wouldn't operation on the render array, but the regions array before it's passed to the layout, which isn't technically a render array. Per the docs on
LayoutInterface::build()it's an array of render arrays keyed by region name, and specifically in the context of layout_builder Sections, it's actually an array of regions, where each region is an array of the render arrays for each of the components. This is important, because we need to know that that's what it is if we want to implement a region style that turns each component into a tab, or an item in an accordion, etc.Also, that's the alter event that happens before we ask the layout to do it's thing, but I could some contrib module wanting an alter event on the render array AFTER the layout does it's thing, so the patch includes an event for that as well.
Comment #3
yogeshmpawarChanging status to Needs Review & Triggering bots.
Comment #5
dsnopekUsing the patch here, I've started experimenting with implementing region styles in this sandbox:
https://www.drupal.org/sandbox/dsnopek/3059795
It has a couple examples, including an "Accordion" style which puts all the blocks in the region into a jQuery UI accordion. Already, this points to a weakness in the current patch here, because it's difficult to get the block title and to hide it from normal rendering.
The thought I had immediately is that the
LayoutBuilderEvents::SECTION_BUILD_REGIONS_ARRAYevent shouldn't just get an array of the block's render arrays, but that layout_builder should register it's own event subscriber which builds that. Then my contrib module can register an event subscriber that comes before layout_builder's one, and do it's own rendering, which would allow the region style to easily grab the block label, and then also change the block settings to hide the label before generating the render array.When I get a chance, I'll try that, and then post a new patch here if it works out well.
Comment #6
dsnopekHere's a new patch that makes the changes I discussed in #5:
SectionComponents::toRenderArray()is doing tooThis allowed my experimental implementation of region styles to deal with block titles easier, and made for a simpler and better accordion region style. (I'll be pushing my code to the sandbox for that in a couple minutes.)
This is probably ready for others to comment on, so setting status to "Needs review"
Comment #8
tim.plunkettI'm not sure I understand the purpose of this event. Maybe it'd be clearer with a test implementation.
Oh, this is backwards from what I expected. This code lives in the _region_ build event?
Why have two events at this point? Moving the core implementation to a subscriber itself allows someone to participate both before and after core. Couldn't a single event handle this?
Comment #9
dsnopek@tim.plunkett: Thanks for taking a look!
I have an example implementation in this sandbox module:
https://git.drupalcode.org/sandbox/dsnopek-3059795/blob/8.x-2.x/src/Even...
It implements region styles. If you enable the 'block_style_plugins_ng_examples' module, it includes an accordion region style that makes each block in the region be an item in the accordion.
To actually test it, once you put some blocks in a region in layout_builder, a "Configure region style" link will appear at the top of the region:
Which looks like this when rendered:
The 1st event operates on the regions array that is passed to the layout - this is used for my region styles implementation. The 2nd event operates on the render array that comes back from the layout. So, the layout is doing it's business between the two events.
I honestly don't currently have a use-case for the 2nd event. My thinking was just that some contrib might put something on the third-party settings of a Section, and want to use that to modify the fully finished render array (with the layout applied and everything). So, it was really added for "completeness" because without an event or hook after the layout does it's business, you wouldn't be able to alter the fully finished render array in a generic way (ie. regardless of how the Section is being used, be that to render an entity like core does, or to render a page_manager variant per #2960739: Create a layout builder variant).
Comment #10
tim.plunkettIf the second event also came with a
setRegions()method, your example code set an earlier priority than core and manipulate the event payload there.That might be attempting to streamline it too much, idk
Comment #11
dsnopekI don't know if exactly that would work. Currently, the patch has got these things happening in this order:
$regionsarray$build = $layout->build($regions)$buildrender arraySo, like, setting the
$regionsarray in event nr 2 doesn't matter, because the layout already took that, and converted it into the$buildrender array.I suppose if layout_builder (or my module's) event subscriber on event nr 1 also did the
$build = $layout->build($regions)part, then it'd be a matter of whether or not your event subscriber ran before or after it?That seems like it'd make for convoluted DX, though, it'd be like, "set your priority higher than 100 to mess with the $regions array, and lower than 100 to mess with the $build array - if you mix that up, your changes will have no effect."
Comment #13
nicxvan commentedI think I may be misunderstanding how this works or it is not working properly.
I've created an event subscriber called Section forms based on the example provided here: https://git.drupalcode.org/sandbox/dsnopek-3059795/-/blob/8.x-2.x/src/Ev...
My end goal is to take a value that is set on each section when it's been added and feed that value into a form so that each section has a form element button that I can use to perform `some` action.
My understanding based on this and the example is that I need a onBuildRegions event that I can then grab the section configuration then generate the form and output it.
Here is the simplified eventsubscriber I am working with, the log nor the exit ever get hit when visiting the page with the sections on it.
Comment #14
dsnopek@nicxvan One quick thing to double check: are you registering the event subscriber in your module's MODULE.services.yml file? Probably you are, but throwing that out there, just in case.
Also, do you have my 'block_style_plugins_ng' module installed? If so, it could be that its event subscriber is running first, and the
$event->stopPropagation()there is preventing yours from running. There can really only be one subscriber that takes over this event the way my sandbox module does (although, you could have some light weight subscribers that run before it and make some small modifications to the data or something).For what you're trying to do, I wonder if instead of subscribing to this event, you could use my 'block_style_plugins_ng' module to make a regions style plugin? From a region style plugin you can modify each block, like the 'Accordion' region style does in the examples.
Comment #17
erik seifert commentedSame problem here. I try to implement a pluggable system for region and section styles. But this is not possible at this time.
Comment #18
claudiu.cristeaI've closed #3101655: Layout section and component information accessible during render process as duplicate of this. This is older
Comment #19
claudiu.cristeaClosed #3210230: Block placements by 3rd-party in layout section as duplicate.
Comment #20
claudiu.cristea@tim.plunkett,
I see the value. I described in deep the need, in the #3210230: Block placements by 3rd-party in layout section description. Will update also here the IS to clarify.
This certainly needs a change notice.
Let's rename it to
SectionRenderArraySubscriberSymfony event dispatcher component is dropping this constant and uses the event class full qualified name as event ID. Let's follow that path. Event constants may create issues, see also #2825358: Event class constants for event names can cause fatal errors when a module is installed
We don't need this event at all. Let's follow the component similar approach. We only need to dispatch one event and provide a subscriber that builds the region array with a priority of 100. Then contrib is able to hook in before or after.
Let's type
$in_reviewto bool.As this is new code we can strict type the return.
Maybe
isInPreview()? And let's strict type the return to bool.Let's allow chaining, return
$this.Comment #21
claudiu.cristeaImproved IS.
Comment #22
claudiu.cristeaAssigning to fix the issues.
Comment #23
claudiu.cristeaFixing #8.2 & #20.
Still needs:
Comment #24
claudiu.cristeaFixing PHP CS.
Still needs:
Comment #25
claudiu.cristeaIt was a wrong patch.
Comment #27
claudiu.cristeaFixing PHPUnit tests. I think it's OK that the new subscribers to be able to provide cache metadata.
Comment #29
claudiu.cristeaDevelopment has been moved in drupal-3062862 fork, merge request !588.
Fixed the PHP CS failure from #27.
Still needs:
Comment #30
claudiu.cristeaAdded tests.
Still needs:
Comment #31
claudiu.cristeaAdded CR: https://www.drupal.org/node/3210520. Ready for review.
Comment #32
claudiu.cristeaComment #33
claudiu.cristeaComment #34
claudiu.cristeaThis looks more like a feature request.
Comment #36
claudiu.cristeaI think that 3rd-party should also be aware about the section's delta. But how to pass that?
Section::toRenderArray()doesn't own that info.Comment #40
segovia94 commentedSomewhere along the way we lost one of the subscribers that allowed altering the final render array. Now it is only possible to alter the section regions and not the section itself. For example, if I want to add an attribute to the section like a new css class there is no longer a way to do this. The current event is dispatched before
core/lib/Drupal/Core/Layout/LayoutDefault::build(), and that method strips out all data except previously declared regions. So trying to add an$regions['#attributes']['class'][] = 'my-custom-class'in the current event subscriber is useless since it is stripped out.Comment #41
dsnopekI think this goes back to #3062862-20: Third party should be allowed to alter the section render array when @claudiu.cristea started working on this and wrote (in point nr 3):
In #3062862-23: Third party should be allowed to alter the section render array the event that modifies the render array is essentially removed (although, this is a little confusing to follow on the interdiff, because there it looks like the region event is removed, and then the render array one is converted to be about the regions :-))
Without this second event, it's impossible to implement component and region styles like in my sandbox module (https://www.drupal.org/sandbox/dsnopek/3059795).(EDIT: see next comment!)However, @claudiu.cristea did a great job of modernizing this patch! So, probably best to try to add the missing event back in on top of the latest code here.
Comment #42
dsnopekI take that back! I thought my module was depending on the event that was removed, however, it turns out it depends on the other one. I just got the events all mixed up. :-)
So, I've rebased the issue fork, and ported my sandbox module to use the current code here.
@claudiu.cristea and @segovia94: Thanks for all the work you've put into this!
Comment #44
pbabin commentedIn speaking with @segovia94 we may have an edge case where we need the event that was removed. We're working on trying to apply styles to sections. In some cases the styles are applied; however, in others they are not applied correctly. Specifically where we have a sections within a section. Styles are applied correctly in layout builder but do not display correctly when the page is rendered in view mode.


Layout Builder view
Page view
Missing default classes from block style plugins and the gold class even though it is not visually shown in the screenshots.
Comment #45
segovia94 commented@pbabin I think I may have confused you. The problem you ran into is not related and is due to a workaround in the specific module created since this second event was removed from this issue. But yes, having the second event would fix the problem.
I can add back in the second event that was triggered after the
build(). The other option is to edit theDrupal\Core\Layout\LayoutDefault::build()to add into the render array any properties passed into it. That's the biggest problem causing the existing event not to be as useful since it currently can only affect individual regions and not the overall section properties.Also, I don't see a way to change the merge request to be against 9.4.x.
Comment #46
heddn@claudiu.cristea can you rebase the MR on 9.4 please? Or at least change the target of the MR? As you created it, I think you have to change it.
Comment #47
claudiu.cristea@heddn, done
Comment #49
ldavidsp commentedFixed for drupal 9.4.x
Comment #50
ldavidsp commentedFixed for drupal 9.4.x
Comment #51
heddnComment #53
ravi.shankar commentedFixing failed test of patch #50.
Comment #54
ldavidsp commentedFixed hunk for
LayoutBuilderEntityViewDisplay.phpto drupal 9.4.xComment #55
edysmpAdded the `SectionBuildRenderArrayEvent` event that will allow us to alter the section's build render array while we still have access to the section's third party settings. e.g use case, add configurable CSS classes to the section's container div.
Comment #56
ravi.shankar commentedFixed Drupal CS issues of patch #55.
Comment #58
heddnRe-roll for 10.1 and a 9.5 patch for applying on _those_ sites still.
Comment #59
heddnMake drupalci happy.
Comment #60
smustgrave commented+ @trigger_error('Calling LayoutBuilderContextTrait::getPopulatedContexts() without the $delta argument is deprecated in drupal:9.3.0 and the $delta argument will be required in drupal:10.0.0. See https://www.drupal.org/node/3210520', E_USER_DEPRECATED);1. This will have to be updated for 10.1 and 11
Not needed but would be useful to have a tests-only.patch
Comment #63
heddnI've rebased and pushed to
issue/3062862-layout-3rd. Still need to reply to #61.Comment #64
milos.kroulik commentedIt looks like it doesn't work without specifying event name for
SectionBuildRenderArrayEvent(at least in Drupal 10.2), such asI will update the MR shortly based on this.
Comment #65
edysmp#64: Event names are not required. You can use the event's class name when subscribing.
e.g
Comment #66
graber commented...
Comment #68
edysmprebased onto the main branch
Comment #73
graber commentedOk, all green and #60 addressed. Also hidden all previous branches so there is no confusion. Back to review.
Comment #74
graber commentedSince Lucas just reviewed and I addressed his feedback (deprecation msg update), moving this to RTBC.
Comment #75
godotislateSome comments on the MR.
Comment #76
graber commentedThank you, all addressed. We have 2 tests failing now but both look unrelated (both from Drupal\FunctionalTests\DefaultContent\ContentExportTest).
Moving back to RTBC.
@godotislate pls check changing your BubbleableMetadata to CacheableMetadata just in case - added a comment on the MR.
Comment #77
quietone commentedGlancing at the last changes I am not sure this should have been set back to RTBC. Typically a committer will explicitly say that is OK.
I also don't see any review of the change record and the last time it was updated was in 2022. So that needs a review as well. If nothing else the branch and version numbers needs an update.
Comment #78
quietone commentedComing back to be more explicit.
The change record needs work because, at least, the version and branch are incorrect. Also, a change record should lead with the change. and the two code blocks, if still relevant, should have some text to explain what they are for. For reference, https://www.drupal.org/community/contributor-guide/task/write-a-change-record-for-a-drupal-core-issue
There were quite a few questions and suggestions by @godotislate. They seem to be answered but this is not my area and the GitLab UI is not showing some of the changes made so I can't confidently resolve 'easy' threads. One of the suggestions was to use BubbleableMetadataach, which does have a reply but has not been reviewed. At least, that needs review before being passed to the committers.
I hope that helps.
Comment #79
nicxvan commentedI do wonder if this is a better fit for an alter hook.
We typically do alters for stuff like this and we'd be able to expose it for themes. An event subscriber won't work in themes.
Comment #81
graber commentedHere's a hook solution (MR 15723), also updated the change record. There may be test failures but I tested current main and unfortunately those were pre-existing so someone is breaking things on main.
Moving to review, if any minor issues (phpstan baseline sort for example), I'll update on the fly.
Comment #82
heddnre #79: We should consider though that
LayoutBuilderEvents::SECTION_COMPONENT_BUILD_RENDER_ARRAYis already a thing in almost the same space. Offering one via event subscriber and the other in hook alter seems confusing.Comment #83
graber commentedTrue but I agree a hook will fit better here, that’s why I did it. We can have a follow up to convert the other one to an event and I can handle it too, not a very difficult change.
Comment #84
nicxvan commentedYeah, I think this is something that themes will want to interact with, the right answer is likely a hook and deprecating those other events to hooks as well.