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.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

effulgentsia’s picture

Issue tags: +Entity Field API

Tagging

yched’s picture

Also, 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.

Berdir’s picture

I think per our discussion yesterday, $items as a name is fine.

yched’s picture

$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"

Berdir’s picture

We 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.

fago’s picture

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.

As discussed yep, but it probably should be discussed in its own issue.

Berdir’s picture

Status: Active » Needs review
FileSize
9.68 KB

Getting started. Also made the same change for files and images.

Status: Needs review » Needs work
Issue tags: -Entity Field API

The last submitted patch, get-value-items-2042773-6.patch, failed testing.

pcambra’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: +Entity Field API
FileSize
8.79 KB

Here'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?

yched’s picture

Thanks for rerolling !

  1. +++ b/core/lib/Drupal/Core/Field/WidgetBase.php
    @@ -406,7 +406,7 @@ public function massageFormValues(array $values, array $form, array &$form_state
         if ($this->fieldDefinition->isFieldMultiple() && isset($items[0]->_weight)) {
    -      $itemValues = $items->getValue(TRUE);
    +      $itemValues = $items->getValue();
           usort($itemValues, function ($a, $b) {
             $a_weight = (is_array($a) ? $a['_weight'] : 0);
             $b_weight = (is_array($b) ? $b['_weight'] : 0);
    

    Not sure we need ->getValue() anymore here. Using $a->_weight in the closure should work ?

  2. +++ b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
    @@ -706,7 +706,7 @@ public function getItems($values) {
    -        $items[$count]['raw'] = $render_array['#items'][$count];
    +        $items[$count]['raw'] = $render_array['#items'][$count]->getValue();
    

    Similarly, the code downstream could probably be updated to work with the FieldItem object instead of the ->getValue() array. Possibly far-reaching though.

  3. +++ b/core/modules/file/file.field.inc
    @@ -516,7 +516,7 @@ function file_field_find_file_reference_column(FieldInterface $field) {
    - *   - items: An array of file attachments.
    + *   - items: An array of file field items.
    

    Maybe we could directly mention the FileFieldItem class here ?

  4. +++ b/core/modules/image/lib/Drupal/image/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -113,14 +113,15 @@ public function viewElements(FieldItemListInterface $items) {
    -          $elements[$delta]['#item'] += array('attributes' => array());
    -          $elements[$delta]['#item']['attributes'] += $item->_attributes;
    +          $elements[$delta]['#item']->attributes = array();
    +          $elements[$delta]['#item']->attributes += $item->_attributes;
    

    $elements[$delta]['#item'] = $item, right ? So what this does is the item ends up with both ->attributes and ->_attributes ?

  5. +++ b/core/modules/picture/picture.module
    @@ -173,23 +173,21 @@ function theme_picture_formatter($variables) {
    -  elseif (isset($variables['item']['entity'])) {
    -    $picture['#uri'] = $variables['item']['entity']->getFileUri();
    -    $picture['#entity'] = $variables['item']['entity'];
    +  elseif (isset($variables['item']->entity)) {
    +    $picture['#uri'] = $variables['item']->entity->getFileUri();
    +    $picture['#entity'] = $variables['item']->entity;
    

    "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 :-)

Status: Needs review » Needs work

The last submitted patch, 9: get-value-items-2042773-9.patch, failed testing.

Berdir’s picture

4. I have no idea what this snippet is about, but it's the same result, just object/array, no?

yched’s picture

Not 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 :-)

pcambra’s picture

Status: Needs work » Needs review
FileSize
17.21 KB
13.63 KB

About 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.

pcambra’s picture

Issue tags: +LONDON_2013_DECEMBER

The last submitted patch, 14: get-value-items-2042773-14.patch, failed testing.

yched’s picture

Issue tags: +Needs reroll

Anyone up for a reroll ?

Berdir’s picture

Issue tags: -Needs reroll
FileSize
17.2 KB

Yes.

Status: Needs review » Needs work

The last submitted patch, 19: get-value-items-2042773-19.patch, failed testing.

The last submitted patch, 19: get-value-items-2042773-19.patch, failed testing.

yched’s picture

Thanks @Berdir!

+++ b/core/modules/image/lib/Drupal/image/Plugin/Field/FieldFormatter/ImageFormatter.php
@@ -113,18 +113,10 @@ public function viewElements(FieldItemListInterface $items) {
-        // Pass field item attributes to the theme function.
-        if (isset($item->_attributes)) {
-          $elements[$delta]['#item'] += array('attributes' => array());
-          $elements[$delta]['#item']['attributes'] += $item->_attributes;
-          // Unset field item attributes since they have been included in the
-          // formatter output and should not be rendered in the field template.
-          unset($item->_attributes);
-        }

This causes the fails.
I'm looking into it.

yched’s picture

Status: Needs work » Needs review
FileSize
18.79 KB
3.28 KB

This should fix it.

effulgentsia’s picture

Looking great, and much less code needed to change than I expected. Minor nits:

  1. +++ b/core/lib/Drupal/Core/Field/WidgetBase.php
    @@ -427,7 +427,7 @@ public function massageFormValues(array $values, array $form, array &$form_state
    -      $itemValues = $items->getValue(TRUE);
    +      $itemValues = $items->getValue();
    

    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.

  2. +++ b/core/modules/file/file.field.inc
    @@ -521,7 +521,7 @@ function file_field_find_file_reference_column(FieldInterface $field) {
    - *   - items: An array of file attachments.
    + *   - items: An array of FileFieldItem items.
    

    Better, but still not true. It's a FieldItemListInterface, not an array.

effulgentsia’s picture

Title: Decide whether/how to change the API of #items within a theme_field() render array » Change #items within a theme_field() render array from an *array* to the same $items *object* used throughout the rest of the formatter pipeline
Status: Needs review » Needs work
Issue tags: +API change, +Needs issue summary update

Updating 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.

yched’s picture

Status: Needs work » Needs review
FileSize
18.1 KB
1.18 KB

#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())

effulgentsia’s picture

Status: Needs review » Reviewed & tested by the community

Thanks. 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.

yched’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

Updated the issue summary.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.x, thanks!

yched’s picture

Title: Change #items within a theme_field() render array from an *array* to the same $items *object* used throughout the rest of the formatter pipeline » Change notice: Change #items within a theme_field() render array from an *array* to the same $items *object* used throughout the rest of the formatter pipeline
Status: Fixed » Active
Issue tags: +Needs change record

Cool. Probably needs a change notice though.

RainbowArray’s picture

Assigned: Unassigned » RainbowArray

Drafting a change notice.

RainbowArray’s picture

Draft 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:

  • template_preprocess_field()
  • hook_field_attach_view_alter()
  • hook_entity_view_alter()

Render arrays generated by ImageFormatter/PictureFormatter::viewElements() also need to be updated. For example:

  • theme_image_formatter()
  • theme_picture_formatter()

File formatters use a #theme property in their render arrays that also need an update, such as:

  • theme_file_formatter_table()

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:

$items[$delta]['property']

After
Now a field value is handled as an object, like so:

$items[$delta]->property
cwgriesel’s picture

Looks good, mdrummond

Les Lim’s picture

Previously, in Drupal 7, 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()

This is actually earlier in the D8 cycle; D7 didn't have a TypedData API.

The following functions that “see” this ‘#items’ element need to be updated:

The following functions hook implementations, maybe?

$items[‘delta’][‘property’]

That looks like the wrong single-quote character to me.

RainbowArray’s picture

Edited the draft to reflect the suggested changes.

RainbowArray’s picture

Assigned: RainbowArray » Unassigned
Status: Active » Needs review
Issue tags: +SprintWeekend2014

The 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.

mradcliffe’s picture

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.

Small typo in FormmaterBase instead of FormatterBase.

Here are some examples of hook implementation that "see" this '#items' element that need to be updated:

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:

RainbowArray’s picture

Revised 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:

  • template_preprocess_field()
  • hook_field_attach_view_alter()
  • hook_entity_view_alter()

Render arrays generated by ImageFormatter/PictureFormatter::viewElements() also need to be updated. For example:

  • theme_image_formatter()
  • theme_picture_formatter()

File formatters use a #theme property in their render arrays that also need an update, such as:

  • theme_file_formatter_table()

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:

$items[$delta]['property']

After
Now a field value is handled as an object, like so:

$items[$delta]->property
RainbowArray’s picture

Title: Change notice: Change #items within a theme_field() render array from an *array* to the same $items *object* used throughout the rest of the formatter pipeline » Change #items within a theme_field() render array from an *array* to the same $items *object* used throughout the rest of the formatter pipeline
Status: Needs review » Fixed
Issue tags: -Needs change record

Change record created: https://drupal.org/node/2181753

Berdir’s picture

Change 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...

yched’s picture

Thanks @mdrummond !
Agreed with @Berdir though. I refactored the change notice a bit more into a "Drupal 7 / Drupal 8" shape.

yched’s picture

Pointed 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

Status: Fixed » Closed (fixed)

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