EntityReferenceFormatterBase's prepareView() and getEntitiesToView() work hand in hand and store internal data in unofficial properties in each item :
- $item->originalEntity contains the entity that was multi-loaded in prepareView()
The name of the property, and the associated code comments, are fairly unclear about why this is used, rather than the usual $this->entity property.
- $item->access is set by getEntitiesToView(), as a static cache for "I already checked 'view' access on that entity, and it's TRUE, no need to check it again", in case the ER field is displayed several times in the request.
But :
It's incomplete, only "view access is TRUE" is preserved, access gets re-computed each time if it's FALSE.
It duplicates EntityAccessControlHandler's own static cache. Two static caches on top of each other is useless and error-prone.
Beta phase evaluation
| Issue category | Task: internal cleanup in EntityReferenceFormatterBase. |
|---|---|
| Issue priority | Normal: internal cleanup / simplification |
| Disruption | None, this is only internal communication between prepareView() and getEntitiesToView(). getEntitiesToView() is the official method to use by ERFormatters, and its result remains unchanged |
| Comment | File | Size | Author |
|---|---|---|---|
| #59 | interdiff-51-59.txt | 6.55 KB | yched |
| #59 | 2374019-ERFormatter_custom_properties-59.patch | 9 KB | yched |
| #57 | interdiff.txt | 7.51 KB | yched |
| #57 | 2374019-ERFormatter_custom_properties-57.patch | 8.18 KB | yched |
| #51 | interdiff.txt | 1.67 KB | yched |
Comments
Comment #1
larowlan+1 this broke der
Comment #2
amateescu commented@larowlan, can you elaborate a bit on what you mean by "this"? :)
Comment #3
larowlanThe original entity bit
Comment #4
yched commented@larowlan, can you elaborate a bit on what you mean by "der"? :p
Comment #5
yched commentedAnd also, how exactly originalEntity breaks it...
Comment #6
jibranDER is https://drupal.org/project/dynamic_entity_reference and perhaps the related issue is #2366093: Unable to set field value programmatically..
Comment #7
yched commentedAw - but then dynamic_entity_reference is its own field type, different from core's entity_reference, and thus cannot use its formatters and widgets (that assume that all entities are of the same entity type).
Although, note to @amateescu : if we had a static ERItemList::loadReferencedEntities(ERItemList[]) that both $items->referencedEntites() and ERFormatterBase::prepareView() used, as we discusssed in #2370703: ER's "autocreate" feature is mostly broken (and untested), then dynamic_entity_reference could simply override that static method, ERFormatterBase would work, and dynamic_entity_reference wouldn't require re-implementing all formatters :-)
Comment #8
jibranIt is a bug report as per #1 and contrib blocker so it is a major.
Comment #9
yched commentedI don't think the dynamic_entity_reference is #2366093: Unable to set field value programmatically., this issue here is about formatters.
Comment #10
larowlanSorry, yes when the original issue went in (#2346315: Translated entity references not rendered in the entity display language) it made the DER formatter need a similar change - I don't think we should need to set random object properties to get a formatter to work - so agree with the issue space here.
See http://cgit.drupalcode.org/dynamic_entity_reference/commit/?id=5e70ba5 for the DER commit.
Comment #11
amateescu commentedLet's try it out then :) Just wondering if we'll get the same errors as #2370703-12: ER's "autocreate" feature is mostly broken (and untested) or more.
Comment #12
jibransorry for the noise :)
Comment #14
jibranCreated #2377841: Allow user to chose per entity type formatter. for DER after #7
Comment #15
yched commentedRelated : #2349573: Convert access property to method on Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem
Comment #16
yched commentedThat's a lot of fails, but would still be nice IMO :-)
Comment #19
jibranI'll try to look at some fails. If I can wrap my head around it.
Comment #20
yched commentedAwesome, thanks @jibran !
Comment #21
jibranLet's see how much this fix. I have reverted the access change just to check the fails.
Comment #23
jibranSome test fixes. Interdiff is against #11.
Comment #25
jibranHere is the final fix hopefully green.
Comment #26
jibranWhile viewing host entity after deleting referenced entity I was getting non-object error hence this fix. We are still setting
$entity = $item->entity;which isNULLin this case. It fixes the issue but is it a correct fix? IMOEntityReferenceFormatterBase::prepareView()should not set it. And we don't have tests for this case in core luckily EntityCacheTagsTestBase tests this case.Comment #28
jibranThis is a green patch. I want to say good night but it is almost noon here.
Comment #29
yched commentedYay ! Thanks @jibran !
The need to explicitely grant "access content" to anon users in tests sure is a bit tedious. Wondering how we could make that less painful.
Other than that, what do you think, @amateescu ?
Comment #30
jibranWe can create a trait for that.
Comment #31
amateescu commentedNot sure a trait with a single method will be very useful here, maybe just a helper method (with $roles and $permissions parameters) on our base test classes.
About these custom properties.. the patch in #11 was written just from curiosity, I'm sorry if it was seen as an approval of the issue scope :)
Now that I'm giving it more than a 2 second thought, I think that removing 'access' is probably fine. It is used mainly to simplify tests but also a static cache for access checking at render time, if the same ER field item is render multiple times. That static cache can be useful but I guess we can move it to a better place like the entity access handler, if it doesn't have one already.
However, 'originalEntity' was added with a very specific purpose in #2346315-23: Translated entity references not rendered in the entity display language. Since
EntityReferenceFormatterBase::getEntitiesToView()returns potentially translated entities, I think it's necessary (or very useful?) to keep at hand the original (untranslated) entity too.Comment #32
jibranThanks @yched and @amateescu for the review. So is it still a NR, NW or RTBC?
So in which base class perhaps TestBase?
So this contradicts from IS
Can we please have a consensus here?
Do you want me to revert that change?
Comment #33
yched commented- about ditching 'access' :
Yeah, AFAICT, EntityAccessControlHandler already does static caching, so no need to add another one ?
- about ditching 'originalEntity' :
Not sure I get that - the patch here doesn't change the fact that $this->entity always contain the untranslated entity. The translated entities are only returned by getEntitiesToView(), and not written anywhere in the $item, that still only contains the untranslated one (which is why I don't see why originalEntity is needed to begin with)
Comment #34
jibranReroll after formatter moved to core.
Comment #35
amateescu commentedFair enough, I was talking from distant memories so that's why it probably didn't make sense. Now I'm looking at the actual code/patch and I see the following removed code:
This comment looks to me quite explanatory on why
EntityReferenceFormatterBase::prepareView()sets a custom property (originalEntity). Basically, prepareView() has to be performant and handle multiple-loading of referenced entities, but it should not unset any $item for which the referenced entity is not available anymore, so it has to signal this case somehow to the following code paths which have to loop over $items and decide if there's something to render or not.Comment #36
yched commentedDamn. Good point :-)
For items referencing stale non-existing entities,
prepareView() did not find the entity and did not pre-populate $item->entity,
so getEntitiesToView() accessing $item->entity will re-attempt to load it.
That's more general issue with the auto-loading : if the target id is invalid, all calls to $item->entity will always attempt to load it from the db over and over again.
And we don't have a way to say "give me the entity in $item->entity if it's already there but don't try to load it if it's not there". Something $item->get($property, $trigger_autocompute = FALSE)...
Damn damn damn.
Then yes, maybe prepareView() needs to put the loaded entities in a separate custom property that can simply be NULL, so that getEntitiesToView() can just read it without triggering auto-loading.
That sucks hard, but can't think of better proposal for now :-(
At least, that custom property could be named in a way that's more consistent with the reasons above (preloadedEntity ?), and the corresponding code comments should also more cleanly reflect that.
Comment #37
yched commentedShameless plug - if you're not following it already, #2405469: FileFormatterBase should extend EntityReferenceFormatterBase might interest you folks :-)
Comment #38
jibranWhat is the next step here now?
Comment #39
yched commentedSo according to #35 / #36, next step would be :
- keep the 'originalEntity' property, but rename it to 'preloadedEntity'
- Update the comment about it to explain that it is only used so that subsequent code doesn't use ->entity, which would attempt to reload from the db for items with target_ids that do not exist anymore.
Comment #40
amateescu commented+1 for #39 :)
Comment #41
yched commentedThinking with #2405469: FileFormatterBase should extend EntityReferenceFormatterBase in mind :
Maybe instead of 'preloadedEntity' we could just add a 'loaded = TRUE' property for the valid entities, and then getEntitiesToView() ignores the items where empty($item->loaded), and can still use the regular "$item->entity" instead of a weird alternate property ?
This is basically the "get $item->entity only if its already there, but do not try to load it if it's not" feature mentioned in #36, but done manually since the current "computed properties" API doesn't provide it.
Attached patch does that. Feels simpler IMO :-)
(interdiff with #34, but reading the patch directly is probably easier)
Comment #43
yched commentedNote to self : stop being a smart ass and quickly wrapping up patches in a dummy text editor.
Also :
LOL / facepalm
Comment #44
amateescu commentedI just submitted the same but you were faster :D
Comment #45
amateescu commentedAlso, this means we can change the tests to set ->loaded = TRUE instead of all the things we're changing now?
Comment #46
yched commentedHmmm, not currently : ->loaded = TRUE means we still need to check access().
Not sure if we really need to switch the translation *before* checking access(), BTW. Can entity access be granted or denied depending on the language the entity is in ?
Comment #49
amateescu commentedI have exactly 0 clues about that :/ I'd say to just make sure we're doing it in the same order as HEAD.
Comment #50
yched commented#43 came back green.
Comment #51
yched commentedRather use a _ prefix for an internal, non official property ?
Comment #52
amateescu commentedNo opinion on the _ prefix so the patch looks ready to go.
Comment #53
amateescu commentedI just spoke to @plach in IRC about this and the answer is yes. So we're doing it right by checking access after getting the translation.
Comment #54
yched commentedSo that still leaves the tedious need to explicitely setup "anon users have 'access content' perm" in a lot of tests (#29 / #30 / #31).
It feels a bit absurd to have to do that in WebTests, isn't it the case by default ? It is the case in standard.profile, less sure about testing.profile, since I can't seem to find how standard does it :-)
in current HEAD, ResponsiveImageFieldDisplayTest, for example, has to explicitely *remove* the perm...
I guess it's more tricky in KernelTests (and of course UnitTests, but that's not really an issue there anyway).
Comment #55
alexpottIt's not entirely clear what bug is being fixed from the issue summary. Also can the beta evaluation be added. Thanks.
That's how standard does it :)
So I'm not sure why this is necessary since node is being installed. I think we need more investigation.
Comment #56
yched commentedRight, this got recategorized as a major bug in #8, but that aspect was clear in #9 / #10.
This is a normal internal cleanup task - although it will make the critical #2405469: FileFormatterBase should extend EntityReferenceFormatterBase easier to work on.
Clarified the IS, and added the beta evaluation.
Will try to investigate the Test stuff a bit more.
Comment #57
yched commentedOK, so :
- Looks like the changes about 'access content' in the couple WebTests were not needed, they still pass locally if I revert the changes.
- For the other tests using entity_test entities, it seems it would make sense for entity_test.module to do the same as node_install() does (thanks @alexpott for the hint in #55) : grant the 'view test entity' perm to ANON and AUTH roles by default, and have tests explicitely revoke it if that's what they want to test.
Patch does that, let's see what fails.
Comment #59
yched commentedOK never mind, that's far beyond the scope of this issue. There are a ton of tests out there that currently have to manually grant 'view test entity', this patch just adds a couple more. We'll live.
This mostly reverts #57. For clarity, interdiff is with the previously RTBCed patch in #51.
Comment #60
jibranI agree this is test code let's not dwell about it.
This is a cleaver change. I like it.
Comment #62
alexpottI'm committing this under the beta fragility maintainer discretion proviso. Committed 6d75fd5 and pushed to 8.0.x. Thanks!