Problem/Motivation
Most of the code that manipulates field values has been progressively updated to work with the native D8 value objects (FieldItem / FieldItemList) rather than with the old-style D7 / EntityBC arrays.
One of the last remaining places is theme_field(), which still receives $element['#items'] as an array, manually generated from the runtim FieldItemList object using TypeData's getValue():
$element['#items'] = $items->getValue($compute_and_include_all_computed_properties = TRUE);
The viewElements() methods of the image formatters do something similar for each individual field item when passing it to the theme functions they respectively use.
We should get rid of that casting:
- it has a performance impact
- it causes inconsistency on API structures depending on the place in the callstack (object here, array there)
Proposed resolution
Pass the real business objects down the rendering pipeline.
Remaining tasks
None
User interface changes
None
API changes
- The render arrays generated by FormatterBase::view() have '#items' = the real FieldItemList object istead of a "translated" array structure.
You manipulate it with $items[$delta]->property instead of $items[$delta]['property']
- Functions that "see" this '#items' element need to be adjusted accordingly:
template_preprocess_field()
hook_field_attach_view_alter()
hook_entity_view_alter()
- Similar for render arrays generated by ImageFormatter/PictureFormatter::viewElements()
- Functions that work with those are adjusted accordingly:
theme_image_formatter()
theme_picture_formatter()
- *No* syntax change when accessing the values in a twig template (the twig syntax eats the object / array syntax variants)
Original report by @effulgentsia
Follow up to #2021817: Make widgets / formatters work on EntityNG Field value objects.. In that issue we're changing the $items parameter to formatters and widgets from a PHP array to a FieldInterface object. And after #2019601: Rename config Field / FieldInstance structures ? lands, we might rename it to $field. However, so far, we've left the '#items' property set in FormatterBase::view() as a PHP array.
Should we change it to a FieldInterface object, and if so, should we rename it to '#field'?
Note that this would be an API change, and we're past API freeze. However, there might be a performance gain in not having FormatterBase::view() needing to call getValue() to convert from the object to the array, and API changes for performance gains are exempted from freeze.
Comment | File | Size | Author |
---|---|---|---|
#26 | interdiff.txt | 1.18 KB | yched |
#26 | get-value-items-2042773-26.patch | 18.1 KB | yched |
#23 | interdiff.txt | 3.28 KB | yched |
#23 | get-value-items-2042773-23.patch | 18.79 KB | yched |
#19 | get-value-items-2042773-19.patch | 17.2 KB | Berdir |
Comments
Comment #1
effulgentsia CreditAttribution: effulgentsia commentedTagging
Comment #2
yched CreditAttribution: yched commentedAlso, theme_file_formatter_table() and theme_image_formatter(), used in image and file formatters (through a #theme property in their render arrays), currently receive an old-style $items array that would need to be switched to a Field object too.
Comment #3
BerdirI think per our discussion yesterday, $items as a name is fine.
Comment #4
yched CreditAttribution: yched commented$items / #items as a name is fine, but #items is currently the array result of $items->getValue(TRUE) (see FormatterBase::view())
Question is: do we want to leave it that way, or pass #items as "the FieldItemList object"
Comment #5
BerdirWe discussed this today and yes, we should pass $items in there I think. We change everything else to it too (e.g. widgets), so let's make it consistent.
As a bonus, we *could* remove most of the other #stuff in there, because you can get all of it through $items, although we might want to keep a few that are useful for accessing directly. But it would save some bits on those structures.
Comment #6
fagoAs discussed yep, but it probably should be discussed in its own issue.
Comment #7
BerdirGetting started. Also made the same change for files and images.
Comment #9
pcambraHere's a re roll with some minor improvements, most of the tests should be passing...
Why on earth would the bot remove tags from the issue? :)
What would be the next step for this?
Comment #10
yched CreditAttribution: yched commentedThanks for rerolling !
Not sure we need ->getValue() anymore here. Using $a->_weight in the closure should work ?
Similarly, the code downstream could probably be updated to work with the FieldItem object instead of the ->getValue() array. Possibly far-reaching though.
Maybe we could directly mention the FileFieldItem class here ?
$elements[$delta]['#item'] = $item, right ? So what this does is the item ends up with both ->attributes and ->_attributes ?
"if (isset($item->entity))" could be just "if ($item->entity)" - or "if ($entity = $item->entity)" in tnat case.
(+ other places in the patch)
isset() tends to indicate "sometimes it will be there, sometimes not", which is slightly misleading. It will always be "there" (computed, in fact), but can be NULL.
Also, nitpicky, but while we're in there, functions like that could really use a "$item = $variables['item']" at the top for readability :-)
Comment #12
Berdir4. I have no idea what this snippet is about, but it's the same result, just object/array, no?
Comment #13
yched CreditAttribution: yched commentedNot really - previously in this snippet in ImageFormatter::viewElements(), $item and $elements[$delta]['#item'] were two separate things:
- $item was a FieldItem object,
- $elements[$delta]['#item'] was an array (result of $item->getValues())
The 1st one has an $item->_attributes property, the 2nd doesn't have an '_attributes' entry, the code adds a 'attributes' (no underscore) equal to $item->_attributes - the intent is basically, take a attributes from a property in FieldItem, and put it in a place where the theme layer can see it ($elements[$delta]['#item']).
With the patch, $item === $elements[$delta]['#item'] === the FieldItem object.
So the adapted code makes little sense - no need to take a property in the object and put it back in the same object :-)
Comment #14
pcambraAbout 1. Even if FieldItemList ends up extending array access, seems we can't usort those:
Warning: usort() expects parameter 1 to be array, object given in Drupal\Core\Field\WidgetBase->sortItems() (line 435 of core/lib/Drupal/Core/Field/WidgetBase.php).
Changed 2. Couldn't find any place this is invoked so I guess we're fine in here?
For 3, used "- items: An array of FileFieldItem items." but the same text is in many files around.
Removed 4 altogether as for #13
Taken care of 5 and the $item = $variable['item'] things.
Also fixed most of the tests, there's one still around with the entity reference autocreate that is really strange, as is testing a feature that is impossible to achieve through the UI (autocreate nodes from another node), not sure why this is tested and why this patch breaks it.
Comment #15
pcambraComment #17
yched CreditAttribution: yched commentedRelated : #2160611: Provide {{ item.item.alt }} Twig syntax for getting data from $item['#item']['alt']
Comment #18
yched CreditAttribution: yched commentedAnyone up for a reroll ?
Comment #19
BerdirYes.
Comment #22
yched CreditAttribution: yched commentedThanks @Berdir!
This causes the fails.
I'm looking into it.
Comment #23
yched CreditAttribution: yched commentedThis should fix it.
Comment #24
effulgentsia CreditAttribution: effulgentsia commentedLooking great, and much less code needed to change than I expected. Minor nits:
This might or might not be a good change, I'm not sure, but since this is widget side, is it needed by this issue? If so, why? If not, let's take it out of here, and make a separate issue for it.
Better, but still not true. It's a FieldItemListInterface, not an array.
Comment #25
effulgentsia CreditAttribution: effulgentsia commentedUpdating the title to reflect the decision. Feel free to shorten if you can think of how.
This could also use an updated issue summary / draft text for change notice.
Comment #26
yched CreditAttribution: yched commented#24:
1. Agreed. I think we could be smarter than that in here now, but yeah, separate issue.
Opened #2170979: remove the getValue() / setValue() dance in WidgetBase::sortItems()
2. Fixed. In fact we should typehint to FileFieldItemList, the function uses it as such (calls $item->isDisplayed())
Comment #27
effulgentsia CreditAttribution: effulgentsia commentedThanks. Patch is good to go, so RTBC in case a committer is willing to commit without the issue summary update. I suggest updating it though to increase those odds.
Comment #28
yched CreditAttribution: yched commentedUpdated the issue summary.
Comment #29
catchCommitted/pushed to 8.x, thanks!
Comment #30
yched CreditAttribution: yched commentedCool. Probably needs a change notice though.
Comment #31
RainbowArrayDrafting a change notice.
Comment #32
RainbowArrayDraft change notice:
Summary
Previously, field values were passed to theme_field() using an array, with $element['#items']. This array was generated from the runtime FieldItemList object using TypeData’s getValue(), like so:
$element['#items'] = $items->getValue($compute_and_include_all_computed_properties = TRUE);
The viewElements() methods of the image formatters used something similar for each individual field item when passing it to the theme functions they respectively used.
We are now moving away from these arrays to value objects, FieldItem and FieldItemList. Using objects improves performance as well as consistency in API structures: previously either objects or arrays were used depending on the place in the callstack.
This new method passes real business objects down the business pipeline.
Now the render arrays generated by FormatterBase::view() have '#items' (a real FieldItemList object instead of a "translated" array structure).
You can manipulate this with $items[$delta]->property instead of $items[$delta]['property'].
Here are some examples of hook implementation that "see" this '#items' element that need to be updated:
Render arrays generated by ImageFormatter/PictureFormatter::viewElements() also need to be updated. For example:
File formatters use a #theme property in their render arrays that also need an update, such as:
So, essentially any code making use of the render arrays generated by FormmaterBase::view() will need to be updated to treat $items as an object rather than an array.
Twig templates that access this value do not need to be updated, as the twig syntax already handles the object/array syntax variants.
Before
Accessing a field value was handled with an array, like so:
After
Now a field value is handled as an object, like so:
Comment #33
cwgriesel CreditAttribution: cwgriesel commentedLooks good, mdrummond
Comment #34
Les LimThis is actually earlier in the D8 cycle; D7 didn't have a TypedData API.
The following
functionshook implementations, maybe?That looks like the wrong single-quote character to me.
Comment #35
RainbowArrayEdited the draft to reflect the suggested changes.
Comment #36
RainbowArrayThe draft change notice above has been updated with some suggested changes. Would be great to get some additional feedback from those who worked on this patch.
Comment #37
mradcliffeSmall typo in FormmaterBase instead of FormatterBase.
This is a bit confusing to read in my opinion. How about...
The following examples of hook implementations make use of the change from array key to object property:
Comment #38
RainbowArrayRevised change notice:
Summary
Previously, field values were passed to theme_field() using an array, with $element['#items']. This array was generated from the runtime FieldItemList object using TypeData’s getValue(), like so:
$element['#items'] = $items->getValue($compute_and_include_all_computed_properties = TRUE);
The viewElements() methods of the image formatters used something similar for each individual field item when passing it to the theme functions they respectively used.
We are now moving away from these arrays to value objects, FieldItem and FieldItemList. Using objects improves performance as well as consistency in API structures: previously either objects or arrays were used depending on the place in the callstack.
This new method passes real business objects down the business pipeline.
Now the render arrays generated by FormatterBase::view() have '#items' (a real FieldItemList object instead of a "translated" array structure).
You can manipulate this with $items[$delta]->property instead of $items[$delta]['property'].
The following examples of hook implementations make use of the change from array key to object property:
Render arrays generated by ImageFormatter/PictureFormatter::viewElements() also need to be updated. For example:
File formatters use a #theme property in their render arrays that also need an update, such as:
So, essentially any code making use of the render arrays generated by FormatterBase::view() will need to be updated to treat $items as an object rather than an array.
Twig templates that access this value do not need to be updated, as the twig syntax already handles the object/array syntax variants.
Before
Accessing a field value was handled with an array, like so:
After
Now a field value is handled as an object, like so:
Comment #39
RainbowArrayChange record created: https://drupal.org/node/2181753
Comment #40
BerdirChange notice looks good.
It's a bit special that the $items array was generated with getValue(), but the main reason this was done is to simulate the behavior that existed in 7.x, where $items was the raw array data structure.
There will probably be more people that will look at that and are coming from 7.x, not the temporary 8.x state. So maybe it can be re-formulated a bit to clarify separate that? Not sure how...
Comment #41
yched CreditAttribution: yched commentedThanks @mdrummond !
Agreed with @Berdir though. I refactored the change notice a bit more into a "Drupal 7 / Drupal 8" shape.
Comment #42
yched CreditAttribution: yched commentedPointed by @Berdir in #2190767: Preview shouldn't mean serializing the render arrays :
As a result of this change here, we serialize the field item list objects including their definitions if the resulting render array gets cached, which happens for example for preview.
Opened #2190767: Preview shouldn't mean serializing the render arrays