With the Field API improvements that went in the past weeks / months, looks like MetadataGeneratorInterface::generateField() could be refactored a bit:

- method name is weird, this is not generating a field :-)

- signature is generateField(EntityInterface $entity, FieldDefinitionInterface $field_definition, $langcode, $view_mode),
and part of the job of the method is to work on $items = $entity->getTranslation($langcode)->get($field_definition->getFieldName())
Typically, those kind of functions have switched to receiving $items directly, letting the caller resolve entity translation.
So this could be just generateField(FieldItemListInterface $items, $view_mode)

- editorSelector->getEditor($formatter_id, $field_definition, $items) should receive $items as the FieldItemListInterface object, instead of one that has been cast to an array by ->getValue()
Could then just be getEditor($formatter_id, FieldItemListInterface $items) - and then switching the order of arguments might be more consistent with what the rest of core does ($items is typically the primary business logic parameter)

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

  • I totally agree about the weird method name. It originally was "generate", but at some point somebody changed it to "generateField" because we also needed to generate metadata for entities. So what about "generateFor(Field|Entity)"?
    That's what I implemented in the attached patch in any case.
  • So apparently given a \Drupal\Core\Field\FieldItemListInterface object, I can indeed retrieve the associated entity and field definition, so in theory this is indeed possible. But is it really better?
    AFAICT most functions that have switched to receiving FieldItemListInterface $items directly have done so because their primary task is to manipulate the values of that.
    But in this case, the majority of the metadata actually comes from the entity (access check, ARIA attribute value), field definition (label, ARIA attribute value). $items is used as an additional piece of context while selecting an in-place editor.
  • In the EditorSelectorInterface case, I agree that this change makes sense.

Additionally, if we're changing EditorSelectorInterface, then it also makes sense to change EditPluginInterface. So I changed that too.


Note that the attached patch works perfectly, but tests fail. I have no idea how to navigate through the maze of classes to get the EditorSelectionTest::testText() DUTB test working again. I'd appreciate some help with that :)
(I tried, but failed.)

yched’s picture

- Method name:
AFAIK we try to have method names that carry a standalone meaning (verb + complement) without the help of the class name. I.e, even if the object is a "Metadata generator", it helps IMO to have the method state that it generates metadata :-)

--> I'd suggest generateFieldMetadata() ? getFieldMetadata() ?

- arguments:
I see your point about $entity being the primary source of logic, and actual field values being only secondary. I'd argue that this prioritization is only a detail true of the current implementation in MetadataGenerator::generateField(). At the interface level, the semantic of the method is "the metadata can depend on the $items field values", even if in some/most cases it will only depend on the entity & field definition.
But I'll let you see what you think is best :-)

At any rate, the $langcode should IMO still disappear from the signature, and resolved upstream by the calling code taking care of the $entity->getTranslation($langcode)

--> generateFieldMetadata($entity, $field_definition, $view_mode);

Status: Needs review » Needs work

The last submitted patch, 1: edit_fielditemlistinterface_refactor-2144879-1.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
13.62 KB

First, a straight reroll so the patch applies again. Should result in the same test failures.

Status: Needs review » Needs work

The last submitted patch, 4: edit_fielditemlistinterface_refactor-2144879-4.patch, failed testing.

Wim Leers’s picture

Title: Brush up MetadataGeneratorInterface::generateField() » Brush up MetadataGeneratorInterface::generate(Field|Entity)(): use FieldItemListInterface + better naming
Status: Needs work » Needs review
FileSize
19.72 KB
7.09 KB

I'd suggest generateFieldMetadata() ?

Works for me. Done.

I see your point about $entity being the primary source of logic, and actual field values being only secondary. […] At the interface level, the semantic of the method is "the metadata can depend on the $items field values", even if in some/most cases it will only depend on the entity & field definition.

What about this, then: pass both FieldDefinitionInterface $field_definition and FieldItemListInterface $items to generateFieldMetadata()?
That's technically duplication, but the semantics are more clear.

At any rate, the $langcode should IMO still disappear from the signature, and resolved upstream by the calling code taking care of the $entity->getTranslation($langcode)

This also has a semantical problem: it means it's possible to pass the untranslated node as well, since there isn't a separate interface I can typehint on.

yched’s picture

What about this, then: pass both FieldDefinitionInterface $field_definition and FieldItemListInterface $items to generateFieldMetadata()?
That's technically duplication, but the semantics are more clear

Well, I'd tend to understand that as "if I get passed $items and $definition as separate params, it means they can differ - i.e the calling code can decide to pass a $definition that's different from $items->getFieldDefinition() - or maybe the $definition is in fact supposed to be another, entirely unrelated definition ?"
So, not sure about clearer semantics ;-)
I think at this point the rest of core is pretty standard about the fact that a FieldItemListInterface $items means access to the underlying definition.

Regarding $langode: well, the moment the caller passes generateFieldMetadata() a FieldItemListInterface param, it means it already has effectively narrowed to a language. So no need to pass a useless $langcode separately ?

The last submitted patch, 6: edit_fielditemlistinterface_refactor-2144879-5.patch, failed testing.

The last submitted patch, 6: edit_fielditemlistinterface_refactor-2144879-5.patch, failed testing.

Wim Leers’s picture

Assigned: Unassigned » Wim Leers
FileSize
30.95 KB
28.4 KB

I think at this point the rest of core is pretty standard about the fact that a FieldItemListInterface $items means access to the underlying definition.

Okay, then I concede. I personally find it very confusing. But I think that over time, it will be the clearest — I think my historical understanding might be causing that confusion :)


Also fixed all tests. Should come back green.

Wim Leers’s picture

FileSize
30.79 KB
976 bytes

Forgot to remove two debug statements.

The last submitted patch, 10: edit_fielditemlistinterface_refactor-2144879-10.patch, failed testing.

The last submitted patch, 11: edit_fielditemlistinterface_refactor-2144879-11.patch, failed testing.

yched’s picture

Thanks Wim :-)

From the interdiff:

+++ b/core/modules/edit/lib/Drupal/edit/EditPluginInterface.php
@@ -18,13 +18,13 @@
-   * @param \Drupal\Core\Field\FieldItemListInterface $items
+   * @param \Drupal\Core\Field\FieldItemListInterface $field

(and lots of similar renames)
$items is actually the usual var name for a FieldItemListInterface across core ;-)

Wim Leers’s picture

It is, except in one place. Plus, in many cases in Drupal core, the docs say "the field" or "the field object". And that's what makes sense in all of Edit's cases too, "the field values" or "the field items" doesn't make much sense (unless you have the necessary knowledge of the internals). So that's why I named them consistently like this.

Is there a good reason to not do it this way?

Wim Leers’s picture

The last submitted patch, 11: edit_fielditemlistinterface_refactor-2144879-11.patch, failed testing.

yched’s picture

Naming has gone through a lot of confusion in this cycle :-/.
EntityNG initially started naming all its "field value objects" $field / Field / "the field",
while the Field API that came from D7 used $field for "the definition structures", and $items for values.

After a couple months of confusion and debates, it's been agreed in Prague to settle on :
- FieldDefinition / $field_definition for the definition structures
- FieldItem / FieldItemList / $item / $items for the values

It's very likely that there remains places in core that haven't been updated accordingly, but we've been cleaning up existing code progressively, and trying to make sure new code follows the above.

yched’s picture

More thoughts:

IMO it makes a lot of sense that the object passed to generateFieldMetadata() is the same than the one passed to WidgetInterface::form() or FormatterInterface::view() - an $item - because that's exactly the level at which these metadata come into play : included in the formatter display, used to generate a widget in a form. You generate metadata for the exact same thing the formatter / widget generate HTML. So parameters being the same is actually rather telling IMO, those really are sister APIs.

Not willing to start a war on that though - your system, your call. Just refining my arguments ;-)

Wim Leers’s picture

FileSize
33.02 KB
12.67 KB

First, let's get this patch green — finally :)

Wim Leers’s picture

FileSize
33.1 KB
18.59 KB

Hehe :) I just want to make sure I continue to understand the module I'm supposed to maintain, hence my continued hesitance.

Thanks for the additional historical context you've provided in #18, that really helps!

It's just that whenever I see FieldListItemInterface $items, that signals to me "these are merely the values of the field, not any metadata-ish/definition-ish of the field". Especially the $items part.

However, #19 has convinced me. If I see Field API also working solely with this parameter, then I have zero objections. I still think the naming is off/confusing (or maybe "misleading" is a better word), but it's better to then at least have consistently confusing naming, that's still less confusing :)

All this interdiff changes, is renaming FieldItemListInterface $field to FieldItemListInterface $items.

yched’s picture

I just want to make sure I continue to understand the module I'm supposed to maintain, hence my continued hesitance.

Sure - that's the constant maintainer battle. I totally sympathize with that...

It's just that whenever I see FieldListItemInterface $items, that signals to me "these are merely the values of the field, not any metadata-ish/definition-ish of the field. ... I still think the naming is off/confusing (or maybe "misleading" is a better word)

TBH I'm not fully convinced that we have nailed the absolute best naming pattern either. But that was a thorny discussion, concessions were made, an agreement was found, which was a win in itself :-). And yeah, consistency helps clarity too.

Other than that, being able to retrieve metadata about a runtime object from the runtime object itself has become more & more common in many places in D8: from an entity you can get the definitions of its fields, and soon its "entity type definition" too; from a plugin object you can get its plugin definition... Similarly, from a "field value object" you can get its definition, its langcode, its parent entity...

This greatly helped saving API verbosity & consistency IMO, because you don't need to think of adding separate params to pass the various metadata (entity type, field definition, langcode...) along (and make sure they're in sync) in case implementations need them; or force them to fetch the metadata themselves, which means dependencies on the metadata registries and prevents one-off variants...

Anyway - glad that you agree :-)

Minor remarks, but no blockers, patch is committable as is :

  1. +++ b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/InPlaceEditor/WysiwygEditor.php
    @@ -32,7 +34,7 @@ function isCompatible(FieldDefinitionInterface $field_definition, array $items)
    -      $format_id = $items[0]['format'];
    +      $format_id = $items[0]->get('format')->getValue();
           if (isset($format_id) && $format_id === 'full_html') {
    

    Core code tends to standardize on the shorthands : $items[0]->format.
    Also, I think the isset check isn't necessary anymore.
    if ($items[0]->format === 'full_html') should do the job.

  2. +++ b/core/modules/edit/tests/modules/lib/Drupal/edit_test/Plugin/InPlaceEditor/WysiwygEditor.php
    @@ -43,8 +45,8 @@ function isCompatible(FieldDefinitionInterface $field_definition, array $items)
         return $metadata;
    ...
    +    $format_id = $items[0]->get('format')->getValue();
         $metadata['format'] = $format_id;
    

    Similarly, $metadata['format'] = $items[0]->format would work.

    + similar in Drupal\editor\Plugin\InPlaceEditor\Editor

yched’s picture

Status: Needs review » Reviewed & tested by the community

Wrong tab, meant to do this.

The last submitted patch, 21: edit_fielditemlistinterface_refactor-2144879-21.patch, failed testing.

yched’s picture

Wim Leers’s picture

Thanks for providing yet more context & insight — that not only is helpful for me for understanding the current situation, it also makes me optimistic about where we're heading :)

yched++

Reroll to address remarks in #22.

yched’s picture

Still green. Confirming RTBC.

Thanks a lot @Wim !

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Ok, there's quite a bit here, but it all looks consistent with what we're doing elsewhere for similar stuff.

Committed and pushed to 8.x. Thanks!

Wim Leers’s picture

Issue tags: -sprint
Wim Leers’s picture

Status: Fixed » Closed (fixed)

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