Problem/Motivation
In \Drupal\entity_embed\EntityHelperTrait::renderEntityEmbed():
$this->moduleHandler()->alter(array("{$context['data-entity-type']}_embed", 'entity_embed'), $build, $entity, $context);
$entity_output = $this->renderer()->render($build);
This triggered my instinctive oh, that's interesting, but … does it deal with cacheability metadata correctly?
reaction.
Problem 1: the documentation doesn't say anything about cacheability, and how altering this affects also non-embedded rendering
It should say something like:
+ * Note that any alterations made here must update the cacheability metadata in
+ * $build: otherwise alterations made for embedding an entity in a certain view
+ * mode will also affect that entity in that view mode when not embedded.
+ *
+ * It is recommended to NOT use this hook and instead create embedding-specific
+ * view modes.
Because that is the problem: Entity Embed allows you to embed an entity using a certain view mode. Great! But this hook allows you to alter it. And if you do so without modifying #cache[keys], you end up with a race condition:
- if entity X in view mode Y is rendered first outside of Entity Embed, then the Entity Embed filter will also render it like that, without the alterations made by this hook, because the CID will be the same
- if entity X in view mode Y is rendered first by Entity Embed, then it will also be rendered like that outside of Entity Embed filter, with the alterations made by this hook, because the CID will be the same
Conclusion: it'd be better to not use this hook to alter existing view modes (entity view displays), but instead have site builders create custom view modes/entity view displays. Note that this does not mean you MUST do that. It's only necessary to do so if you want the embedded entities to be rendered differently than one of the existing view modes. So you can still choose to render the teaser view mode, for example.
Problem 2: hook_entity_embed_alter() does not operate on a fully built render array despite it claiming so
So I went to look and found this in entity_embed.api.php:
function hook_entity_embed_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, array &$context) {
// Remove the contextual links.
if (isset($build['#contextual_links'])) {
unset($build['#contextual_links']);
}
}
This cannot work. Contextual links do not exist yet at this stage. So the example is broken.
The documentation says:
* This hook is called after the content has been assembled in a structured
* array and may be used for doing processing which requires that the complete
* block content structure has been built.
This is wrong: the content structure has NOT yet been built at this stage (the whole point is that viewing an entity results in a very thin, very cheap render array, which uses a #pre_render callback to build it *fully*, and which is only called on a cache miss, so that on a cache hit, we do as little work as possible). And also, it's not a block, but an entity — but that's a nitpick :)
Conclusion: First of all this is broken — what this aims to do can only be done using hook_entity_view_alter(), which is called by EntityViewBuilder. But using that hook means you wouldn't be able to know that this entity render array is actually being rendered for embedding purposes, because hook_entity_view_alter() doesn't carry any contextual information. Nor could/should it do so, because this is only invoked after the entity's render array is fully built, and so it only happens on a cache miss, which means the CID was already known, which means modifying it only for the "embed" context would be wrong. So hook_entity_build_defaults_alter() would be better because that'd allow you to alter the cache keys, but there too you don't have that contextual information that this is an entity display being rendered for embedding.
And that is the whole point: given a certain entity display, it is always rendered in the same way. Entity Embed shouldn't allow manipulating that. Entity view modes/entity view displays are specifically designed to allow you to create different ones for different situations. Embedding is a different situation.
Proposed resolution
Either:
- Document the limitations.
- Remove
hook_entity_embed_alter(), recommend creating custom view modes instead.
Remaining tasks
TBD.
User interface changes
None.
API changes
TBD.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #2 | 2593315-2-docs.patch | 1.84 KB | wim leers |
Comments
Comment #2
wim leersHaving written the docs patch, I'm increasingly convinced that just removing this is much better. Much clearer: no confusion, no complexity. And steers people in the right direction: it encourages them to use the patterns that even Drupal 7 already had, that Drupal 8 has strengthened. It'd be great to have Entity Embed set the right example.
Comment #3
yched commentedThe IS sounds like a fair assessment...
Comment #4
wim leersThe test coverage at
\Drupal\entity_embed\Tests\EntityEmbedHooksTest::testEntityEmbedHooks()also did not test this "infection" of the non-embedded version of an entity display.Comment #5
wim leersPR at https://github.com/drupal-media/entity_embed/pull/186.
Comment #6
wim leersThanks for confirming, @yched!
Comment #7
dave reidThe problem is not everything is going to use custom view modes. We already have several ways to render items that do not include the rendered entity + view mode process, like the entity label, file, or image formatters. Those do not have any points of alteration in the output process, so we provide one. I don't think we should remove this hook, but we should fix it.
Comment #8
wim leersYou're saying the problem is that we invoke this
hook_entity_embed_alter()even when using "regular" view modes/entity view displays? I.e. that this hook should only be invoked for render arrays built by these Entity Embed Display Plugins?Comment #9
dave reidComment #10
dave reidComment #11
dave reidI think I was confused about both having a patch there and a linked PR. I would support the patch here but have closed the PR, because there are many cases where "the proper" entity view altering hooks are not invoked (the instances I listed: entity label field formatter, file field formatters, image field formatters, which can all be re-used as embed display plugins).
Comment #12
wim leersIndeed, which is why I wrote this in #8:
Comment #13
dave reidNo, I don't think it should be conditional based on which plugin is being used. That shouldn't be our choice to make. We should direct people to the correct hooks when they can, which the patch does, but the users need to know that those hooks will not always be used for other plugins.
Comment #14
dave reidComment #15
wim leersHm… IMHO it's extremely misleading right now. The hook is presented like it works for everything. But it really doesn't.
I think it'd be better if the individual plugins provided alter hooks to allow their stuff to be altered, so the developer implementing that alter hook knows what data structure to expect.
IMO it's better to not offer all the hooks people might ever want to use, and add them later, when it's clear that there's a use case for them, and when it's clear what the constraints are.
It's possible to add hooks. It's impossible to remove hooks.
Comment #16
wim leersI wonder what Berdir and slashrsm think about this.
Comment #17
berdirI'm not sure. Your argumentation is based on the assumption that everything is a rendered entity. We discussed yesterday that's not the case.
There are valid cases even then. For example, what you could do is *switch* the view mode. Or add a pre_render render callback and do stuff there. And you could alter other embed display plugins in other ways.
I'm not sure how many use cases there are for this, but it seems like a useful thing if we update the documentation to what you can and can't do.
Comment #18
slashrsm commentedComment #19
wim leers