Problem/Motivation

In page_manager, there is a concept of a "variant". Usually used with "pages", they can be used to provide alternate configurations and are selected based on certain conditions.

For example, they can present different configurations of blocks to users with different roles.

Usually they collaborate with a layout, and are stored by an entity.

However, they are standalone plugins, and can be instantiated directly, and can choose to not hand off to a layout.

In contrib, these can be used by modules like Panels, Context, Panels Everywhere, Panelizer, and possibly even Display Suite.

This way, both Blocks and contrib "replacements" will use the same API.

Proposed resolution

Add the basic interfaces and plugin manager for variants.

Write a "Full Page" variant and use it to replace the internal logic of block_page_build(), block_list(), and block_get_blocks_by_region().

Remaining tasks

N/A

User interface changes

N/A

API changes

API addition:
New plugin type
API change:
Removed block_list() and block_get_blocks_by_region().

Comments

tim.plunkett’s picture

Status: Active » Needs review
StatusFileSize
new31.37 KB

Here it is. There are some things that might be overkill/leftover from page_manager, but the changes to block.module are the exciting part.

larowlan’s picture

+ return \Drupal::service('uuid'); +
The full page variant could override this and inject the uuid service, it already implements container factory plugin interface

Looks great

tim.plunkett’s picture

StatusFileSize
new31.51 KB
new2.2 KB

Fair enough!

tim.plunkett’s picture

StatusFileSize
new20.03 KB

Okay, talked over with @EclipseGc, and we agreed to remove the block and region handling. Not all variants will need it.

This should be the bare minimum.

Since I was essentially just deleting code, an interdiff wasn't really possible.

tim.plunkett’s picture

StatusFileSize
new25.82 KB
new9.62 KB

Injected the view builder, and cleaned up/expanded the unit tests.

tim.plunkett’s picture

StatusFileSize
new25.84 KB
jibran’s picture

+++ b/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php
@@ -0,0 +1,146 @@
+class FullPageVariant extends VariantBase implements ContainerFactoryPluginInterface {

class doc missing.

tim.plunkett’s picture

StatusFileSize
new26.4 KB
new6.06 KB

Added a doc block, and injected the theme negotiator instead.

eclipsegc’s picture

Status: Needs review » Reviewed & tested by the community

I don't see anything in here that makes me think we shouldn't commit this now.

Eclipse

moshe weitzman’s picture

Looks good. The @Annotation and @inheritdoc blocks make me want to poke my eyes out but that's not invented here.

+    $page += \Drupal::service('plugin.manager.display_variant')
+      ->createInstance('full_page')
+      ->render();

The method should be called 'build'. We have been careful in the past to reserve the word render to refer to the process of turning a render array into HTML. In this case we are building the render array, not rendering it.

tim.plunkett’s picture

StatusFileSize
new2.69 KB
new26.43 KB

Fair point, thanks @moshe weitzman!

Also noticed a stray comment about block_list().

tim.plunkett’s picture

StatusFileSize
new26.43 KB
new572 bytes

It's been a long week.

eclipsegc’s picture

Still RTBC.

Eclipse

benjy’s picture

Awesome, I like this issue. :)

+++ b/core/lib/Drupal/Core/Display/VariantBase.php
@@ -0,0 +1,133 @@
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, array &$form_state) {
+  }

Do we need this if it's empty?

tim.plunkett’s picture

It's a nice thing to have, to ease the burden of subclasses. See the other base classes implementing PluginFormInterface, or even FormBase.

Without that, then every variant would need an empty validate method.

dawehner’s picture

Probably this patch should have been waved through, in that case sorry. Can we please add a tag for that?

  1. +++ b/core/lib/Drupal/Core/Display/Annotation/DisplayVariant.php
    @@ -0,0 +1,35 @@
    +
    +/**
    + * Defines a display variant annotation object.
    + *
    + * @Annotation
    + */
    +class DisplayVariant extends Plugin {
    
    +++ b/core/lib/Drupal/Core/Display/VariantBase.php
    @@ -0,0 +1,133 @@
    +/**
    + * Provides a base class for DisplayVariant plugins.
    + */
    +abstract class VariantBase extends PluginBase implements VariantInterface {
    
    +++ b/core/lib/Drupal/Core/Display/VariantInterface.php
    @@ -0,0 +1,77 @@
    +/**
    + * Provides an interface for DisplayVariant plugins.
    + */
    +interface VariantInterface extends PluginInspectionInterface, ConfigurablePluginInterface, PluginFormInterface {
    
    +++ b/core/modules/block/tests/src/Display/FullPageVariantTest.php
    @@ -0,0 +1,146 @@
    +/**
    

    Nothing in this patch explains what a display variant actually means.

  2. +++ b/core/lib/Drupal/Core/Display/VariantInterface.php
    @@ -0,0 +1,77 @@
    +  /**
    +   * Determines if this display variant is accessible.
    +   *
    +   * @return bool
    +   *   TRUE if this display variant is accessible, FALSE otherwise.
    +   */
    +  public function access();
    

    Is there a reason why the account is not passed in here? This seems to be something which could be done in a follow up,
    but why should we ... this patch introduces new code already.

  3. +++ b/core/lib/Drupal/Core/Display/VariantManager.php
    @@ -0,0 +1,37 @@
    +    parent::__construct('Plugin/DisplayVariant', $namespaces, $module_handler, 'Drupal\Core\Display\Annotation\DisplayVariant');
    

    OT: Did we ever considered to move the namespace, annotation class, cache key and alter hook name into parameters in the service definition?

  4. +++ b/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php
    @@ -0,0 +1,160 @@
    + * Contains \Drupal\block\Plugin\DisplayVariant\FullPageVariant.
    ...
    +class FullPageVariant extends VariantBase implements ContainerFactoryPluginInterface {
    
    +++ b/core/modules/block/tests/src/Display/FullPageVariantTest.php
    @@ -0,0 +1,146 @@
    + * @file
    + * Contains \Drupal\block\Tests\Display\FullPageVariantTest.
    + */
    

    Don't we no longer sync the namespace in the actual code and in the tests?

  5. +++ b/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php
    @@ -0,0 +1,160 @@
    +   */
    +  protected function getTheme() {
    +    if (!$this->theme) {
    +      $this->theme = $this->themeNegotiator->getActiveTheme();
    +    }
    +    return $this->theme;
    +  }
    

    Doesn't the theme negotiator provides its own caching? One less variable is one less possible static caching bug.

  6. +++ b/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php
    @@ -0,0 +1,160 @@
    +    foreach ($this->getRegionAssignments() as $region => $blocks) {
    +      /** @var $blocks \Drupal\block\BlockInterface[] */
    ...
    +   * @return array
    +   *   The array is first keyed by region machine name, with the values
    +   *   containing an array keyed by block ID, with block entities as the values.
    +   */
    +  protected function getRegionAssignments() {
    

    If getRegionAssignments() would document the return array the inline docs would not be needed ...

  7. +++ b/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php
    @@ -0,0 +1,160 @@
    +      @uasort($assignment, 'Drupal\block\Entity\Block::sort');
    

    Should we better document why we have to hide errors here? This does not seems to be obvious for me.

  8. +++ b/core/modules/block/tests/src/Display/FullPageVariantTest.php
    @@ -0,0 +1,146 @@
    + * Tests the full page display variant.
    + *
    + * @group Drupal
    + * @group Block
    + * @group Display
    + */
    +class FullPageVariantTest extends UnitTestCase {
    ...
    +  /**
    +   * Tests the rendering of a full page variant.
    +   */
    +  public function testRender() {
    

    Missing @covers annotations.

  9. +++ b/core/modules/block/tests/src/Display/FullPageVariantTest.php
    @@ -0,0 +1,146 @@
    +    $this->themeNegotiator = $this->getMockBuilder('Drupal\Core\Theme\ThemeNegotiator')
    +      ->disableOriginalConstructor()
    +      ->getMock();
    

    Can't we use the interface? This might be a leftover from my previous fuckups.

tim.plunkett’s picture

5) I cannot write \Drupal\block\BlockInterface[][], double nesting does not work.
9) getActiveTheme() is not on the interface

I can clean up some of the other bits tomorrow, but I believe they are minor.

And I don't know what you mean about waving this through.

tim.plunkett’s picture

StatusFileSize
new27.45 KB
new5.15 KB

1) Fair
2) Fixed
3) No, but that's an interesting idea.
4) Fixed
5) Fixed
6) As I said, I wish I could do \Drupal\block\BlockInterface[][] but I cannot.
7) Because PHPUnit invocation matching triggers https://bugs.php.net/bug.php?id=50688 :(
8) As I said, getActiveTheme() is only on ThemeNegotiator, not on ThemeNegotiatorInterface.

Just minor clean ups, still RTBC.

andypost’s picture

  1. +++ b/core/modules/block/block.module
    @@ -125,33 +112,6 @@ function block_page_build(&$page) {
    - * Gets a renderable array of a region containing all enabled blocks.
    ...
    -function block_get_blocks_by_region($region) {
    
    @@ -242,43 +202,6 @@ function block_theme_initialize($theme) {
    - * Returns all blocks in the specified region for the current user.
    ...
    -function block_list($region) {
    
    +++ b/core/modules/block/src/Plugin/DisplayVariant/FullPageVariant.php
    @@ -0,0 +1,157 @@
    +class FullPageVariant extends VariantBase implements ContainerFactoryPluginInterface {
    ...
    +  protected function getRegionAssignments() {
    

    Is API change, also leaving no API to get regions and their blocks

  2. +++ b/core/modules/system/system.api.php
    @@ -600,6 +600,19 @@ function hook_contextual_links_plugins_alter(array &$contextual_links) {
    +  $definitions['full_page']['admin_label'] = 'Block layout';
    

    admin label better to wrap in t()

tim.plunkett’s picture

Those were not API functions to begin with, and you can still get regions and blocks the normal way, see system_region_list and entityQuery.

To your second point, it's in *.api.php , doesn't matter.

dawehner’s picture

Those were not API functions to begin with, and you can still get regions and blocks the normal way, see system_region_list and entityQuery.

Not sure how fair this is tbh. Drupal people have a massive amount of missuage of all the "API"s.

7) Because PHPUnit invocation matching triggers https://bugs.php.net/bug.php?id=50688 :(

Is there a chance we document that?

berdir’s picture


OT: Did we ever considered to move the namespace, annotation class, cache key and alter hook name into parameters in the service definition?

Yes, this was considered and discussed in Portland, whThiere I worked on DefaultPluginManager. However, the needed arguments were a bit more complex back then (an array), and it was not possible to have everything in services.yml, and @yched was against splitting this up.

Seems like it should be possible not, would be quite nice. We would have to make those methods public, though, I think they are protected right now.

tim.plunkett’s picture

Issue tags: +API change, +Needs change record
StatusFileSize
new27.58 KB
new813 bytes

I will write a change notice for those two functions.
Added a comment about the @uasort.

A new issue for #23 would be worth looking at, but is really OT here.

tim.plunkett’s picture

wim leers’s picture

Having worked quite a bit on BlockViewBuilder and touched block_get_blocks_by_region() etc., the overall approach and changes in this patch look great!

tim.plunkett’s picture

StatusFileSize
new28.2 KB
new6.86 KB

Reroll for the RouteMatch commit. This now addresses #17.5, since we're using the interface directly.

Also, we now have a core.api.php for subsystems, so I moved the hook docs there after checking with @jhodgdon.

tim.plunkett’s picture

Issue summary: View changes
webchick’s picture

Status: Reviewed & tested by the community » Fixed

Spent a good hour on this with Tim, mostly with docs. The patch itself is pretty straight-forward... it introduces a new concept of a "display variant" and uses one of them ("full page") to render out full page views from Block module, in lieu of calling functions like block_get_blocks_by_region(). Apart from that change it's 90% copy/paste boilerplate code that you need to implement a plugin, and then tests.

We struggled with the docs of "display variant" for awhile though. It's tricky because core doesn't (yet) use the concept of displays; that may come or not as a result of #2292733: [meta] Introduce Display objects. And display variants themselves are incredibly generic, and can be used for everything from a full page display ala Block module to a single "block" content ala mini panels to even a simple http response code type of deal. In the end, we settled for a description of what HEAD does with this patch as an example, and kind of left it at that. I think that's enough for someone who comes across this term and says "wtf?" to have a reasonable shot at being directed how to read up on how it works, and this entire description will get overhauled anyway when/if the display issue makes it in, so didn't make sense to go from 80% => 100% in this case.

Apart from that, the only things that stood out to me were the losing of some "orientation" comments around the removed code in block_page_build (restored a one-liner there) and the @uasort thing that's already been mentioned above, but that's got docs next to it explaining what's going on.

This is a nice change overall that's not very invasive on its own, but nevertheless introduces some very nice flexibility that hopefully all of the various "core block/layout replacement" modules out there can make use of, without each inventing their own wheels. Change notice looks fine.

Ergo, committed and pushed to 8.x (with some docs modifications). Thanks!

  • webchick committed 539061c on 8.x
    Issue #2286357 by tim.plunkett: Introduce Display Variants, use for the...

Status: Fixed » Closed (fixed)

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