Problem/Motivation

Currently, the PageDisplayVariantSelectionEvent allows you to set which plugin id should be used as the DisplayVariant to render the current page.

Core only provides two variants:

  • Drupal\Core\Render\Plugin\DisplayVariant\SimplePageVariant
  • Drupal\block\Plugin\DisplayVariant\BlockPageVariant

Neither of them need to take configuration - SimplePageVariant is basically a no-op, and BlockPageVariant uses essentially global data.

However, VariantInterface extends ConfigurablePluginInterface, so it allows for plugins to take configuration, and any variant that works with Page Manager needs to take configuration in order to work!

So, to allow Page Manager variants to be use by core (in order to implement something like Panels Everywhere), we'd need to also set configuration on PageDisplayVariantSelectionEvent too.

Proposed resolution

I propose adding setPluginConfiguration(array $configuration) and getPluginConfiguration() methods to PageDisplayVariantSelectionEvent, which would be used by Drupal\Core\Render\MainContent\HtmlRenderer::prepare() when instantiating the variant plugin.

Remaining tasks

  1. Write the patch
  2. Write PoC of Panels Everywhere demonstrating how this would be used
  3. Get reviews
  4. Commit!

User interface changes

None.

API changes

Adding a getter and setter to PageDisplayVariantSelectionEvent.

This won't affect core because none of the variants take configuration.

However, this will allow us to write Panels Everywhere for Drupal 8!

Data model changes

None.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because Variants can take configuration (they extend ConfigurablePluginInterface) and some variants (like the Panels and Page manager variants) requires it. But the PageDisplayVariantSelectionEvent class in core doesn't provide a way to pass that configuration, making it impossible to fulfill the Variants API. I suspect most variants provided by contrib will take configuration!
Issue priority Major because it renders this event useless for any Variant that needs configuration, which will likely be any Variant provided by contrib. The core variants are kind of exceptional in NOT needing configuration.
Disruption Disruptive for core and contrib will be minimal because this only adds a new getter/setter, it doesn't change any existing method signatures.
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dsnopek’s picture

Title: PageDisplayVariantSelectionEvent should allow setting configuration for the Variant (to allow Page Manager variants to be used by core and more easily enable Panels Everywhere) » VariantInterface extends ConfigurablePluginInterface so PageDisplayVariantSelectionEvent should allow passing configuration to the Variant (to enable Panels Everywhere)
Category: Feature request » Bug report
Issue summary: View changes

Issue summary update and better (ie. shorter) title.

dsnopek’s picture

Issue summary: View changes

Added writing a PoC of Panels Everywhere to the task list.

dsnopek’s picture

Status: Active » Needs review
FileSize
2.93 KB

Here's my first pass at a patch!

dsnopek’s picture

Issue summary: View changes
dsnopek’s picture

+++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php
@@ -42,6 +49,7 @@ class PageDisplayVariantSelectionEvent extends Event {
   public function __construct($plugin_id, RouteMatchInterface $route_match) {
     $this->pluginId = $plugin_id;
+    $this->pluginConfiguration = array();
     $this->routeMatch = $route_match;
   }

I didn't add an argument to the constructor for the plugin configuration to minimize API change. The current patch only adds the getter/setter and doesn't change existing APIs. But I don't know if that actually makes sense. :-)

So, I'd love some feedback on that in particular!

It's likely only core that creates this event, so it'd probably be safe to change the constuctor's signature.

dsnopek’s picture

Issue summary: View changes

Here's a proof-of-concept implementation of Panels Everywhere, which includes the config for a Page Manager Page, and an event subscriber listening on RenderEvents::SELECT_PAGE_DISPLAY_VARIANT which returns the selected variant from that page:

https://github.com/dsnopek/panels_everywhere_poc

It won't work with the current 8.x-3.x-dev of Panels, because we still need to solve #2511570: Remove Drupal\page_manager\Plugin\PageAwareVariantInterface, but as a work around to see it working now, you can comment out the foreach loop in PanelsDisplayVariant::getContextAsTokenData().

This also includes its own display variant plugin, but this really shouldn't be necessary -- PanelsDisplayVariant should implement PageVariantInterface and do the extra work to support that. No issue created for that yet.

In any case, the most important bit is this code in the event subscriber:

  /**
   * Selects the page display variant.
   *
   * @param \Drupal\Core\Render\PageDisplayVariantSelectionEvent $event
   *   The event to process.
   */
  public function onSelectPageDisplayVariant(PageDisplayVariantSelectionEvent $event) {
    $page = $this->entityStorage->load('site_template');
    if ($variant = $page->getExecutable()->selectDisplayVariant()) {
      if ($variant instanceof PageVariantInterface) {
        $event->setPluginId($variant->getPluginId());
        $event->setPluginConfiguration($variant->getConfiguration());
      }
    }
  }

In this file: https://github.com/dsnopek/panels_everywhere_poc/blob/master/src/EventSu...

It shows how it's selecting the variant from the Page Manager Page. Theoretically, there could be several variants with different selection conditions. When it finds the right one, it gives the event the plugin id and the configuration, and HtmlRenderer will use it to render the page!

andypost’s picture

the main question here why the event needs this method because page variant is instantiated not here so event just selects plugin then it should get config in some other place

and minor nits

  1. +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
    @@ -174,6 +174,7 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
    +      $variant_config = $event->getPluginConfiguration();
    
    @@ -201,6 +202,9 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
    +      if (!empty($variant_config)) {
    

    if ($vc = $event->get..
    should be enough, better not spread get and usage in 30 lines

  2. +++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php
    @@ -26,6 +26,13 @@ class PageDisplayVariantSelectionEvent extends Event {
    +  protected $pluginConfiguration;
    
    @@ -42,6 +49,7 @@ class PageDisplayVariantSelectionEvent extends Event {
    +    $this->pluginConfiguration = array();
    

    protected $pluginConfiguration = [];

dsnopek’s picture

Priority: Normal » Major
Issue summary: View changes
Issue tags: -Needs beta evaluation
FileSize
2.96 KB
2.14 KB

@andypost: Thanks for the review! Here's a new patch. :-)

I also added the beta evaluation to the issue summary! I'd appreciate feedback on that. I ended up changing the priority to Major, since that seemed to apply more than "prioritized change."

However, I'm not sure about the tests. Do we really need to test a simple getter and setter added to the event object? And the change to to HtmlRenderer is just passing the data from one getter to another setter. The amount of code required to test that would be large, and I think the value would be minimal. But if necessary I can add some tests!

Thanks again!

dsnopek’s picture

Er, sorry, I messed up rolling that patch. Here's a better one! The interdiff on #8 is correct, though.

dsnopek’s picture

Oh, jeez, my Git-fu is failing me this morning! Not sure how I could get the interdiff right, and the patch so wrong, but THIS one should be right. Sorry for all the extra comments.

Intediff on #8 still correct.

dsnopek’s picture

About adding tests:

I don't think it makes sense to unit test the getter and setter added to PageDisplayVariantSelectionEvent, since we aren't testing that getters and setters work anywhere else in core (at least that I could find). I could see adding tests for the change to HtmlRenderer::prepare(), however, there aren't currently any unit tests for HtmlRenderer at all and it's calling \Drupal::theme() directly, so at the moment it can't be tested. I created a new issue for that: #2535212: Add test coverage for HtmlRenderer

@tim.plunkett suggested creating a web test (rather than a unit test) that provides a variant that needs configuration, and makes sure that it was passed when the page was rendered. So, I'll take a stab at that next!

dsnopek’s picture

Alright! Here's a new patch with tests like the ones that @tim.plunkett recommended.

dsnopek’s picture

As recommended by @tim.plunkett, here's a FAIL patch that has the change to HtmlRenderer::prepare() removed to show that the tests fail. And I've attached a PASS patch too, which is identical to the patch in #12, but hopefully it'll reset the testbot to passing.

The last submitted patch, 13: drupal8-variant-selection-event-config-2512062-13-FAIL.patch, failed testing.

tim.plunkett’s picture

+++ b/core/modules/system/tests/modules/display_variant_test/src/Plugin/DisplayVariant/TestDisplayVariant.php
@@ -0,0 +1,54 @@
+      throw \Exception('Required configuration is missing!');

Even though this won't run (except in the test-only), it should be throw new \Exception (missing new)

dsnopek’s picture

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

Looks great, thanks!

Wim Leers’s picture

Status: Reviewed & tested by the community » Needs review

Why was this not necessary so far in the D8 port of Page Manager? Because it was only a partial port?


  1. +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
    @@ -192,6 +192,9 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
    +      if ($variant_config = $event->getPluginConfiguration()) {
    +        $page_display->setConfiguration($variant_config);
    +      }
    

    The test coverage tests the case of $variant_config not being empty. Shouldn't we test the other case as well?

    Or… can this be simplified to:

    $page_display->setConfiguration($event->getPluginConfiguration());
    

    ?

  2. +++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php
    @@ -65,6 +72,25 @@ public function getPluginId() {
    +  public function setPluginConfiguration(array $configuration) {
    ...
    +  public function getPluginConfiguration() {
    

    These are generic names. IDK what the convention is, but given that this event only applies to a specific plugin type, shouldn't we be more specific here?

    i.e. (get|set)PageDisplayVariantConfiguration?

dsnopek’s picture

@Wim Leers: Thanks for the review! :-)

Why was this not necessary so far in the D8 port of Page Manager?

Page Manager (in both D7 and D8) allows you to take over the main content of the page. It's the Panels Everywhere module that allows you to take a Page Manager "Page" and use it to replace the whole page.

I think this is the first time that someone has tried to implement Panels Everywhere. With the change on this issue, it actually becomes quite elegant (as opposed to a hack in D7).

  1. Or… can this be simplified to:
    $page_display->setConfiguration($event->getPluginConfiguration());
    

    Yes it can! My thinking was just to avoid calling the setter if there was nothing to set. But here is a new patch that does it that way.

  2. These are generic names. IDK what the convention is, but given that this event only applies to a specific plugin type, shouldn't we be more specific here?

    i.e. (get|set)PageDisplayVariantConfiguration?

    Well, (get|set)PluginConfiguration() matches the existing (get|set)PluginId() method names. You end up using it like:

    $event->setPluginId('variant_plugin');
    $event->setPluginConfiguration(['config' => 'value']);
    

    So, if we give setPluginConfiguration() a more specific name, then to match, we really should give setPluginId() a more specific name. However, then this is an API change!

    In the interest of not making an API change, but only an API addition, I think we should leave this alone. But I don't really care about the names and would be happy to go with whatever the consensus is!

Wim Leers’s picture

  1. Well, I'm not saying it should be that way, I'm just saying it could :) I'll leave it up to you & Tim to decide that.
  2. Good points! Let's leave it as-is then.

  1. +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
    @@ -192,6 +192,7 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
    +      $page_display->setConfiguration($event->getPluginConfiguration());
           $page_display->setMainContent($main_content);
    

    If we're gonna do it this way, then let's chain it.

  2. +++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php
    @@ -65,6 +72,25 @@ public function getPluginId() {
       /**
    +   * Set the configuration for the selected page display variant.
    +   *
    +   * @param array $configuration
    +   *   The configuration for the selected page display variant.
    +   */
    +  public function setPluginConfiguration(array $configuration) {
    +    $this->pluginConfiguration = $configuration;
    +  }
    

    Should return $this. Then you will actually be able to chain it.

  3. +++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php
    @@ -65,6 +72,25 @@ public function getPluginId() {
    +   * @return array;
    

    Trailing semi-colon that doesn't belong there :)

dsnopek’s picture

@Wim: Thanks again for the review!

#20.1: Done!

However, I'm not sure on the coding standards for doing chaining. Should both the setters be on their own line? For example:

$page_display
  ->setMainContent($main_content)
  ->setConfiguration($event->getPluginConfiguration());

I went the other way in the patch, but I can switch it?

#20.2: Done! Changed setPluginId() to return $this as well. Hopefully, changing the return value from NULL to $this qualifies as an API addition, rather than an API change?

#20.3: Done!

So, this patch has gotten a little messier - the one from #16 changed fewer things, but hopefully the changes are still minor enough to make this easy to get in. :-)

Status: Needs review » Needs work

The last submitted patch, 21: drupal8-variant-selection-event-config-2512062-21.patch, failed testing.

The last submitted patch, 21: drupal8-variant-selection-event-config-2512062-21.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
1.42 KB
8.43 KB

SimplePageVariant::setMainContent violated its interface.

Also, I think we generally do chaining each on its own line.

Wim Leers’s picture

Alright, back to RTBC!

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

For realz.

dsnopek’s picture

Thanks, Tim!

Also, hiding old patches.

EclipseGc’s picture

FWIW I'm about +1000000000 to this patch.

Eclipse

alexpott’s picture

Committed 53f6c5a and pushed to 8.0.x. Thanks!

Thanks for adding the beta evaluation to the issue summary.

diff --git a/core/modules/system/tests/modules/display_variant_test/src/EventSubscriber/TestPageDisplayVariantSubscriber.php b/core/modules/system/tests/modules/display_variant_test/src/EventSubscriber/TestPageDisplayVariantSubscriber.php
index 3749339..b8154c7 100644
--- a/core/modules/system/tests/modules/display_variant_test/src/EventSubscriber/TestPageDisplayVariantSubscriber.php
+++ b/core/modules/system/tests/modules/display_variant_test/src/EventSubscriber/TestPageDisplayVariantSubscriber.php
@@ -9,7 +9,6 @@
 
 use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
 use Drupal\Core\Render\RenderEvents;
-use Drupal\Core\Display\PageVariantInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**

Unused use removed on commit.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

  • alexpott committed 53f6c5a on 8.0.x
    Issue #2512062 by dsnopek, tim.plunkett, Wim Leers: VariantInterface...
dsnopek’s picture

Woohoo! Thanks! :-)

Status: Fixed » Closed (fixed)

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