From #2143291-105: Clarify handling of field translatability by @xjm:

@alexpott and I both find it a little off for FieldDefinition to implement both interfaces. @alexpott pointed out correctly that changing that would be out of scope here, but let's reference a followup discussion?

So, here's that issue.

Background

Here's what the situation will be when the following issues are completed if we choose to do nothing in this issue.

  • After #2143291: Clarify handling of field translatability is done, FieldDefinitionInterface (FDI) (the information about a field attached to a concrete bundle) and FieldStorageDefinitionInterface (FSDI) (the information about a field that is independent of bundle, because field values need to be stored in a schema that is independent of bundle so that Views can query across bundles) will be two completely separate interfaces.
  • For configurable fields, after #2287727: Rename FieldConfig to FieldStorageConfig is done, FieldStorageConfig will implement only FSDI and FieldConfig will implement only FDI.
  • For base fields, FieldDefinition is a single implementation class that implements both FSDI and FDI.
  • After #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields is done, if a base field attached to a particular bundle gets any bundle-specific customization (e.g., the default value of the node "promote" field is made different for that bundle vs. the base default), then that will be represented by a FieldDefinitionOverride class that implements FDI only. If the field is not customized in any way for that bundle, it will be represented by the original FieldDefinition object (returned by ContentEntityInterface::baseFieldDefinitions()) that implements both FSDI and FDI.

Problem/Motivation

Evaluate whether the above difference between bundle-uncustomized base fields (a single FieldDefinition object implementing both FSDI and FDI) and configurable fields (always two separate objects, each one implementing only one of the interfaces) is desirable.

Arguments for leaving it as-is

  • Entity types implementing ContentEntityInterface::baseFieldDefinitions() just need to return a single object for each field. That object can define both the bundle-independent information, and the default values for information that can in theory be customized per-bundle. From the perspective of the content entity type, it doesn't need to care about what is bundle-customizable and what isn't.

Arguments for splitting FieldDefinition into FieldDefinition and FieldStorageDefinition

  • More implementation-level symmetry between base fields and configurable fields.

Comments

effulgentsia’s picture

Issue summary: View changes
yched’s picture

Whatever the outcome, I think it's a good thing to allow us to re-evaluate this at this point.

Yes, I'm not fond either of FieldDefinition implementing both interfaces. AFAIK the only (but very valid) reasons are
- DX for defining fields through code in EntityType::baseFieldDefinitions() : for most fields, providing just one definition (the FSDI, which aloso contains the info to act as a FDI, the same for all bundles) is enough. I guess we could still keep that DX sugar while still internally creating, out of those FSDI, separate FDI objects for each bundle.
- The reason then becomes performance - the current formulation in HEAD tries to minimize the # of objects we need to instantiate at runtime.

yched’s picture

Status: Postponed » Active
plach’s picture

Issue tags: +beta deadline, +API change

I think we need this sorted out before beta, I am afraid it would be a big API breaking change...

plach’s picture

I am wondering whether a possible solution to cope with the DX regression implied by making FD implement just FDI, could be adding a method to get a (wrapped?) FSD from a FD and keep defining it with all the properties in just one object as we do now.

yched’s picture

I've always struggled with the notion of "base fields" as implemented by the current FieldDefinition class as a weird and confusing singularity with respect to how the rest of the ecosystem works with FieldDefinitionInterface objects.
The recent evolutions (the formalization of FieldStorageDefinitions, the "bundle overrides" being worked at #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields) only strengthen that feeling.

My current thoughts on this (and from an IRL discussion, @amateescu shares the same):

Those FieldDefinition objects are primarily handy shortcuts when writing your entity type's baseFieldDefinitions(), that lets you specify in one single definition object :
- a field storage definition, seen by the ecosystem as an FSDI
- and a "bundle field definition" on no specific bundle, seen by the ecosystem as "an FDI present on all existing bundles"

That's a very valid feature, baseFieldDefinitions() would be very painful to write otherwise.
But then it is currently painful for the rest of the system to have to deal with those weird animals at runtime.

In such a case IMO, the "shorthand" aspect that is valid at definition time should not leak out to the rest of the consuming runtime APIs.
Meaning, when building the "registry of FDI objetcs for a given $bundle", EM::buildBundleFieldDefinitions() would expand those shorthand objects into actual FDI objects, with an actual bundle.

API-wise :

- The "shorthand" class itself does not implement FDI, it's not an actual "definition o a field on a bundle".
It thus wouldn't be named FieldDefinition, but BaseFieldDefinition.
--> In MyEntityType::baseFieldDefinitions(), I create and return BaseFieldDefinition objects - makes sense.

- A BaseFieldDefinition is "the definition of one FSDI + a shorthand for definition FDIs on all bundles". It could still implement FSDI directly, I guess. No need to create a separate object for "the FSDI of this BaseFieldDefinition".

- The FieldDefinition class, implementing FDI, is the one used for the actual FDIs expanded for each bundle at runtime.

- FDI objects, manipulated by all the runtime ecosystem, are "field definitions on a bundle". No more "FDIs with no bundle because all bundles" singularity.

- If you need a list of "fields that are constituent of the Entity Type and appear across all bundles no matter what" (that's a need for Rules, if I understand correctly), you still have EM::getBaseFieldDefinitions(), that returns BaseFieldDefinition objects.

- #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields would then mean that all FDIs are in fact ConfigEntities. The ones expanded out of BaseFieldDefinition objects simply happen not to be ->save()d and written into config by default, but they *can* be (and you then have an override).
--> might go as far as FDI extends ConfigEntityInterface ?

The above IMO is an API design that faithfully represents what our actual concepts are.

I do realize that there's some perf overhead to be watched for :
- more objects creation in EM::buildBundleFieldDefinitions()
- more objects serialization on cache write, more unserialization on read, and after that more objects in memory at runtime (when no overrides though, those additional objects would be just a $bundle string and a pointer to their source BaseFieldDefinition)

But I would rather try to actually measure the perf cost and find ways to mitigate it on top of a sound & consistent API structure, rather than having confusing optimization shortcuts baked in the API design.

alexpott’s picture

I totally agree with the main points of #7 and the idea that

- #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields would then mean that all FDIs are in fact ConfigEntities. The ones expanded out of BaseFieldDefinition objects simply happen not to be ->save()d and written into config by default, but they *can* be (and you then have an override).
--> might go as far as FDI extends ConfigEntityInterface?

is pretty exciting.

If we get agreement on this - then I would suggest postponing #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields on this and making this beta blocking.

alexpott’s picture

Priority: Normal » Critical
Issue tags: -beta deadline +beta target

Actually I think that we could do #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields first as long as we agree the class names and config file names. And do this as part of the beta - yes we'd want to get this in early but the only modules we'd break as ones providing new Entity types or extending ContentEntityDatabaseStorage. I don't think this change falls under "stable data model and stable critical APIs" criteria for beta blockers. (Of course Berdir or yched might prove me wrong :) )

However I do think the concept of FieldDefinition and FieldDefinitionStorage and their separation is critical and it will be great when both FieldAPi and EntityFieldAPI both enforce this distinct through the implementations - rather than having just FieldAPI do it.

xjm’s picture

And do this as part of the beta - yes we'd want to get this in early but the only modules we'd break as ones providing new Entity types or extending ContentEntityDatabaseStorage.

Isn't this pretty much exactly the definition of the content storage model though?

alexpott’s picture

Yes this will change how you define the content storage model ie...

  /**
   * {@inheritdoc}
   */
  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    $fields['nid'] = FieldDefinition::create('integer')
      ->setLabel(t('Node ID'))
      ->setDescription(t('The node ID.'))
      ->setReadOnly(TRUE)
      ->setSetting('unsigned', TRUE);

    $fields['uuid'] = FieldDefinition::create('uuid')
      ->setLabel(t('UUID'))
      ->setDescription(t('The node UUID.'))
      ->setReadOnly(TRUE);
 //....

There will be a new class here - it won't be FieldDefinition - but the model itself show not change. But the methods should all be the same. And there will be internal change in the EntityManager but I think that should be about it.

xjm’s picture

So, I think that is actually pretty much the definition of a beta blocker, if it's critical. The question for me is whether it's actually critical (I struggle to make a case), and how much time it would take.

alexpott’s picture

I think we should proceed with #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields since this creates a BundleFieldDefinition object and goes a lot of the way there. This issue could then discuss if we want to change getFieldDefinitions() to always return objects that implement BundleFieldDefintitionInterface objects and whether we want to merge FieldDefintionInterface and BundleFieldDefinitionInterface together and rename the FieldDefinition object to something like BaseFieldDefinitionBuilder.

As to whether this is a critical beta blocker - the more I think about it I think that with #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields we're in a situation we could live for D8. I think if we do the simple renames and merges this won't be a lot of work. But actually trying to remove FieldDefinition and replace it with a builder that implements both FSDI and FDI and then decomposing that into 2 run time objects feels like a lot of work.

yched’s picture

My point is, I'd really like to avoid introducing BundleFieldDefinitionInterface in addition to FieldDefinitionInterface, IMO that is adding awkward complexity to work around a flawed model, adds more confusion on top of the current confusion :-/

Fine if BFDI is added as an intermediate step, but it would be sad if we shipped with both.
FWIW, I'm willing to work on this once "rename FieldConfigl is in.

berdir’s picture

The main reason we went with the current approach is to try and limit the amount of definition objects we need at runtime because this sums up very very fast. Take a bigger site which displays nodes of 10 different types on a page, with terms, users, comments and that's just core. You're quickly way past 100 field definitions that you're carrying around.

Right now, we need both the definition and the storage definition to do anything useful.

I can see that this might actually be an improvement *if* we manage to avoid loading the storage definitions on a normal, cached node view, for example. Then this could actually be an improvement compared to now as we only have to do 1 cache get instead of 2. (only applies to the first bundle of an entity type), although we trade it with needing more memory as soon as multiple bundles are involved.

fago’s picture

My point is, I'd really like to avoid introducing BundleFieldDefinitionInterface in addition to FieldDefinitionInterface, IMO that is adding awkward complexity to work around a flawed model, adds more confusion on top of the current confusion :-/

I don't think our current model is a confusing shortcut - it models the reality of having a single field definition for all the base fields that have no per-bundle variations. There is just one thing - but if the API makes two out of the single-defined thing, this is something our (simplified) model enforces on it.

Anyway, if the simplified model works sufficiently there is nothing against it to say. However, I must say that performance concerns me here as well. I don't think it makes sense to change this when we end up having more stuff being loaded into memory just for keeping our simplified model intact.

That said, I'm not sure I fully understand the reasoning on why the current model is seen as flawed / bad. Is it the aim to have the simplified storage+instance model everywhere, or is it more related to having #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields and per-bundle variations pretty much everywhere anyway?

My point is, I'd really like to avoid introducing BundleFieldDefinitionInterface in addition to FieldDefinitionInterface, IMO that is adding awkward complexity to work around a flawed model, adds more confusion on top of the current confusion :-/

BundleFieldDefinitionInterface sounds like making it confusing, yep - not sure where the need for this would come from. I guess I head over and read up on #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields now, which is probably related.

effulgentsia’s picture

We discussed this issue at length in yesterday's #drupal-entity IRC meeting, and here's what we came up with as I understand it. Please correct me if there's something here that doesn't reflect that consensus, and now's a good time to make some coffee to drink slowly while reading this:

  1. For conceptual understanding, interfaces are more important than implementations. Most code shouldn't care whether a particular class is implementationally similar to another class, or whether a class that implements interface A also happens to implement interface B or not. What's important is that interfaces are well named and model the most important concepts as directly as possible.
  2. The two most primary interfaces related to Field API that we already have in HEAD are FieldStorageDefinitionInterface and FieldDefinitionInterface. The meaning of the former is: the information about a field that is required to be the same for all of that field's items, regardless of bundle or any other per-entity variation. Because of that requirement, that information can be used by the storage handler to optimize the schema, by Views to set up exposed filters, and by Rules, Panels, and other code that needs to rely on something about a field that is independent of bundle. The meaning of the second is: the information about a field that might vary from entity to entity. Now currently in Drupal, in practice and with core's UIs and some APIs, that variation can only be by bundle (i.e., the field definition of $entity_1->FIELD_FOO and $entity_2->FIELD_FOO will be the same if $entity_1 and $entity_2 have the same bundle); however, there's nothing about FieldDefinitionInterface itself that specifies or requires that (except the getBundle() method, which I propose later in this comment to remove).
  3. An orthogonal distinction to the FSDI/FDI split is base fields vs. bundle fields. A "base field" is defined as "a field that exists for every entity of the given entity type, regardless of bundle, regardless of the state of configuration". Meaning, it is impossible to do a configuration deployment that causes the field to go away for some bundles but not others, or for new bundles to be added without automatically receiving that field. Currently, we implement this requirement by making modules declare their base fields in code (ContentEntityInterface::baseFieldDefinitions() + hook_entity_base_field_info() + hook_entity_base_field_info_alter()) where what is returned are objects that simultaneously implement both FSDI and FDI, and so that FDI information can be passed to code that needs it with no reliance on any config files. Other implementations are theoretically possible, such as using config files, autocreating one for every bundle, and locking them from deletion (see #2289551: Clarify what 'locked' means for a config entity and whether it's okay for code to rely on a locked config entity existing), but that's not the approach currently in HEAD. A "bundle field" is the opposite of that: it's a field that may or may not exist on some bundles. An example use case for code wanting to retrieve all the base fields, but not the bundle fields, is a Rules UI where you want to configure a rule that only relies on fields that are guaranteed to exist regardless of bundle. Or, an entity storage handler that can optimize storage based on knowing whether a field is guaranteed to exist for every bundle. However, code that acts on one entity at a time, shouldn't need to care whether a field is a base or bundle field; as far is it's concerned, it's just working on "all the fields of the entity", and at least with respect to that entity, a field is a field regardless of whether it also exists or not on other bundles. Note that the base/bundle split is orthogonal to the FSDI/FDI split. "Base" only tells you that the field exists regardless of bundle. It does not tell you whether the FDI information is the same for every bundle. And FSDI only tells you that for the bundles on which the field exists, here's the information that doesn't vary by bundle, but it does not tell you whether the field exists for any given bundle.
  4. And a final orthogonal distinction is a field definition vs. a field configuration. The former is defined by FieldDefinitionInterface, while the latter is defined by FieldInstanceConfigInterface, to be renamed to FieldConfigInterface in #2312093: Rename FieldInstanceConfig to FieldConfig and most likely moved from field.module to \Drupal\Core\Field in #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields. The difference is that a definition is read-only while a configuration is read-write. In other words, FieldConfigInterface extends FieldDefinitionInterface, but not vice versa. In the meeting, yched expressed dissatisfaction with this split, because it doesn't model a functional requirement: with #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields, all field definitions will be configurable, so why split the interfaces like that: it just adds extra nomenclature and cognitive load. For example, in our cache system, we just have a single CacheBackendInterface, not a read-only sub-interface in addition to the full read-write one. However, the justification for doing so is performance: it just happens to be the case that for base fields without bundle-specific definition overrides, the same object can be reused so long as it's not written to, and most of our performance-critical code doesn't need to write. So, by splitting the interface, we can delay instantiating a writeable object until it's needed. One could argue that it would be better to use flyweight/proxy objects to hide that optimization from the API, but there are some challenges with that. If someone's inspired to try, go for it in a separate issue, but my opinion is it's not worth the effort because the interface segregation is sensible for other reasons as well, such as that because most code (e.g., formatters and widgets) doesn't need to change the field definition, it's convenient when working with that code for IDE autocompletion to only show methods relevant to reading.

So, if you accept all of the above (especially that last point), then our conclusion was:

  1. We should rename FieldDefinition (the implementation class, not the interface) to BaseFieldDefinition, since it's an implementation specific to base fields.
  2. There's not a strongly compelling DX argument for changing BaseFieldDefinition into not implementing FSDI. The statement in #7 that "it is currently painful for the rest of the system to have to deal with those weird animals at runtime" is only true to the extent we have code that isn't working with interfaces. We need to fix that code to work with interfaces, at which point, FDI, FCI, and FSDI are all separate, and whether the classes that implement them choose to implement those interfaces together in a single class/object or in separate classes/objects makes no DX difference except to the class itself.
  3. Since there is a performance benefit to BaseFieldDefinition implementing both FDI and FSDI in a single object (that can be reused cross-bundle), we should continue to do so. In other words, we should close this issue as "by design".
  4. We can only reuse BaseFieldDefinition objects cross-bundle if we remove the getBundle() method from FDI. There are few callers of FDI::getBundle() and they can/should be replaced with either passing $bundle as a separate param (if the need for the bundle is unrelated to changing the definition) or using FCI instead (if the need for the bundle is to override the definition for that bundle).
  5. We need to remove setters from FDI and move them to FCI.
  6. There's an open question of what the API should be for getting an FCI (whether to have another method on EntityManager, like getFieldConfigurations() that's parallel to getFieldDefinitions(), or whether to have a FDI::getConfig($bundle) method). yched recommends the latter, but plach said that was tried in #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields and was problematic for some reason, but it may be worth trying again. In any case, let's move that part of the discussion to that issue.

Finally, here's the proposed hierarchy/naming for the above, to be implemented in #2312093: Rename FieldInstanceConfig to FieldConfig and #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields:

// Interfaces
- FieldStorageDefinitionInterface
- FieldDefinitionInterface
- FieldConfigurationInterface extends FieldDefinitionInterface, ConfigEntityInterface

// Implementations
- BaseFieldDefinition implements FieldStorageDefinitionInterface, FieldDefinitionInterface
- BaseFieldBundleOverride implements FieldConfigurationInterface
- FieldStorageConfig implements FieldStorageDefinitionInterface, ConfigEntityInterface
- FieldConfig implements FieldConfigurationInterface

// Config entity type names and config prefix:
- BaseFieldBundleOverride: base_field_bundle_override, core.field.*
- FieldStorageConfig: field_storage_config, field.storage.*
- FieldConfig: field_config, field.field.*
plach’s picture

#17 looks accurate to me (@effulgentsia++), just a couple of minor remarks that do not affect in any way the validity of this writeup:

However, code that acts on one entity at a time, shouldn't need to care whether a field is a base or bundle field; as far is it's concerned, it's just working on "all the fields of the entity", and at least with respect to that entity, a field is a field regardless of whether it also exists or not on other bundles.

This is a bit misleading: it's correct that you don't need to worry whether a particular field on an $entity object is base or bundle, BUT the main reason to have base fields is exactly being always able to rely on them. If a module defines (or targets) a particular entity type, the business logic it implements should be built on base fields, which are guaranteed to be there for any $entity object. A good example of this would be the price of a commerce product (aside from the fact I am pretty sure it will be implemented as configurable field as in D7, that is as a bundle field).

- BaseFieldBundleOverride implements FieldConfigurationInterface

Shouldn't it implement also ConfigEntityInterface? @yched brought up the idea of having a single config entity shared between Field[Instance]Config and BaseFieldBundleOverride, which would have lots of advantages, however @alexpott in #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields brought up good reasons for not doing that. I guess this discussion should be continued over there.

plach’s picture

Shouldn't it implement also ConfigEntityInterface?

Scratch that, sorry. I missed this due to the new line:

-  FieldConfigurationInterface extends FieldDefinitionInterface, ConfigEntityInterface
yched’s picture

Thanks for the comprehensive summary, pure @effulgentsia magic :)

I still stand that having both FieldDefinitionInterface and FieldConfigInterface, meaning a 3rd "orthogonal distinction" in your explanation, is an unfortunate cognitive overload, only justified by performance impact that we never actually evaluated, and thus qualifies as a preemptive, possibly premature optimization sadly baked in the shape of the API.

But I agree that this is our best way forward with #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields atm.

5. We need to remove setters from FDI and move them to FCI

Then BaseFieldDefinition needs to duplicate those setters' doc (which would also be the case in my scenario where BFD *not* implements FDI).

Also, if the setters present on BFD are not part of any interface, what type-hint do we use for methods/functions that:
- return such objects specifically:
EntityType::baseFieldDefinitions(), hook_entity_base_field_info[_alter](), EM::getbaseFieldDefinitions()...
- receive such objects with the explicit intent to use those setters :
EntityType::bundleFieldDefinitions(), hook_entity_bundle_field_info[_alter]()...
?

It seems we either type-hint on BFD directly, or need a BaseFieldDefinitionInterface with setters duplicated from FCI ?

FieldConfig implements FieldConfigurationInterface

Hm - maybe FieldConfigInterface for consistency ?
(I know I suggested FieldConfigurationInterface in IRC, re-reading it tickles now)

BaseFieldBundleOverride: base_field_bundle_override, core.field.*
FieldConfig: field_config, field.field.*

entity_type id and config prefixes: that's were I was not really sure, that's merely a proposal to be discussed in #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields.

yched’s picture

Further thinking on my comment #20 above :

With #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields, EntityType::bundleFieldDefinitions(), hook_entity_bundle_field_info[_alter]() should be made to return FCIs rather than clones of BFDs ? And thus, renamed to bundleFieldConfig() ?

Before :

public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $fields['some_field'] = clone $base_field_definitions['some_field'];
  $fields['some_field']->setXxx('foobar');
  return $fields;
}

After :

public static function bundleFieldConfig(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $fields['some_field'] = BaseFieldBundleOverride::createFromFieldDefinition($base_field_definitions['entity_id'], $bundle);
  // Or, easier:
  $fields['some_field'] = $base_field_definitions['some_field']->getConfig($bundle);
  $fields['some_field']->setXxx('foobar');
  return $fields;
}

A by-bundle clone of a BFD makes little sense, that's what FCIs are here for. Plus the current DX is horrible :-)

alexpott’s picture

What's great about #21 is that FD's could lose all their setters! +1

alexpott’s picture

re #22 ignore me I'm wrong.

yched’s picture

Trying to translate #17 into actionable issues:

1. We should rename FieldDefinition (the implementation class, not the interface) to BaseFieldDefinition, since it's an implementation specific to base fields

Opened #2315237: Rename FieldDefinition to BaseFieldDefinition (major, beta-target)

4. remove the getBundle() method from FDI. There are few callers of FDI::getBundle() and they can/should be replaced with either passing $bundle as a separate param (if the need for the bundle is unrelated to changing the definition) or using FCI instead (if the need for the bundle is to override the definition for that bundle).

5. remove setters from FDI and move them to FCI

Less sure whether those should be split to dedicated issues or adressed as a whole. The latter still has unresolved consequences mentioned in #20.

fago’s picture

Thanks for the great write-up effulgentsia.

I've read through all the IRC log as well, but #17 is a great summary to me. I think this is a solid plan, so overall +1, but here some additional comments:

  1. I like the way you point the distinction between base fields vs. bundle fields and FieldStorageDefinitionInterface vs FieldDefinitionInterface is ortogonal. Base field / bundle field determines for *whom* the fields have been defined, while FSD vs FD determines *what* has been defined.
  2. As yched pointed out in #21 we need a way to alter/override field definitions in alter hooks. I don't think we should or have to rename the hook, as the system in the end only cares to get field definitions back - that's the interface which matters for the system here. However, I agree that the provided DX should be around using field configurations. Maybe this would be a reasonable DX?
    $override = $base_field_definitions['some_field']->getBundleOverride($bundle);
    If getBundleOverride() finds an pre-existing bundle field which is not a bundle override, i.e. someone hand-crafted another implementation, it can still take over its values based on the interface getters + replace the object with an suiting override object. It's a litte bit of extra work for us to do, but that way we end up with a consistent way to override while the field definitions returned to and from the EM stay read-only again, i.e. if you ask the EM about the field definitions of a bundle you are not supposed to change anything and you can't.
  3. 1. We should rename FieldDefinition (the implementation class, not the interface) to BaseFieldDefinition, since it's an implementation specific to base fields

    Yep, that seems reasonable!

xjm’s picture

Priority: Critical » Major
Issue tags: -beta target +Pre-AMS beta sprint, +beta deadline

@alexpott, @effulgentsia, and I (well mostly @alexpott and @effulgentsia) discussed this issue at TCDrupal. We had the following @todo for @effulgentsia:

"Make sure all feedback is addressed and close works as designed"

I still think this is a major beta deadline, not a critical beta target, though I guess it's a moot point if we're closing it works as designed. As I stated above, I'm not comfortable with the idea of changing this after beta; it seems like it would impact a lot of code. Tagging to look at during the pre-AMS beta sprint.

effulgentsia’s picture

Closing this per #26, except not calling this "by design", because it's more of a performance compromise than a purely design-driven decision.

From what I can see, here's the feedback from comments that has not already been implemented:

We can only reuse BaseFieldDefinition objects cross-bundle if we remove the getBundle() method from FDI. There are few callers of FDI::getBundle() and they can/should be replaced with either passing $bundle as a separate param (if the need for the bundle is unrelated to changing the definition) or using FCI instead (if the need for the bundle is to override the definition for that bundle).

#2346421: WTF with getTargetEntityId() / getBundle() renamed FDI::getBundle() to getTargetBundle() rather than removing it entirely. We discussed this at DrupalCon Amsterdam though, and decided it's okay to leave this method on the interface. The claim about that method making BFD objects not reusable cross-bundle was incorrect, since the method is defined as being able to return NULL in that case. I still think that creates cruft that isn't ideal, but in the absence of it posing any functional problem, it's too late to change for D8.

EntityType::bundleFieldDefinitions(), hook_entity_bundle_field_info[_alter]() should be made to return FCIs rather than clones of BFDs ?

We also discussed this at AMS, and while I disagree with using FCI for something that is not configuration, I agree that HEAD's current approach of BFD cloning is problematic, so we opened #2346347: Finalize API for creating, overriding, and altering code-defined bundle fields for that.

Please comment if there's anything else left outstanding here other than the above.