Problem/Motivation

Some display variants need to be passed an array of "resolved contexts" because they can't get all their contexts globally.

For example, in Page Manager, you can create a Page which has a specific static context that doesn't come from the global contexts (ex. a context that refers to the Node with NID 6), or create a new context based on a relationship to another context (ex. a context that refers to the Node referred to by an entity reference on the Node currently being viewed).

So, we need a way to pass these contexts to the display variant!

(Note: This is different than ContextAwarePluginInferface which allows the plugin to declare what contexts it needs, and to have contexts without values. However, the display variant just needs to be passed an associative array with contexts with values.)

Like #2512062: VariantInterface extends ConfigurablePluginInterface so PageDisplayVariantSelectionEvent should allow passing configuration to the Variant (to enable Panels Everywhere), this is a blocker to doing Panels Everywhere in Drupal 8: if we don't have a way to pass contexts to the display variant, then Panels Everywhere can only use global context, but not static context or relationships! This is a bug in the Variant API.

While it would make sense for core to use this in its BlockPageVariant, it isn't strictly necessary, because that variant only requires global context.

Proposed resolution

  1. Move ContextAwareVariantInterface and ContextAwareVariantTrait from Page Manager into core
  2. Add getContexts() and setContexts() methods to PageDisplayVariantSelectionEvent
  3. Modify HtmlRender to pass the resolved contexts from the event to the variant after creation if it implements ContextAwareVariantInterface
  4. Add tests for making sure the context is passed!

We (EclipseGC and dsnopek) discussed using a convention to pass this data in the plugin configuration (as a magic 'context' key) but that isn't ideal because it's API by array key, which we're trying to get away from in Drupal 8. Of course, if we can't get this issue in core, that's exactly what we'll do. :-)

Remaining tasks

  • Create patch
  • Get review
  • Merge!

User interface changes

None.

API changes

This would be an API addition of getters/setters to a couple classes and a new interface and trait, but no API changes.

Data model changes

None.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because without allowing contexts to passed to the variant, it won't be possible to fully implement Panels Everyone in Drupal 8 using the core Variant API. While neither of the variants in core need it (they work off of no or global data) I suspect most variants provided by contrib will need to be passed context!
Issue priority Major because it renders the PageDisplayVariantSelectionEvent useless for any Variant that needs context, which will likely be any Variant provided by contrib. The core variants are kind of exceptional in NOT needing to be passed contexts.
Disruption Disruption for core and contrib will be minimal because this only adds a new getter/setter, interface and trait -- it doesn't change any existing method signatures.

Comments

dsnopek created an issue. See original summary.

dsnopek’s picture

Title: Add setters/getters for "resolved contexts" to VariantInterface and allow PageDisplayVariantSelectionEvent to pass them to HtmlRenderer » Allow passing "resolved contexts" to VariantInterface (to enable Panels Everywhere to use static context and relationships)
dsnopek’s picture

Looking at page_manager, it's already doing this with its ContextAwareVariantInterface. I'm wondering if just copying that interface (and it's companion trait) into core, would be better than adding those methods directly to VariantInterface?

dsnopek’s picture

Title: Allow passing "resolved contexts" to VariantInterface (to enable Panels Everywhere to use static context and relationships) » Allow passing contexts to display variants (to enable Panels Everywhere to use static context and relationships)
Issue summary: View changes

I decided to implement this by moving the ContextAwareVariantInterface from Page Manager, so updating title and issue summary.

dsnopek’s picture

Status: Active » Needs review
StatusFileSize
new8.16 KB

Here's my first pass at this! The tests are updated for it, but it would be cool to try using it for the block modules BlockPageVariant.

larowlan’s picture

  1. +++ b/core/lib/Drupal/Core/Display/ContextAwareVariantInterface.php
    @@ -0,0 +1,33 @@
    +interface ContextAwareVariantInterface {
    

    should this extend the existing PageVariantInterface?

  2. +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
    @@ -204,6 +205,9 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
    +      if ($page_display instanceof ContextAwareVariantInterface) {
    

    <preference class="personal">personally I feel use of instanceof is an anti-pattern that makes the caller to aware of the details of the items it is dealing with, a kind of hidden dependency - can we add these methods to PageVariantInterface and provide a base-class that implements a null op?</preference>

  3. +++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php
    @@ -33,6 +33,13 @@ class PageDisplayVariantSelectionEvent extends Event {
    +  protected $contexts = [];
    
    @@ -92,6 +99,29 @@ public function setPluginConfiguration(array $configuration) {
    +  public function getContexts() {
    ...
    +  public function setContexts(array $contexts) {
    

    If we made the trait generic, we could use it here too.

  4. +++ b/core/modules/system/tests/modules/display_variant_test/src/Plugin/DisplayVariant/TestDisplayVariant.php
    @@ -18,7 +20,9 @@
    +class TestDisplayVariant extends VariantBase implements PageVariantInterface, ContextAwareVariantInterface {
    

    If the later interface extends the former, we can simplify this

dsnopek’s picture

StatusFileSize
new8.18 KB

@larowlan: Thanks for the review!

1. Hm. I don't think PageVariantInterface, because we could want to implement a variant thats just a VariantInterface and ContextAwareVariantInterface but NOT a PageVariantInterface. However, I think it would make sense to extend VariantInterface, similar to how PageVariantInterface does! Added in new patch.

2. Well, we're already using this same pattern with PageVariantInterface in this same bit of code. If that's OK to say "this variant can render the main content block", I think it should be OK to use that pattern to say "this variant can take context."

3. I'm not sure what you mean?

4. Per point 1, I think those two interface are best left separate.

dsnopek’s picture

Priority: Normal » Major
Issue summary: View changes

Added beta evaluation.

larowlan’s picture

3. the methods|properties you add to this object exist in the trait, can we re-use the trait instead?

2. yep, I'm just registering my objection to this pattern in core, the horse has bolted on that

1. right, sorry about that, wrong interface - but there is one we can extend, sweet

4. agree

So just the question as to whether we can use the new trait instead of those methods|properties.

dsnopek’s picture

#10.3: Oooh, I understand now, it's basically the exact same code. :-) So, that would just be a matter of making the API documentation make sense in both situations, right? Let me think about how to do that...

dsnopek’s picture

StatusFileSize
new7.82 KB
new3.12 KB

Here's a new version of the patch which takes @larowlan's suggestion of using the ContextAwareVariantTrait for the event as well.

dsnopek’s picture

larowlan’s picture

+++ b/core/lib/Drupal/Core/Display/ContextAwareVariantTrait.php
@@ -0,0 +1,51 @@
+ * Contains \Drupal\Core\Plugin\Display\ContextAwareVariantTrait.
...
+trait ContextAwareVariantTrait {

Sorry to be a pain, but perhaps this should be Drupal\Core\Plugin\Context\ContextAware trait as it has re-use potentional beyond variants in my opinion

Other than that (and I rarely weigh in on naming, so sorry) - I think this is ready.

Might be worth pinging Tim Plunkett for another set of eyes.

dawehner’s picture

Good point @larowlan

dsnopek’s picture

re #14: I'd worry that making the name more generic would cause people to think they needed to use it with ContextAwarePluginInterface, which is really what you'd want to use in most cases. This interface (and hence trait) is much simpler and all that's necessary in order to pass contexts to a display variant - but too simple for most other uses of context. Having the names paired makes it easier to understand that they go together, and NOT with any of the ContextAwarePlugin*Interface's.

In fact, now that I'm thinking about it, maybe this should be about passing the array of context values, rather than an array of ContextInterface objects? That would put extra emphasis on the fact that this is a simplified case...

EDIT: Er, nevermind, that won't work because when we pass the context on to blocks (or whatever) they actually implement ContextAwarePluginInterface and we need to have real ContextInterface objects to give them. :-)

I'll ping Tim Plunkett to see what he thinks.

dsnopek’s picture

StatusFileSize
new7.55 KB
new4.43 KB

So, I'm personally still against this rename, but since everyone else seems to be for it and we really need this issue to move forward for work on Panels, I'm just going to give in and do it. :-)

Here's a new patch which renames ContextAwareVariantTrait to ContextAwareTrait.

lslinnet’s picture

Status: Needs review » Reviewed & tested by the community

This patch looks quite solid and works as described.

wim leers’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -236,6 +237,9 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
+      if ($page_display instanceof ContextAwareVariantInterface) {
+        $page_display->setContexts($event->getContexts());
+      }

+++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php
@@ -18,6 +19,8 @@
 class PageDisplayVariantSelectionEvent extends Event {
 
+  use ContextAwareTrait;
+

These are huge changes. It feels very, very strange to make this event context-aware, and to require the HTML renderer to deal with this also.

I just want to ask the question: Is this really necessary?

I think the answer is yes, and so in that case I'd like to ask you to update the docblocks of HtmlRenderer and PageDisplayVariantSelectionEvent to contain essentially the explanation in the IS.

dsnopek’s picture

I just want to ask the question: Is this really necessary?

Unfortunately, yes. Although, it's largely because of the way this event works: we're passing it the plugin id, and then all the stuff it needs to work. If we could just pass the instantiated variant object, then the event subscriber could just be responsible for getting the variant object ready? Is making a change like that a possibility?

I think the answer is yes, and so in that case I'd like to ask you to update the docblocks of HtmlRenderer and PageDisplayVariantSelectionEvent to contain essentially the explanation in the IS.

Ok, I'll update the patch in a moment!

dsnopek’s picture

Status: Needs work » Needs review
StatusFileSize
new8.32 KB
new3.03 KB

Ok! I added some information to the PageDisplayVariantSelectionEvent docblock. In the case of HtmlRenderer, I put it in a code comment near the code in question, since this implementation detail isn't visible externally, I didn't think it made sense in the docblock which would end up on api.drupal.org - but will hopefully help people who are reading it's code and wondering why it needs to do that.

Please let me know if this is sufficient or needs any changes!

lslinnet’s picture

Status: Needs review » Reviewed & tested by the community

Comment changes look good, I would agree with dsnopek that the comment for HtmlRenderer doesn't belong in the docblock but it is fine to leave it as code comment.

tim.plunkett’s picture

+++ b/core/lib/Drupal/Core/Display/ContextAwareTrait.php
@@ -0,0 +1,47 @@
+ * Contains \Drupal\Core\Plugin\Display\ContextAwareTrait.

If we're making this super generic, it's in the wrong namespace. What about just moving it up a level, out of Display ?

tim.plunkett’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new8.77 KB
new5.77 KB

Actually, once I applied the patch and looked at it more, and looked at how page_manager would look after adapting to this, we should NOT make a weird generic trait just to save on copy/paste.

The trait should be there to satisfy an interface, but one of the two uses are the Event, which doesn't use that interface.

Also, other than the test there will likely just be one other usage of the trait anywhere...

So we don't need it!

Plus, then we get to tailor the docs on the methods on the Event to the actual use case.

dsnopek’s picture

+1 on these changes! I'd RTBC it if I could. :-)

lslinnet’s picture

Was just about to raise the same concert about having this generic ContextAware trait, but in regards to name confusion with ContextAwarePluginBase which might easily could be mistaken for something that used the ContextAware trait for plugins.

+1 for this direction, code looks good and coverage of it seems sufficient

tim.plunkett’s picture

StatusFileSize
new8.77 KB
new1.95 KB

The doc blocks were backwards on the trait (they were {@inheritdoc} in page_manager), this swaps them :)

japerry’s picture

Status: Needs review » Reviewed & tested by the community

+5. thanks for fixing that switched docblock.

neclimdul’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/Display/ContextAwareVariantInterface.php
    @@ -0,0 +1,33 @@
    + * Contains \Drupal\Core\Plugin\Display\ContextAwareVariantInterface.
    + */
    +
    +namespace Drupal\Core\Display;
    

    Wrong namespace in @file doc.

  2. +++ b/core/lib/Drupal/Core/Display/ContextAwareVariantInterface.php
    @@ -0,0 +1,33 @@
    +interface ContextAwareVariantInterface extends VariantInterface {
    

    I feel like this should be just a ContextAwareInterface in Drupal\Core\Plugin\ but... I now know Tim isn't a fan of that. Not going to hold this up on this because that admittedly could open a can of worms.

  3. +++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php
    @@ -13,6 +13,10 @@
    + * It also allows you to pass along any additional information that the variant
    + * needs to work, for example: configuration, and any contexts (with values)
    + * that can't be found globally.
    + *
    

    Outside of this patch, how do I do pass along that information? I think an interface would make this clearer but we could just make the docs clearer maybe.

    "Context methods allow additional configuration and values to be passed that can't be found globally." ?

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new8.76 KB
new1.41 KB

1) Yep.

2) We have \Drupal\Core\Plugin\ContextAwarePluginInterface, \Drupal\Component\Plugin\ContextAwarePluginInterface, \Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface, and even \Symfony\Component\Routing\RequestContextAwareInterface.
Now we're adding \Drupal\Core\Display\ContextAwareVariantInterface.

The term "ContextAware" is overloaded already. How would adding ContextAwareInterface help remove confusion?

3) Came up with this in IRC after further discussion.

neclimdul’s picture

Status: Needs review » Reviewed & tested by the community

2) Because the ContextAwarePluginInterface should be small enough we could use it here. Too late to split it up into composable interfaces though I guess.

Thanks!

lslinnet’s picture

+++ b/core/lib/Drupal/Core/Render/PageDisplayVariantSelectionEvent.php
@@ -13,6 +13,11 @@
+ * Subscribers of this event can call the following setters to pass additional

This should be overwrite, based on the setContexts function which overwrites the context array with the passed in array of contexts.

tim.plunkett’s picture

That's an implementation detail. We have setters like this all over, if you care about existing values you can do:

$event->setContexts($my_contexts + $event->getContexts());

Or similar.

wim leers’s picture

Patch looks nice & simple.

+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -236,6 +237,14 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
+      // context (ex. a context that refers to the Node with nid 6), If any

Nit: I've never seen "ex." being used before.

s/If/if/

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Contributed project blocker, got walked through the patch at DrupalCon. Woot.

Committed and pushed to 8.0.x. Thanks!

  • webchick committed 3a8d44b on 8.0.x
    Issue #2550941 by dsnopek, tim.plunkett, lslinnet: Allow passing...
dsnopek’s picture

Woohoo! Thanks everyone!

Status: Fixed » Closed (fixed)

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