Problem/Motivation

The layouts patch (#1787846: Themes should declare their layouts), as it was committed, creates configuration objects that were never intended to be anything more than "layout configuration" - that is, how blocks are arranged into regions defined by a layout. You pair that config with a layout plugin, and you have a layout plugin instance.

Thing is, that's not flexible enough to meet our needs. For one, that approach tends to rely on static mapping of blocks to regions; however, given that one of our goals is the ability to dynamically (at some level) swap out layouts, it makes more sense to abstract the storage a bit and include information about the region 'role' (issue: #1816968: Introduce region 'types' to layout spec). This ends up meaning having layout plugin configuration...without a layout plugin.

Proposed resolution

We should create a different type of config object that can exist independent of any particular layout plugin, whose responsibility is to be the canonical way of describing page composition. In Panels, we have historically called these things a 'Display', and I've stuck with that terminology for now.

The primary place this terminology clashes is with entity rendering/view modes & field UI. The current plan (roughly agreed to by the relevant parties so far) is to solve that problem simply by using the same Display class for all of those things. In other words, top-level renderables that are configured via the UI are all, always blocks, and can be placed into layouts. That's not actually as crazy as it sounds :)

For the initial implementation, we should create a ConfigEntity subclass called Display, and a DisplayInterface that describes its behavior. These config objects should contain a reference to a layout plugin (and any relevant configuration that is specific to that layout itself), references to block plugins and their configuration (which will have to be retrieved via subsequent calls to config()), and information about the placement of those blocks within the layout's regions.

The expectation is that these Display objects are what will be produced by anything that wants to build a page - for example, the system being worked on in #1787942: Allow assigning layouts to pages. There are a number of ways we might expect they'll be used to do the job of displaying a page, or some sub-page element (if used in field UI), but the expectation is that whatever system creates a Display should save it at some config address that some corresponding system will be able to find later in order to render it. The evolving pattern for doing that in the context of rendering the entire page is in #1812720: Implement the new panels-ish controller [it's a good purple]. This patch originally lived there, but we separated it out.

Remaining tasks

Mostly we need to talk through the interface a bit more and make sure it covers the initial use cases we can imagine. Displays will not really be functional, however, until #1535868: Convert all blocks into plugins is in, as they need a standard way of addressing block config (and possibly even doing block instance retrieval, though that's unlikely).

User interface changes

No UI changes, this is entirely API level.

API changes

Layouts need to adapt to expect Display objects instead of the configuration format they had before.

CommentFileSizeAuthor
#82 interdiff.txt1.74 KBsdboyer
#82 display-1817044-82.patch32.16 KBsdboyer
#77 display-1817044-77.patch32.05 KBeffulgentsia
#77 interdiff.txt14.61 KBeffulgentsia
#73 interdiff.txt5.8 KBsdboyer
#73 display-1817044-73.patch31.26 KBsdboyer
#67 display-1817044-67.patch29.23 KBeffulgentsia
#67 interdiff.txt10.92 KBeffulgentsia
#64 display-1817044-64.patch31.99 KBeffulgentsia
#64 interdiff.txt7.8 KBeffulgentsia
#62 display-1817044-61.patch31.99 KBsdboyer
#62 interdiff.txt1022 bytessdboyer
#61 display-1817044-61.patch31.95 KBeffulgentsia
#61 interdiff.txt2.41 KBeffulgentsia
#57 display-1817044-57.patch32.04 KBsdboyer
display-1817044-56.patch605.35 KBsdboyer
#53 display-1817044-53.patch31.86 KBsdboyer
#53 interdiff.txt971 bytessdboyer
#49 interdiff.txt583 bytessdboyer
#49 display-1817044-49.patch30.91 KBsdboyer
#47 display-1817044-47.patch31.18 KBtim.plunkett
#47 interdiff.txt2.29 KBtim.plunkett
#46 display-1817044-46.patch31 KBsdboyer
#46 interdiff.txt3.3 KBsdboyer
#44 layout-1817044-43.patch30.86 KBtim.plunkett
#44 interdiff.txt6 KBtim.plunkett
#37 interdiff.txt33.46 KBsdboyer
#37 display-37-1817044.patch31.2 KBsdboyer
#29 interdiff.txt629 bytessdboyer
#29 display-29-1817044.patch32.11 KBsdboyer
#27 display-27-1817044.patch32.12 KBsdboyer
#27 interdiff.txt21.65 KBsdboyer
#22 interdiff.txt29.2 KBsdboyer
#22 display-22-1817044.patch17.9 KBsdboyer
#14 Objects.jpg39.39 KBGábor Hojtsy
#10 display-10-1817044.patch11.3 KBsdboyer
#10 interdiff.txt9.79 KBsdboyer
#5 display-6-1817044.patch7.28 KBsdboyer
display.patch7.29 KBsdboyer
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Gábor Hojtsy’s picture

Status: Active » Needs review

The layouts patch (#1787846: Themes should declare their layouts), as it was committed, creates configuration objects that were never intended to be anything more than "layout configuration" - that is, how blocks are arranged into regions defined by a layout. You pair that config with a layout plugin, and you have a layout plugin instance.

I've seen this confusion before, but just because it is using .yml files, it does not mean it is "configuration" (at least in the sense that you can edit it, or create new things via CMI or that it would be staged with CMI). It is not managed by the CMI system and the only part of the config system we are using is the yml parser abstraction (which should probably be a level up from the CMI system anyway). We merely use the yml format to provide *metadata* about the layout. We could have picked XML or .info files just as well.

As for the code, I think the added data stores and containers look fine. I don't understand the $staticData piece and the code comments don't help either. What is the purpose of that? As for the rest:

+++ b/core/lib/Drupal/Core/Config/Entity/Display.phpundefined
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Booze\Display.
+ */
+
+namespace Drupal\Core\Config\Entity;
+

There is not a lot of agreement in the docblock and the namespace as to where this is :D

+++ b/core/modules/system/system.moduleundefined
@@ -4118,6 +4118,29 @@ function system_archiver_info() {
 /**
+ * Implements hook_entity_info().
+ *
+ * Provides the core Display object, which contains all the info necessary for
+ * block-driven pages.
+ */
+function system_entity_info() {
+  return array(
+    'booze_display' => array(
+      'label' => t('Display'),
+      'entity class' => 'Drupal\Core\Config\Entity\Display',
+      'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
+      'config prefix' => 'booze.display',
+      'fieldable' => FALSE,
+      'entity keys' => array(
+        'id' => 'id',
+        'label' => 'label', // @todo may not need this if internal-only (?)
+        'uuid' => 'uuid',
+      ),
+    )
+  );

Do we even need to prefix this with something? Why not just use 'display'? We are in core, we can be bold and general.

On the label which is also marked elsewhere, I agree we might not need it if we always create displays as part of other things (page, node type, etc). Not sure how far would the reusability of displays go, it might be useful for reuse the same display for different pages, in which case having them as independently human readably labeled would be useful.

Gábor Hojtsy’s picture

Also I'm wondering where would the creation of these display objects go. I mean they need to have a UI eventually to pick a layout, configure blocks, etc. If we want to reuse this with entity field/property displays and pages alike, the layout picking, block config, etc. would not go to either module, but would instead go to somewhere else. Where does it make sense? If we create a display.module for this and somehow pull in the interactions to creating pages and entity display's that might or might not be overkill (or just repurpose block module for this, since blocks without displays have no way to display themselves and we had blocks to themes relations and display in block module always).

webchick’s picture

Issue tags: +Dynamic layouts

Tagging.

Status: Needs review » Needs work

The last submitted patch, display.patch, failed testing.

sdboyer’s picture

FileSize
7.28 KB

I've seen this confusion before, but just because it is using .yml files, it does not mean it is "configuration" (at least in the sense that you can edit it, or create new things via CMI or that it would be staged with CMI). It is not managed by the CMI system and the only part of the config system we are using is the yml parser abstraction (which should probably be a level up from the CMI system anyway). We merely use the yml format to provide *metadata* about the layout. We could have picked XML or .info files just as well.

i was actually referring to something that, i think, the Layouts patch isn't generating yet: not the yml file which describes a layout, but the the layout *instance* config which describes where blocks are placed in a particular layout plugin instance.

as for the boozey references, yeah, i forgot to pull those out when generating the initial patch. a new one is attached :)

layouts, blocks, and displays all need to be in core (system.module, where a module is needed). the goal we're shooting for is that Drupal can't render HTML without them; thus, it makes no sense to make them disable-able.

as for where within the config namespace things get stored...yeah, that's an open question. in the attached patch, the Display object is used to load everything under the display namespace. how we choose to address them, though, is fundamentally bound up with how we want to load them later. probably the simplest thing to do will be to just claim sub-namespaces under display for certain applications: e.g., display.page is used for the landing page builder, display.view_mode for storing displays describing view modes.

webchick’s picture

Status: Needs work » Needs review

bot

sdboyer’s picture

hopefully that one passes. i don't know why the first one would have failed.

if it's because there's an inherent expectation of translatability on entities, though...well, that's another strike against using ConfigEntity.

yched’s picture

display.view_mode for storing displays describing view modes

Since entity display is configured by bundle, that would probably be something like display.entity.[entity_type].[bundle].[view_mode] - but yeah.

Gábor Hojtsy’s picture

Well, we always had required modules. Just because filter or user module are reequired, they are not lumped into system module, so we can better find their code. So I am not sure that we need to put everything in system module, but again, this can be moved around later.

sdboyer’s picture

FileSize
9.79 KB
11.3 KB

Next iteration on this patch.

I've shifted most of the Display methods out onto an interface, and filled in most of the substantive methods on the Display itself. Which means we can have a more substantive discussion about what logic is placed directly on the display.

To that end, one of the big decisions I've made here is NOT having the Display act as a factory for creating block instances. Instead, it simply passes the config name for a given block instance back out to client code. This allows greater flexibility for the calling code, I think (though I am not necessarily convinced that that flexibility is useful for any real use case), but it has the drawback of requiring calling code to do the work of retrieving those block instances itself. If there is ever any hope that we might create a way for doing config() calls in some batched fashion (beyond getting everything under a prefix, which is inadequate for this case), then it would probably be preferable to encapsulate that logic within the Display so that it doesn't have to be reimplemented every time. For now though, I've opted for the simpler approach.

We still need getters and setters on the display itself, and an isValid() check as well. But we're getting closer.

effulgentsia’s picture

Nice to see the progress. I agree with #9 that the system_entity_info() should move to layout_entity_info() and the Display class and interface should move to the Drupal\layout namespace. We'll be building up this architecture over many issues, and keeping it all in layout module will help with clarity, I think. Later, after feature freeze, we can move relevant bits into more fundamental locations.

It would be helpful to see a sample display.SOMETHING.yml config file as part of this patch, maybe in a test module for starters until we're ready to do a real one for, say, the Standard profile's block assignments.

I'm noticing a similarity between the relationship of a Display entity and a Layout plugin in this patch as there is between a Grid entity and Grid plugin in #1816650: Add swappable (dynamic) grid systems to core. In other subsystems, we typically have a 1:n relationship (e.g., an ImageStyle config entity references multiple ImageEffect plugin instances, and a ViewsStorage config entity references multiple DisplayPlugin instances), but for Display/Layout and Grid, we have an interesting 1:1 relationship between config entity and plugin instance. I wonder what kind of patterns we want to introduce related to that (for example, should they be named the same, or is there a meaningful distinction between "display" and "layout" that warrants the different names)? Related to this, this issue title currently says "for use by layouts, et al", but is there really an "et al"? If we end up using Display config entities for rendering entity view modes, won't that also require us to use Layout plugins for them as well?

yched’s picture

Looking at this from the 'entity display' angle.

- I like the fact that the calling code gets a list of componenents and gets to render them.
This adresses the worries I had about the field_attach_prepare_view($entities) step on list of entities. Should be doable.

- For our use case, though, $blockConfig keys will probably be field names rather than CMI "block config" filenames, though :

'field_foo' => array(
  'region' => 'content',
  'region-role' => 'content',
  'weight' => -100,
  'format' => 'text_trimmed',
  'settings' => array(
    'trim_length' => 200,
  ),
),
'field_bar' => array(
  'region' => 'sidebar_first',
  'region-role' => 'sidebar',
  'weight' => -200,
  'format' => 'integer_default',
  'settings' => array(
    'thousands_separator' => ',',
  ),
),

Maybe a flexible system should let the keys unspecified, free for the calling code to interpret ?

sdboyer’s picture

Nice to see the progress. I agree with #9 that the system_entity_info() should move to layout_entity_info() and the Display class and interface should move to the Drupal\layout namespace. We'll be building up this architecture over many issues, and keeping it all in layout module will help with clarity, I think. Later, after feature freeze, we can move relevant bits into more fundamental locations.

maybe i overestimated the interest in having non-disable-able modules. if folks feel this is the way to go, i can move it, but it does seem a bit superfluous to me. so long as we understand it probably should move back into Core later on.

just to be clear, this also means that the panels-y controller will also have to be in the layout namespace. that starts to seem a little weird to me, but i don't want to bikeshed on this point, and i don't have the experience with this issue to have a strong opinion.

It would be helpful to see a sample display.SOMETHING.yml config file as part of this patch, maybe in a test module for starters until we're ready to do a real one for, say, the Standard profile's block assignments.

yeah, we need to have one of those. i'll go for it in the next iteration of the patch.

a note, though - doing that is gonna be a bit hacky at first, as i expect that the standard way in which modules provide displays will be to provide 'abstract' ones - that is, ones where blocks are bound ONLY to roles (cf. #1816968: Introduce region 'types' to layout spec), not specific regions. and the whole code path for converting abstract displays into realized ones still needs to be tackled. so, for the initial approach to a test, we'll have to do a fully realized display, which will be a bit of an oddity (but is still an important test to have).

In other subsystems, we typically have a 1:n relationship (e.g., an ImageStyle config entity references multiple ImageEffect plugin instances, and a ViewsStorage config entity references multiple DisplayPlugin instances), but for Display/Layout and Grid, we have an interesting 1:1 relationship between config entity and plugin instance.

oh, but it's better than that - we have both! and not even really the 1:1!

Displays have the 1:n insofar as they contain references to multiple block config instances. that's a different layer than what you're referring to, though.

i say Displays don't really have 1:1, though, because Displays are intended to have meaning independent of any specific layout plugin. sure, they can't do anything without them, but they don't have to be paired with a specific one. in #10, i just added basic logic to remap blocks to a new layout plugin on the fly - see Display::remapBlocksToLayout(). i don't know how analogous this is to the Grid case, but the point is that Displays are less of a config object *for* Layouts (which implies Layouts USE that object) and more the other way around - Displays rely on the Layout interface. though really, the two need to be married together via a controller, which is in the other patch (#1812720: Implement the new panels-ish controller [it's a good purple]).

that distinction is certainly enough to merit the different name, i think. and yep, using Displays for view modes would mean using Layouts for them as well. that's got some complications to it, but it sounds like there's enough overlap that it's worth exploring.

Gábor Hojtsy’s picture

FileSize
39.39 KB

@effulgentsia: I think there is some confusion as to how layouts get their setup with their definition and their configuration was meant to store block placement (in the already committed code). That was introduced so that layout instances would hold block placement. @sdboyer suggests we move that to displays instead and layout instances would not have individual configuration (in my understanding). So:

Objects.jpg

That is, layouts provide a generic region placement capability, but no direct configuration. Blocks provide output and displays relate the two together with other metadata. Then a display is taken and stuffed up with visibility settings to display on a page. Or when used for field placement for a node type, imagine replacing Page with whatever it would use to apply to node types.

Question is how far should we bring this abstraction. Could block placement, etc. belong in layouts (like in current core committed code) or should be removed direct from Layouts and be only in Displays. How page and field displays are separated since even though they need specific blocks to be sourced (you should not be able to reuse a page display as a node type display since you use very different blocks), displays themselves lack the placement/visibility features on them to be useful at the front end.

Let's figure this out :)

Gábor Hojtsy’s picture

Discussed the reasons for the above setup with @sdboyer in IRC:

[2:32pm] sdboyer: GaborHojtsy: by keeping the blocks decoupled from the layouts themselves, we can create and enforce contracts - styles (which we haven't dealt with yet, but probably should), caching, meta-things like an in-place-editor, without complicating the logic that has to be contained in layouts
[2:32pm] sdboyer: and that's good for everyone, b/c i can't imagine frontenders want to have to deal with that shit (even on the off chance that they WANT to write a full new layout plugin instead fo just a derivative)
[2:34pm] GaborHojtsy: sdboyer: yeah, that makes sense  what I wanted to point out is that displays are in themselves not yet useful, they just connect layouts to blocks and then still need something to "place" it
[2:34pm] GaborHojtsy: sdboyer: AND they are different based on the something tha tis used to place it
[2:34pm] sdboyer: GaborHojtsy: ahhh ok great. yeah we're definitely in agreement on that point
[2:34pm] sdboyer: *points
andypost’s picture

Suppose block plugins should be related to display in context of regions so we can change a layout but blocks will move to same -named regions in new layout. This also could be very useful for fieldUI - assign fields to region-names and allow to change layout

Gábor Hojtsy’s picture

@andypost: Is that a summary of how the current patch works or a suggestion to change something? To me it sounds like a summary of how the current patch works?!

andypost’s picture

@Gábor Hojtsy, This is how the patch works, so suggestion to update diagram

+++ b/core/lib/Drupal/Core/Config/Entity/Display.php
@@ -0,0 +1,233 @@
+  public $layout;
...
+  public $layoutSettings = array();
...
+  public $blockConfig = array();
...
+  protected function sortBlocks() {
...
+  protected function remapBlocksToLayout(LayoutInterface $layout) {

This code

Gábor Hojtsy’s picture

Well, in that case I believe that is already represented in the diagram.

sdboyer’s picture

@andypost - i have difficulty picturing a diagram that could represent this well. fully realized displays *are* bound to a specific layout, with logic to perform remapping IF that mutation is requested. so the diagram is accurate for describing the state of a relationship of layout to page at any one time. only under circumstances where a remapping is necessary - and those circumstances are intentionally unspecified in this patch - will it occur. so what may be necessary is a parallel set of diagrams that describe the mutation process...but they'll all ultimately end up in the same arrangement as is characterized in #14.

sdboyer’s picture

ahh, i totally missed yched's response with my xpost.

For our use case, though, $blockConfig keys will probably be field names rather than CMI "block config" filenames, though: (...example...) Maybe a flexible system should let the keys unspecified, free for the calling code to interpret ?

we could leave it unspecified, but i think we'd regret it. by restricting the type of object the controller interacts with to being a block, we get assurances about the interface and capabilities that let us do Good Things. for example:

  1. we know that blocks are addressable. or at least, that's our goal. that means that we can fire subrequests to them, which means we can use ONE caching layer for all our top-level renderables - HttpCache. the benefits of a single logical layer at which most (output) caching occurs cannot be overstated.
  2. we can require that blocks become capable of creating and managing Response objects without placing that requirement on fields as well, which would be an awkward cross-domain responsibility for fields.
sdboyer’s picture

FileSize
17.9 KB
29.2 KB

OK, new patch.

  • Displays have been relocated into the layouts module, per the requests to do so made in this issue. \
  • the Display::remapBlocksToLayout() method is now public, on the DisplayInterface, and renamed to simply mapBlocksToLayout().
  • I created the base abstract class DisplayBase, and introduced the UnboundDisplay subclass to cover the use case i've been discussing for modules providing blocks-mapped-to-types-but-not-specific-regions. I've been referring to those mostly as 'abstract' displays, but given how awfully confusing a class AbstractDisplay that is not actually an abstract class would be, i pivoted on the naming. I am *entirely* open to a different name, but this was the most straightforward and descriptive name I could come up with offhand.

    Note that i'm not yet super-clear on the breakdown of methods that ought to be implemented in the DisplayBase parent vs. the child classes.

  • I've created some simple config objects for both a Display and an UnboundDisplay. They're lodged in the layout_test testing module's config directory for now. They're identical, except that the UnboundDisplay has only region type mappings, no specific region mappings (per the spec).

    Adding these config objects necessarily means adding block plugin config, as generated by #1535868: Convert all blocks into plugins. So, while I wouldn't say we're blocked on that per se (we could probably commit without it, we just can't convert stuff until it's in), any folks looking at this will need to keep that in mind.

Remaining tasks are basically the following:

  1. Refactor all the Layout plugins (and their tests) tests to use Displays instead of their current configuration format.
  2. Write tests exercising the various API methods on displays - for things like remapping blocks to new layouts. Can we extend UnitTestBase if we need the config system? i imagine not, since that means db calls)
  3. Consider adding methods to the Display API that help with more granular setter behavior for block info.
  4. Fix up all the shit i find in the course of writing tests, since none of this code has really been properly run yet.

there's still stuff to be figured out, to be sure - i'm really dubious about this DisplayInterface::setMainContent(), DisplayInterface::hasMainContent() stuff, for example. but we can probably sort those things out as we're wrestling with implementation further down the line.

yched’s picture

re #21:
Well, for now I'm still really reluctant and unconvinced about the "fields displayed within entities are regular blocks" approach.

1) I do fear the perf overhead on this highly critical path, for gains that are not clear to me:
- Blocks are directly adressable : do we really need to load individual HTML for a field through ajax ?
- Block cacheability: cacheability of a rendered entity seems valuable at the entity level, but IMO much less down to the field level ?

2) This would mean that the stored "recipe" for a node display in 'full' or 'teaser' would be split in N CMI files, one per field (and per view mode) - aww.
For editability, manageability and reviewability of CMI files changes during deployments, that's a big -1 compared to a single file per view mode that would contain the structure shown in #12. I, for one, would love to be able to manually edit my node.article.full.yml (or something) files to adjust a view mode.
That's also a lot of config entries to read for a single entity view...

sdboyer’s picture

@yched - it's funny, you're making many of the arguments i've made to myself about why joining these two systems isn't necessarily the best idea :) i only continue to push because i had a LOT of people suggest we should reuse this system for view modes et. all, and i don't want to de facto break that obligation by just stopping talking about it.

This would mean that the stored "recipe" for a node display in 'full' or 'teaser' would be split in N CMI files, one per field (and per view mode) - aww.

this current version of the Display spec calls for references to other CMI objects for each of the blocks. Kris has suggested that we might also want to support direct storage of that config, which i'm quite open to; the Display object is a convenient place to abstract that storage mechanism.

for internally & externally-stored block instances to work in harmony though, it'd probably mean changing DisplayInterface to dictate it act as a meta-factory block creation, rather than leaving it up to client code. in any case though, it should be possible to avoid freestanding config if the case merits it.

I do fear the perf overhead on this highly critical path, for gains that are not clear to me:

as do i, for pages or otherwise. i am staunchly opposed to having 1 (or worse, N) queries per block simply to get the config out. there's an open issue about this in general (#1187726: Add caching for configuration / rework config object loading (Was: Memory usage and i/o from config objects)). i'm talking with Kris, and then maybe heyrocker, about a multiloading approach that could be used to minimize queries. unfortunately, simple prefix-based loading isn't going to be adequate for the task.

i tend to agree that those use caching arguments i gave really don't apply very well to the fields use case. i was really giving them as a reason why i'd prefer to have Displays be strictly block-oriented (re: "more flexibility" in #12), as they very much matter for the block case and i'd rather the rendering controller not have to jump through hoops for each renderable item to figure out if it's a block or not.

the more we discuss, the more it seems silly to try to reuse the same system for these. there are similarities, but maybe that just means we should try to mimic/reuse at a pattern level, rather than actually sharing code.

yched’s picture

there are similarities, but maybe that just means we should try to mimic/reuse at a pattern level, rather than actually sharing code

That's a very possible outcome :-), although I don't lose all hope that at least some basic code / base class can be shared at some point.
It would make sense IMO to have a base class that "just" deals with storing and handling "a configured list of stuff in regions, mapped to regions in a Layout", without making assumptions what that 'stuff' actually is. That's what I liked about your current approach where rendering of the content happens outside the Display object.

But well, I might very well be mostly talking out of my back, here :-).

Well, page layouts are clearly the priority, with "layouts within entities / Field UI" only a very cool 'could have'. So the best option could be to design the "display / layout" supporting code for this use case primarily, with what you guys feel makes most sense architecturally for this, so that we don't risk getting nothing in. As I wrote in another issue, my goal when chiming on "displays in entities" was not to derail this. When attacking entity display, we can have this "fields as blocks, really ?" discussion with the various parties involved, and see whether we can identify some base code to share.

Then again, this is not my issue, and if you say a lot of people expressed interest / took position on this, I don't want to shut the discussion down here if people feel it needs to happen sooner.
Your call, @sdboyer :-)

sdboyer’s picture

buck successfully passed ;)

i'll say this - my last patch added a DisplayBase abstract parent, and we have the DisplayInterface. i think we might be able to make it all work by adding another intermediary to that family tree. for the purposes of this patch, though, i think it's important we get it out with the strong page orientation. so let's punt for the moment, to revisit as soon as i get #1812720: Implement the new panels-ish controller [it's a good purple] moving again (which i'm back on as SOON as this goes in).

sdboyer’s picture

FileSize
21.65 KB
32.12 KB

and i'm delighted to say that, with this patch, we're now ready for this to go primetime. or at least, to get primetime evaluation.

  • I added a Display::generateUnboundDisplay() method. this is the complement to UnboundDisplay::generateDisplay; it strips out the relevant region & layout data and gives you a nice, clean unbound display, ready for saving.
  • I added a small fuckload of tests.
  • In the course of adding the tests, I made sure all the major methods work. They do.

there are still certainly problems - some general interface consistency, a lack of helper methods for manipulating data contained by the config object, and no tests for basic CRUD. however, i think this is just about ready to go in as-is, and we can iterate on those things as we go.

note that this patch contains the changes described in #1816968: Introduce region 'types' to layout spec, as they were needed for tests to run and it's just annoying to not include them. we should make that a discussion/docs-only issue to keep things simple.

Status: Needs review » Needs work

The last submitted patch, display-27-1817044.patch, failed testing.

sdboyer’s picture

FileSize
32.11 KB
629 bytes

minor oversight, forgot to change a config val on the layout tests after renaming it on disk. this one should go green.

sdboyer’s picture

Status: Needs work » Needs review
effulgentsia’s picture

I do fear the perf overhead on this highly critical path

In the spirit of #25, I also don't want to derail this issue, but just a quick response to this, and then I'll drop it until there's a better issue to continue that conversation: rather than accepting blocks as having a lot of overhead, and then making fields and other small items expose configuration and render APIs that aren't blocks, I would prefer us to make the Block API capable of handling small, simple units of HTML performantly. In other words, it should be possible to expose blocks that aren't directly addressable, that have their config embedded in a larger object, that aren't independently cacheable, and that don't invoke a subrequest. Then the display of fields can still be exposed as blocks as far as API and configuration UI is concerned. Maybe that's a pipe dream, but I put it out there as something to consider, because I agree that if every block render has a ton of overhead, then it won't make sense to use them at granular levels like individual fields. At the same time, I don't think we should slow down this issue, #1535868: Convert all blocks into plugins, or #1812720: Implement the new panels-ish controller [it's a good purple] worrying about lightweight blocks: I'd rather us get heavyweight blocks in first, then refine the architecture as needed to support lightweight blocks.

Thanks for #29. I'm reading it now, and will post a review if/when I have useful feedback to offer.

sdboyer’s picture

hear hear, for the approach outlined in #31. i think that can serve us quite well, and with not much added backtracking cost later.

effulgentsia’s picture

Status: Needs review » Needs work

The basic architecture here seems sound to me, so on to nits to get this ready for commit:

+++ b/core/modules/layout/lib/Drupal/layout/Config/Display.php
@@ -0,0 +1,93 @@
+  public function __construct(array $values, $entity_type) {
+    // Forcibly restrict this from ever being built as a different entity type.
+    parent::__construct($values, 'display');
+  }

Why do we need this? Entities are instantiated with entity_create() or entity_load(). In either case, hook_entity_info() maps entity type to class. There's no need for us to second guess that hook.

+++ b/core/modules/layout/lib/Drupal/layout/Config/Display.php
@@ -0,0 +1,93 @@
+  public function getLayoutPluginInstance() {

Needs a PHPDoc.

+++ b/core/modules/layout/lib/Drupal/layout/Config/Display.php
@@ -0,0 +1,93 @@
+        throw new \Exception(sprintf('Display "%id" had no layout plugin attached.', array('%id' => $this->id())), E_RECOVERABLE_ERROR);

If we're gonna throw an exception, we should probably create our own namespaced class for it, to make it easier to catch.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+   * The human-readable label of this display.
+   * @todo we probably don't need/want to have this - but is it safe to remove?

Yes. Let's try. If it turns out there's code that breaks without a label, let's fix that code to be more robust.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+   * @todo we might possibly want to separate this into its own config object
+   */
+  public $layoutSettings = array();

How can we resolve this @todo? What are some use cases for layoutSettings?

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+  /**
+   * Storage for data sources that have been statically assigned - that is, not
+   * derived in some way from the Request.
+   *
+   * The array is keyed on the name of a data source, and the values are getters
+   * that are able to retrieve the appropriate data.
+   *
+   * @todo define how the getters work.
+   *
+   * @var array
+   */
+  public $staticData = array();

Would it be okay to remove this from this issue and make it the responsibility of the issue that first implements block data sources to add this? Until then, this is a useless property with no clear API.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+  protected $blocksInRegions;

Needs a PHPDoc.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+  public function getAllBlockInfo() {
+    return $this->blockInfo;
+  }

This and other methods and this class need a PHPDoc.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+  public function getSortedBlocksByRegion($region = '') {

Why default to empty string rather than make it a required parameter?

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+  /**
+   * @param $info
+   */
+  public function setBlockInfo($info) {
+    $this->blockInfo = $info;
+  }

This should either be protected or added to the interface.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+    $layout_instance = $this->getLayoutPluginInstance();
+    if ($this->layout !== $layout_instance->getPluginId()) {

It seems unfortunate that this can ever be true. I guess ideally, we'd make $layout not public, so it could only be changed via setLayout() allowing us to maintain consistency between $layout and the instance. Is there something about ConfigEntity that's requiring all configurable properties to be public, and if so, is there an issue to change that that we can reference in a @todo?

Additionally, why is this method on the abstract class when getLayoutPluginInstance() fails on UnboundDisplay?

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+        // @todo this logic means we only EVER assign to the first region of a type. ick.

Why is this a problem? If there are clear needs to do otherwise, let's add that info to the todo. Otherwise, I think we can remove the todo.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayInterface.php
@@ -0,0 +1,98 @@
+   * @todo this is fairly specific to pages - maybe not best for the interface
+   *
+   * @param Callable $callback
+   *   Any form of callable.
+   * @param array $args
+   *   An array of arguments to pass to the callable.
+   */
+  public function setMainContent($callback, array $args = array());

I recommend removing (set|has)MainContent() from this issue and dealing with it in the controller issue. There, we'll see what code requires it and can better evaluate what interface to add it to. For example, maybe a MainContentAwareInterface, but who knows.

+++ b/core/modules/layout/lib/Drupal/layout/Tests/DisplayInternalLogicTest.php
@@ -0,0 +1,137 @@
+    $this->twocol = display_load('test_twocol');
+    $this->onecol = display_load('test_onecol');
+    $this->unbound = display_load('test_unbound_display', 'unbound_display');

I don't think we want a display_load() wrapper function at all, but even if there's a IDE completion argument for having it, let's not call it from tests.

+++ b/core/modules/layout/lib/Drupal/layout/Tests/DisplayInternalLogicTest.php
@@ -0,0 +1,137 @@
+    $this->assertEqual(count($left), 2, 'Two blocks found in left region.');
+    list($first, $second) = $left;
+    $this->assertEqual($first, 'plugin.core.block.navigation_instance2', 'navigation_instance2 block instance is first in the left region.');
+    $this->assertEqual($second, 'plugin.core.block.main_block', 'main_block block instance is second in the left region.');

Here and elsewhere, this can be shortened to a single assertIdentical() on arrays. Also, the last argument can be omitted: doing so allows the message to be much more useful in case of failure, because then it includes the complete values of both comparables.

+++ b/core/modules/layout/tests/config/display.unbound.test_unbound_display.yml
--- /dev/null
+++ b/core/modules/layout/tests/config/plugin.core.block.main_block.yml

plugin.core is a useless prefix. The filename should start with "block.". block.main_block.yml risks collision with non-test configuration of the same block, so we should use a test-qualified name for the block (e.g., block.test_block.yml or block.layout_test_block.yml or block.main_block__layout_test.yml, etc.).

+++ b/core/modules/layout/tests/config/plugin.core.block.main_block.yml
@@ -0,0 +1,18 @@
+id: system_main_block
+status: '1'
+cache: '-1'
+visibility:
+  path:
+    visibility: '0'
+    pages: ''
+  role:
+    roles: {  }
+  node_type:
+    types:
+      article: '0'
+      page: '0'
+  visibility__active_tab: edit-visibility-path
+subject: 'main content'
+module: system
+region: content
+weight: '0'

Seems like way too many properties here considering the blocks as plugins issue hasn't landed yet. I'd reduce this to just what's needed by the test.

+++ b/core/modules/layout/tests/layout_test.module
@@ -26,7 +26,9 @@ function layout_test_page() {
+  // @fixme this implementation ignores blocks completely, so is inherently incomplete.

s/@fixme/@todo/

yched’s picture

From #33:

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.php
@@ -0,0 +1,228 @@
+   * @todo we might possibly want to separate this into its own config object
+   */
+  public $layoutSettings = array();
How can we resolve this @todo? What are some use cases for layoutSettings?

From other "X as plugin" code, it would seem natural to me that the plugin settings are stored in the same location than the plugin id. The [plugin_id, settings] pair contains everything needed to create a layout instance, they go hand in hand, why would we store them in different places ?

Also, regarding this question of settings, shameless plug for #1764380: Merge PluginSettingsInterface into ConfigurableInterface, which is already in use by 'field formatter' and 'fiel widget' plugin types, and that I'll really try to push as a standard for D8 plugins where it applies.

sdboyer’s picture

Status: Needs work » Needs review

making these changes locally; will post a patch a bit later tonight.

  • re: forcing the entity type. yeah, i'm probably being overprotective. removed.
  • getLayoutPluginInstance(), and the other methods, are documented on the interface. there's nothing additional that needs to be added. ...and i looked up coding standards, and apparently we have a colossally stupid rule that we say we're implementing that method. wow. no value added there. oh well, added it.
  • namespaced exception class: strongly agreed. i skipped it because i don't yet have a thought on how to logically organize those exceptions. this is largely because the organization of this whole system is unclear - i already moved it into layouts. should there be a shared exception stack with layouts, blocks, and displays somehow? just displays? i don't know. certainly the controller will have to deal with exceptions from each of the three. so if you have ideas on the namespacing, i'm all ears - but otherwise i'd rather wait until the bigger picture about where this systems fits congeals.
  • cool, i removed DisplayBase::$label, and the corresponding declaration in layout_entity_info(). lessee what breaks:)
  • layoutSettings are tricky. in Panels, it's where we tuck all the configuration for a flexible layout, for example - though i hate that approach and i want our layout builders to write static layouts, not figure shit out at runtime. and static layouts will never have anything here. it's really just best understood as "config that's injected into the layout plugin."
  • DisplayBase::$staticData - ok, i've removed it. it's definitely gonna be a tricky one; it's one of the main areas we need to get working on, quick. it's also the crossover point with @fago, @_dixon et. all who are working on the data api side of things, as that's basically what we need to put there.
  • DisplayBase::getSortedBlocksByRegion() should be required, thanks. that's a leftover from before i made DisplayBase::getAllSortedBlocks() a separate method.
  • DisplayBase::setBlockInfo() has been removed. it's technically not necessary because ConfigEntityBase::set() exists. however, i do want to explore a richer group of data mutation methods later.
  • "it's a shame if if ($this->layout !== $layout_instance->getPluginId()) is ever true." it is icky, i agree. i would prefer a more explicit approach to changing the layout that's attached to a Display, and doing the remapping then. having it that way just struck me as the safest, simplest possible way for it to work - a good enough starting point.

    and you're right about that method not belonging there. in fact, neither it nor the getSort*() methods belong on the parent, since that's not possible for UnboundDisplays. bleh, i'm starting to think about having three interfaces - DisplayInterface, BoundDisplayInterface, UnboundDisplayInterface. yeah...that's what we need, i think. that's a bit more involved, i'll get to that later tonight.

  • only ever assigning to the first region of a type may not be a problem for most cases, but NOT allowing a mechanism by which some other sorting logic could be used isn't prudent. i've removed the @todo in favor of a new one that's more clear on needing to loosely couple the sorting logic.
sdboyer’s picture

Status: Needs review » Needs work

whoopsie xpost, resetting status.

sdboyer’s picture

Status: Needs work » Needs review
FileSize
31.2 KB
33.46 KB

more point-by-point responses:

I recommend removing (set|has)MainContent() from this issue and dealing with it in the controller issue. There, we'll see what code requires it and can better evaluate what interface to add it to. For example, maybe a MainContentAwareInterface, but who knows.

yeah, agreed, it's premature to deal with them now. i've removed them. i put them in when i was writing the initial patch, and i just never pulled them out. i'm not sure if we could make an interface work to describe it, but we'll be able to better tell when we're further down the road with the controllers work.

I don't think we want a display_load() wrapper function at all, but even if there's a IDE completion argument for having it, let's not call it from tests.

yeah, i was just annoyed at the lack of completion, then got lazy and reused it. i like neither having a wrapper function nor having to call entity_load() to get displays. in any case, removed it from tests.

Here and elsewhere, this can be shortened to a single assertIdentical() on arrays. Also, the last argument can be omitted: doing so allows the message to be much more useful in case of failure, because then it includes the complete values of both comparables.

SWEET. i coulda sworn there was a method like this, but i couldn't find it before. i did not know about the easter egg with the text parameter, though...that's just awesome. changed.

plugin.core is a useless prefix. The filename should start with "block.". block.main_block.yml risks collision with non-test configuration of the same block, so we should use a test-qualified name for the block (e.g., block.test_block.yml or block.layout_test_block.yml or block.main_block__layout_test.yml, etc.).

the prefix probably is useless. but here i'm following (ish) the pattern set in the blocks patch. let's change it there first, then here.

i've added a test_ prefix to all the block configs.

Seems like way too many properties here considering the blocks as plugins issue hasn't landed yet. I'd reduce this to just what's needed by the test.

they're a direct paste of what the blocks-as-plugins patch is currently outputting. in all likelihood, we'll need to change them a bit in the near future, anyway - i'd rather just keep them as they are than speculate about what they may ultimately look like once we get it in.

From other "X as plugin" code, it would seem natural to me that the plugin settings are stored in the same location than the plugin id. The [plugin_id, settings] pair contains everything needed to create a layout instance, they go hand in hand, why would we store them in different places ?

yeah, that's the typical pattern. and i can imagine wanting to reuse a layout instance across a couple different displays, so maybe it is best that we just delegate that to the layout plugin mapper and only store the layout config name on the display.

my only concern with that is that, barring some new optimized loading strategy, that's just one more query we have to run on EVERY html page.

Status: Needs review » Needs work
Issue tags: -Dynamic layouts

The last submitted patch, display-37-1817044.patch, failed testing.

sdboyer’s picture

Status: Needs work » Needs review

#37: display-37-1817044.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +Dynamic layouts

The last submitted patch, display-37-1817044.patch, failed testing.

sdboyer’s picture

i am baffled. these pass locally. wtf?

sun’s picture

Check the "Review log" fieldset on the test results page in cases like this. It shows this fatal error:

Fatal error: Class name must be a valid object or a string in /var/lib/drupaltestbot/sites/default/files/checkout/core/includes/entity.inc on line 263
FATAL Drupal\layout\Tests\DisplayInternalLogicTest: test runner returned a non-zero error code (255).

Also make sure to locally run tests with run-tests.sh, and if that doesn't yield the same errors, additionally try running the failing tests after disabling related modules (here: Layout module). Most often, a test only passes locally, because something of the parent/test runner environment unexpectedly leaks into the test environment - we're not running short on these kind of leakage problems.

sdboyer’s picture

thanks, sun. didn't know about that link. i think timplunkett's gonna step up with the fix, i'm swamped with d.o work today.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
6 KB
30.86 KB

I've moved the entity info to the annotation, and adjusted the existing use statements.

This will likely still break, because classes in the same directory didn't have the use statement to begin with.

+++ b/core/modules/layout/layout.moduleundefined
@@ -31,3 +31,36 @@ function layout_theme($existing, $type, $theme, $path) {
+      'config prefix' => 'display',
...
+      'config prefix' => 'display.unbound',

These would conflict. Config prefixes need foo.bar. I'd recommend layout.display and layout.unbound_display

Also, the Type subdirectory for the manager is being phased out in other parts of core, and I'm not sure the Config subdirectory is relevant any longer.

Status: Needs review » Needs work

The last submitted patch, layout-1817044-43.patch, failed testing.

sdboyer’s picture

Status: Needs work » Needs review
FileSize
3.3 KB
31 KB

thanks, tim!

the use statements weren't there because they were redundant while the classes all shared the same directory. now that they've moved, i've added them explicitly.

i'm not gonna prefix with layout because i don't want to create one more thing to change down the line (layout module is not the final destination for this system), but i've renamespaced to display.bound and display.unbound.

i've removed the label from the annotation, as we don't want displays to have labels.

this is still failing for me locally - the entity system doesn't seem to be picking up the annotations. i can't figure out why; tim said he'd take another stab.

tim.plunkett’s picture

FileSize
2.29 KB
31.18 KB

So, label is required. And it was in the hook_entity_info() already, I didn't add it in my patch, I just moved it.

All that was missing was two extra use statements, and the renaming of the test files to match the new config_prefix.

This passed locally for me.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

I think this patch looks great. Reviewing it here are some details I found though:

- there are 7 @todo comments in the code; do you think all of those should be in followups, not in the initial patch
- there are *various* methods and classes where the first phpdoc comment line is multiline; that would be a minimal code style requirement I think
- the recently committed layouts in layout module are not actually covered in the patch(?)

Otherwise looks good to me.

sdboyer’s picture

FileSize
30.91 KB
583 bytes

one of the @todos is just kinda whining, i removed that one. the others should are fine for followups.

yeah, i spent a while trying to get the first lines of the docblocks down, at least in several cases. it was tricky...though i'm not known for brevity :)

i'm not sure what you mean by "covering" the recently-committed layouts?

sdboyer’s picture

Status: Needs work » Needs review
Gábor Hojtsy’s picture

@sdboyer: I meant these two new layouts (which are finally outside of tests): http://drupalcode.org/project/drupal.git/commitdiff/99e6cef6d165de3548f4...

Status: Needs review » Needs work

The last submitted patch, display-1817044-49.patch, failed testing.

sdboyer’s picture

Status: Needs work » Needs review
FileSize
971 bytes
31.86 KB

ok, i've updated the patch to add region types to the two new layouts.

i think this should be good now.

Status: Needs review » Needs work

The last submitted patch, display-1817044-53.patch, failed testing.

Stalski’s picture

Looks good overall, great work sam!

Only remark at this moment is the naming of Left and Right. This should probably be First and Second to respect the mobile first thought.

sdboyer’s picture

FileSize
32.04 KB

somehow i lost the changes from #44. added those back in, which should take care of the failures.

this interdiff is exactly the same as #44, so just see that one.

ugh, generated the patch wrong.

sdboyer’s picture

Status: Needs work » Needs review
Stalski’s picture

This will be awesome to work with.

Few remarks:

+++ b/core/modules/layout/layouts/static/twocol/two-col.ymlundefined
@@ -4,5 +4,9 @@ template: two-col
-  first: 'First column'
-  second: 'Second column'
+  first:

"Left side" should be replaced to "First column", no?

+++ b/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.phpundefined
@@ -0,0 +1,194 @@
+   */
+  public $layout;
+

I personally prefer $layoutId of $layoutName as it has the value of getPluginId()

Also some files need a empty ending rule.

webchick’s picture

OMG IT'S GREEN!!!!! GREAT WORK, FOLKS!! :D :D

Oops. Thought I was on the blocks as plugins issue. ;) Carry on!

effulgentsia’s picture

FileSize
2.41 KB
31.95 KB

Re #59.1, I agree: this is not the issue to rename HEAD's existing region names, so this patch reverts that. Left/Right/Middle is still used by layouts used in tests, but that's ok: tests can have non-semantic names. This also adds missing newlines.

sdboyer’s picture

FileSize
1022 bytes
31.99 KB

@Stalski - yeah sorry, i should have clarified: i didn't introduce that layout, so i don't feel comfortable changing it here. if i did, i'd start digging into the fact that it's an inherently incomplete layout - it does not have regions adequate to the task of covering even the most essential of the basic region types we've defined.

i've rerolled the patch that adds ending newlines to two of the yml files, the only ones i could find.

sdboyer’s picture

ignore my patch in #63, eff's is better.

effulgentsia’s picture

FileSize
7.8 KB
31.99 KB

Improved (IMO) names and docs of BoundDisplayInterface. I'll want to do the same for the rest of the patch, but not today.

sdboyer’s picture

i'm +1 to all of eff's docs changes in #64. i was running out of steam when writing those, clearly :)

effulgentsia’s picture

Status: Needs review » Needs work
+++ b/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.php
@@ -0,0 +1,194 @@
+      $this->blocksInRegions[$region_name] = array_keys($blocks);

Is there a reason we call array_keys() here? It means getAllSortedBlocks() and getSortedBlocksByRegion() return only block instance names, not their info. Is this what's wanted? If so, we need to update the docs accordingly.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
10.92 KB
29.23 KB

This one:
- Removes the plugin.core.block.* test config files, because they are not used by the tests.
- Removes staticData from the display.* config files, because that got removed from the scope of this issue in #37.
- Removes plugin.core. from block instance names in display.* config files, because #37 says it's there only for compatibility with #1535868: Convert all blocks into plugins, but that issue still has a lot of failing tests and needs a lot of code review, so very likely, this issue will land first, so this issue has no responsibility to follow that one's unagreed-upon patterns.
- Renames the test blocks to just test_block_1, etc. For the purposes of these tests, there's no need to imply some connection with particular block functionality.
- Cleans up DisplayInternalLogicTest

sdboyer’s picture

Is there a reason we call array_keys() here? It means getAllSortedBlocks() and getSortedBlocksByRegion() return only block instance names, not their info. Is this what's wanted? If so, we need to update the docs accordingly.

yeah, this is one place that the API is a bit piecemeal. the current design is for the case where the Display itself does not do any block instanciation, instead relying on the client code to do it. on the one hand, there's getAllBlockInfo(), which returns the array of block info, keyed by config name. client code will need to instantiate those blocks, and the logical way to store those instantiated blocks is in an array, keyed by config name. so if the various getSort methods return simply the array of config names, it's easier to match on them directly that way. if we wanted to instead return the full associative array of block info, i'd be OK with that too. it's easy enough for the client to ignore it, or just array_keys() on its end.

this is definitely a less-clean way to build this API, but turning Displays effectively into a block factory has its own separation-of-concern drawbacks in the long term, in addition to more tightly coupling this patch with #1535868: Convert all blocks into plugins. i would prefer to keep it this way, at least for now, then explore further down the line whether or not it's safe to tighten up the interface by encapsulating more of this logic.

Removes the plugin.core.block.* test config files, because they are not used by the tests.

errr...they're not? they should be, as they're referenced by the test display objects. but it seems to be passing...weird.

oh but wait - per my logic above, the Display API currently leaves that block instantiation (and thereby, config-grabbing) up to client code, so we can just skip it. bueno.

Removes staticData from the display.* config files, because that got removed from the scope of this issue in #37.

great, thanks.

Removes plugin.core. from block instance names in display.* config files, because #37 says it's there only for compatibility with #1535868: Convert all blocks into plugins, but that issue still has a lot of failing tests and needs a lot of code review, so very likely, this issue will land first, so this issue has no responsibility to follow that one's unagreed-upon patterns.

yeah, you really hate that namespacing :) can't blame you. i guess as long as we don't need to have those block config files there, there's no harm in pulling them out.

Renames the test blocks to just test_block_1, etc. For the purposes of these tests, there's no need to imply some connection with particular block functionality.

wfm.

+++ b/core/modules/layout/lib/Drupal/layout/Tests/DisplayInternalLogicTest.php
@@ -89,48 +83,51 @@
-    // @todo the determining factors for which of the nav instances comes first is twisty. improve it.
-    $this->assertIdentical($first, 'plugin.core.block.test_navigation_instance2');

this is a nontrivial issue. i'm not sure where the @todo belongs, but when we have two blocks with the same weight being remapped into the same region, they're sorted (i believe) by alpha order of their config names. that's really kinda icky, though it's not something this patch needs to definitively answer, i don't think. we still need a @todo somewhere about it, though. suggestions as to where? maybe the docblock on a sorting or remapping method?

so if eff and i have both substantially worked on this, what are the rules about who can RTBC it? :P

Gábor Hojtsy’s picture

Status: Needs review » Needs work

So looks like we need a well placed @todo then for the sorting issue. I agree if the blocks share the same weight and remapped to the same region, we don't need to provide any magic solution here. The definition for that could be alphabetic.

More docs related reviews:

+++ b/core/modules/layout/lib/Drupal/layout/Config/BoundDisplayInterface.phpundefined
@@ -0,0 +1,78 @@
+
+interface BoundDisplayInterface extends DisplayInterface {

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayInterface.phpundefined
@@ -0,0 +1,39 @@
+
+interface DisplayInterface {

+++ b/core/modules/layout/lib/Drupal/layout/Config/UnboundDisplayInterface.phpundefined
@@ -0,0 +1,36 @@
+
+interface UnboundDisplayInterface extends DisplayInterface {

Lacks docs.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.phpundefined
@@ -0,0 +1,130 @@
+   *
+   * @return array
+   */

What kind of array is returned? :)

+++ b/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.phpundefined
@@ -0,0 +1,194 @@
+  /**
+   * Perform block remapping per mapBlocksToLayout(), but mutate this object
+   * with the remapping results instead of returning them.

Any way to shorten this?

+++ b/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.phpundefined
@@ -0,0 +1,194 @@
+  public function getLayoutInstance() {
+    if ($this->layoutPlugin === NULL) {

Public function but no docs?

+++ b/core/modules/layout/lib/Drupal/layout/Tests/DisplayInternalLogicTest.phpundefined
@@ -0,0 +1,133 @@
+/**
+ * Tests the API and internal logic offered by Displays.
+ *
+ */

Extra newline.

effulgentsia’s picture

so if eff and i have both substantially worked on this, what are the rules about who can RTBC it?

I feel like my changes have been fairly minor, so if you agree with them, I feel comfortable RTBC'ing. I'd like to do one more pass on the patch this afternoon.

effulgentsia’s picture

if the blocks share the same weight and remapped to the same region, we don't need to provide any magic solution here. The definition for that could be alphabetic.

It matches what we've done for years for module hook invocation order: order by weight, name; so I agree it's an ok thing to do here as well.

yched’s picture

@sdboyer

turning Displays effectively into a block factory has its own separation-of-concern drawbacks in the long term

FWIW, in the "EntityDisplay" work that has started in #1830868: Store entity display settings in EntityDisplay config objects, I'm also considering whether the EntityDisplay object (that basically stores which field is displayed where and how) should also take care of creating and persisting instanciated formatter plugins.

I can't really think of another good place to store instantiated formatters, and keeping them close to the definition from which they were instanciated adds runtime consistency (like, you can wipe them if the definition changes).

Just food for thought for later discussions, when we try to bring the two systems closer. This doesn't have to affect the status of this issue.

sdboyer’s picture

FileSize
31.26 KB
5.8 KB

@Gábor Hojtsy - i've added docblocks on the interfaces themselves. almost all of the other cases are ones where the proper docblock is on the interface's declaration of the method. grumble grumble: one more example of why i dislike our docs standards around this :( by including a child docblock at all, we're suggesting that there may be some additional information there, when really the parent docblock actually covers it all.

i've also added another method - DisplayInterface::getAllRegionTypes(), which returns an indexed array containing a list of all the unique region types to which the Display's contained blocks have been assigned. it'll be handy for a few cases i've got in mind, and it's dead simple.

@effulgentsia - cool, then maybe after your next pass, i'll RTBC. or you'll RTBC. but do your final pass :)

@yched - thanks for the heads-up. yeah, there really isn't another good place to store them, so if we DO make anything their pseudo-factory, Displays are the place to do it. there's an even stronger argument to be made for it once we get some of the batch-loading logic in place, so that Displays can minimize the number of queries required to load all blocks from config. i just don't want to touch it until i/others are actually WORKING on client code that needs it. premature API design and all that.

sdboyer’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work
Issue tags: -Dynamic layouts

The last submitted patch, display-1817044-73.patch, failed testing.

sdboyer’s picture

Status: Needs work » Needs review
Issue tags: +Dynamic layouts

#73: display-1817044-73.patch queued for re-testing.

effulgentsia’s picture

FileSize
14.61 KB
32.05 KB

I did one more pass on docs and variable names, and am pretty happy with this. RTBC anyone?

effulgentsia’s picture

Status: Needs review » Reviewed & tested by the community

@effulgentsia - cool, then maybe after your next pass, i'll RTBC. or you'll RTBC.

Ok, I'll take option 2.

webchick’s picture

Assigned: Unassigned » catch
Category: feature » task
Priority: Normal » Major

This is holding up a ton of work on a UI for blocks and layouts, so escalating to major, and it's also not something that would be defined by feature freeze (it's a necessary component of SCOTCH), so switching to a task.

I would LOVE to commit this, but due to performance concerns cited above, I think it's best to try and let catch have a pass at this before committing. If that doesn't happen in 24 hours or so, then I'll go ahead and commit it anyway, and we can deal with any concerns in a follow-up.

webchick’s picture

Also, please make sure to credit at least Gábor and yched on commit. (in addition to everyone else Dreditor picks up)

catch’s picture

I've discussed the overall structure of this several times with sdboyer and have been really happy with it.

I'll not commit this yet, but unassigning myself to make it clear I'm happy for webchick to when she wakes up (or I might later but there's a lot of stuff in the RTBC queue at the moment).

Comments follow, if these aren't addressed before commit then could we open a follow-up? I do agree with trying to get this in asap to unblock other work.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.phpundefined
@@ -0,0 +1,134 @@
+   * Block instance configuration is stored in a separate config object. This
+   * array is keyed by the config name that uniquely identifies each block
+   * instance. At runtime, various object methods will retrieve this additional

I'm definitely worried about this in the 'fields formatters are a block' case, since we've only just got to the point where field instance configuration caching is performant about a month ago. However on the assumption that we absolutely will not convert view modes + fields to this model without also avoiding hundreds of config() calls for separate objects, I'm OK with punting on that for now. I'm wondering if there's a compromise between re-usable instances across displays vs. allowing a block instance to be configured that only exists on a specific display (i.e. analogous to field configuration on display modes now) - so the display would inject that locally stored config into the block, or get it separately from the config system, dependent on how which type it is.

For top-level blocks on a page this doesn't seem too bad compared to all the other CMI conversions going on - i.e. that's already an extremely serious problem that has a critical task to fix it, and it's not made significantly worse here - just more of the same. block_list() has never been a thing of performance beauty either (hence mongodb_block module iirc), and at least by centralizing all this stuff we have the potential for optimizing all of it as well.

effulgentsia made a good point in the geenral field formatters as blocks discussion already - we should try to optimize blocks up the wazoo first, then look at other solutions for fields if that's not possible - but in the process we can probably improve things a bit for blocks even if it's not good enough. The idea of issuing full sub-requests for field formatters fills me with a deep deep horror so let's not ever get anywhere close to there please ;)

+   *      'block2-configkey' => array(
+   *        'region' => 'sidebar_first',
+   *        'region-type' => 'aside',
+   *        'weight' => -100,
+   *      ),
+   *      'block2-configkey' => array(
+   *        'region' => 'sidebar_first',
+   *        'region-type' => 'aside',
+   *        'weight' => 0,

When would two blocks be assigned to the same region, but to different region types? I can't really see the reason for the duplication here.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.phpundefined
@@ -0,0 +1,134 @@
+   *        // store the region type name here so that we can do type conversion w/out
+   *        // needing to have access to the original layout plugin

Well I guess that's it. In that case, why would access to the original layout plugin be a problem? Is that to handle the case where it goes missing or you're importing something into a site that's missing it?

If we absolutely can't have the original layout plugin available, couldn't we store the region -> type mapping in a separate key from the blocks so it only appears once in the file (and is optional)?

+      // Finally, fall back to dumping everything in the layout's first region.
+      else {
+        $info['region'] = reset($layout_regions_indexed);
+      }

Since $layout_regions_indexed is only used in the fallback case, we should do the array_keys() inside the else { statement instead of the top of the function. Or could probably remove the intermediate variable by just using key() (or key(array_values()) if there really is a chance the array pointer needs to be reset).

The actual logic of region first, then region type, then whatever seems great though. Was thinking last region might be better than first on most displays as the last resort but that's not important.

+++ b/core/modules/layout/lib/Drupal/layout/Config/DisplayBase.phpundefined
@@ -0,0 +1,134 @@
+  public function getAllRegionTypes() {
+    $types = array();
+    foreach ($this->blockInfo as $info) {
+      $types[$info['region-type']] = TRUE;
+    }
+    return array_keys($types);

I'd probably build a normal array then use array_unique() instead of relying on non-duplicate string keys and array_keys()? Or is there a particular reason to not do that?

+++ b/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.phpundefined
@@ -0,0 +1,186 @@
+    foreach ($block_info as &$info) {
+      unset ($info['region']);
+    }

Stray space here after unset

+++ b/core/modules/layout/lib/Drupal/layout/Plugin/Core/Entity/Display.phpundefined
@@ -0,0 +1,186 @@
+    if (!$entity instanceof UnboundDisplayInterface) {
+      throw new \Exception(sprintf('Attempted to create an unbound display using an invalid entity type.'), E_RECOVERABLE_ERROR);

Wondering if we couldn't reduce the amount of stuff that an Entity has to be instantiated with, then just pass a full object in here and add this information to it. That way it could use type-hinting on the method instead of instanceOf?

sdboyer’s picture

FileSize
32.16 KB
1.74 KB

I'm OK with punting on that for now. I'm wondering if there's a compromise between re-usable instances across displays vs. allowing a block instance to be configured that only exists on a specific display (i.e. analogous to field configuration on display modes now) - so the display would inject that locally stored config into the block, or get it separately from the config system, dependent on how which type it is.

great. we've discussed having block config stored locally on a display in some cases in the past, and it's something that i think could be viable, though it eliminates the possibility of block reuse and adds another dimension of complexity to the system, so i'm trying to avoid ti for now :P

it's worth noting that i also discussed this problem with heyrocker at BAD Camp, and we eventually got to discussing a solution that would involve a specialized cache table which will effectively allow us to do a single query to fetch all referenced block instance configurations, then inject that config into the blocks and build them directly, rather than firing a config() call for each one. much more performant, and Crell has also begun asking for that (#1535868-195: Convert all blocks into plugins) from an architecture standpoint, too. per my back-and-forth with @yched in #72 and #73, that will probably mean refactoring Display classes a bit to act as true factories for blocks, but that's fine; we can do that as we get there.

When would two blocks be assigned to the same region, but to different region types? I can't really see the reason for the duplication here.

there actually may be some legitimate cases, though it all really has to do with remapping, as your next comment has noted...

Well I guess that's it. In that case, why would access to the original layout plugin be a problem? Is that to handle the case where it goes missing or you're importing something into a site that's missing it?

sorta - that's really what UnboundDisplayInterface classes are for: they let you specify a display where there is no layout binding, and so there is no region key at ALL in those cases - only region-type. the use case for those is primarily module/distro/upstream devs who want to provide a display without needing a particular layout plugin to be available, which then can be transformed into a fully bound display, using the provided methods logic, somewhere around install-time.

If we absolutely can't have the original layout plugin available, couldn't we store the region -> type mapping in a separate key from the blocks so it only appears once in the file (and is optional)?

the thought hadn't occurred to me; i suppose we could, though that wouldn't make any sense for unbound displays. so it'd mean the logic in the base class could no longer really be shared. i think it's more elegant to retain the same datastructure for both cases, even if it is somewhat redundant for bound displays.

Since $layout_regions_indexed is only used in the fallback case, we should do the array_keys() inside the else { statement instead of the top of the function. Or could probably remove the intermediate variable by just using key() (or key(array_values()) if there really is a chance the array pointer needs to be reset).

sure. updated. it's worth noting this really isn't critical path logic, as i do not anticipate layout remapping being something that's done during normal requests. it's conceivably possible people might build a site that way...but i doubt we'd see it for at least a year or two after release, and even then it will definitely not be the normal case.

I'd probably build a normal array then use array_unique() instead of relying on non-duplicate string keys and array_keys()? Or is there a particular reason to not do that?

no reason at all. patch is updated.

Wondering if we couldn't reduce the amount of stuff that an Entity has to be instantiated with, then just pass a full object in here and add this information to it. That way it could use type-hinting on the method instead of instanceOf?

yeah, i suppose that would work. i think it'd actually be better, probably, as we're cheating a bit on the use of interfaces here. i need to run, though, so i'll get that up in a subsequent patch a little later today.

catch’s picture

Assigned: catch » Unassigned

Failed to unassign myself. Didn't look at the new patch but thanks for the response, all those make sense!

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Awesome. Committed and pudshed to 8.x. Thanks!

Gábor Hojtsy’s picture

Issue to implement master display configuration: #1841584: Add and configure master displays. Issue to implement master display based page display configuration: #1840500: Add simple blank page creation capability.

sdboyer’s picture

we're discussing the renaming of a number of these thingers, displays included (which i'm quite open to) over in #1841824: Terminology: figure out proper words to use in the UI for describing unpopulated layout vs. populated layout.

yched’s picture

Sorry, was out of town for a couple days.

re @catch #81:

I'm definitely worried about this in the 'fields formatters are a block' case

Yeah, so am I, but for now the work that started on EntityDisplays (#1830868: Store entity display settings in EntityDisplay config objects - in a sandbox for now, still in progress) stays out of the block concept entirely. The EntityDisplay object directly stores display properties for the entity components (fields and 'extra fields').

We'll see down the road if "speeding light blocks" can happen, but for now that's the only viable way to rework entity rendering without massive perf impact.

yched’s picture

Automatically closed -- issue fixed for 2 weeks with no activity.