Spin off from #2015697: Convert field type to typed data plugin for file and image modules.
[disclaimer: I personally don't think that's a big issue, but opened this at the request of @claudiu.cristea :-)]

Context

FieldDefinitionInterface, introduced in #1950632: Create a FieldDefinitionInterface and use it for formatters and widgets, merges the notion of field-level & instance-level settings into one single set of 'settings', because:
a) only "configurable fields" have a notion of 'instances', base fields have a single 'field" definition structure
b) 95% client code doesn't care where the setting comes from, they just need the setting value for the current field definition structure they work with.
c) this works transparently for code that only has a $field structure to work with and no $instance (i.e. code that works at a cross-bundles level, e.g. Views, some parts of Entity_ref).

Then there is the (rare) case where a field type uses a setting with the same name at both levels - Image fields currently build on this for their 'default_image' setting.
- It was decided in #1950632: Create a FieldDefinitionInterface and use it for formatters and widgets that one level would take precedence over the other.
- #2060003: Wrong precedence in Field::getFieldSetting[s]() when setting appears in both field and instance fixed FieldInstance::getFieldSetting[s]() so that the order is "instance wins over field" instead of the opposite - that's what image fields expect.

Problem

The issue is that whichever precedence we set for field-level vs instance-level settings within FieldInstance::getFieldSettings(), there is always code that needs the opposite.

"Instance wins over field" is the correct choice for most consuming code. But then there is ConfigFieldItem::settingsForm(), that is in charge of building the settings form for the *field* level settting. By definition, this code needs the field-level values of the settings, not covered by the instance level, to place in the form's #default_value.

The recommended way to access settings within a FieldItem class is $this->getFieldSettings(), which is a shortcut for $this->getFieldDefinition()->getFieldSettings(). But since $this->getFieldDefinition() is an $instance, $this->getFieldSettings() will give you "instance wins over field" values. See #2015697-31: Convert field type to typed data plugin for file and image modules.

Currently ImageItem::settingsForm() works around this with:

$field = $this->getFieldDefinition()->getField();
$settings = $field->getFieldSettings();

This works because in practice $this->getFieldDefinition() is a FieldInstance, so getField() works. Our current runtime flow currently never calls settingsForm() if the field is a base field, base fields are not configurable.
But strictly speaking, $this->getFieldDefinition() returns a FieldDefinitionInterface, and getField() is not part of that interface.

Even if base fields stay non-configurable and we never display settings forms for them, we might come to a point where Field UI displays a non-editable summary of their settings (like for widgets & formatters). Then the method in charge of displaying this summary output would have the same issues.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

yched’s picture

So my take on this, and the reason I consider this to be a non-issue, is:

- If a field type chooses to build functionality on "a setting appears at both field and instance levels", then it's its job to deal with the consequences.
- The consequences are: 'when used on a configurable field, you need some extra dance if you need to access the "field settings not covered by the instance level".
- That extra dance is:

// Only get field-level settings on configurable fields, 
// since we have a setting present at both levels.
if ($this->getFieldDefinition() instanceof FieldInstanceInterface) {
  $settings = $this->getFieldDefinition()->getField()->getFieldSettings();
}
else {
  $settings = $this->getFieldSettings();
}

And that is perfectly fine IMO. Some code has special logic in case it deals with configurable fields.

effulgentsia’s picture

I agree with #1, but you can also call $this->getInstance() instead of checking whether getFieldDefinition() happens to be an instance. Isn't that why ConfigFieldItemInterface has getInstance() as part of its interface? It's exactly the functionality of providing settings forms that makes a field type a "configurable" field type, and for that purpose, and that purpose only, it makes sense to act on the "instance" and "field" config entities directly, rather than via getFieldDefinition().

Even if base fields stay non-configurable and we never display settings forms for them, we might come to a point where Field UI displays a non-editable summary of their settings (like for widgets & formatters). Then the method in charge of displaying this summary output would have the same issues.

Wouldn't Field UI need to choose whether to display the summary as a net summary, or as a partitioned summary of field and instance? If it chooses the former, getFieldDefinition() would be what it needs to act on, for both configurable and nonconfigurable. If it chooses the latter, then it would be meaningless to do that for a nonconfigurable field, so it should only do so for configurable fields, and therefore, safely act on getInstance() and getInstance()->getField().

yched’s picture

Thing is, I'd really want to remove getInstance() :-)

We added it in the rush of the home stretch for the "field types as plugins" patch, when figuring out how to merge with the FieldDefinitionInterface that went in in parallel. But as we've seen in the various "convert D7 field types to plugins" issues since then, its existence makes it too tempting to use in other methods in FieldItem classes, de facto making then unusable for base fields. We don't want to end up with almost-duplicate field types, one that works for base fields, one that works for configurable fields - more on this in #2052135: Determine how to deal with field types used in base fields in core entities :-)

The same "field type" classes should be usable for base fields and configurable fields alike, and do their work using getFieldDefinition() / FieldDefinitionInterface only, without assuming that it is a "configurable FieldInstance". Current HEAD will then happen to never call settingsForm() / instanceSettingsForm() if the field type is used by a base field, but the rest of methods work the same.

Aside from this edge case about "settingsForm() & setting with same name at both field and instance level", this works really well. And special cases can use code like #1 when they need to differentiate.

yched’s picture

Issue summary: View changes

typo

effulgentsia’s picture

Ok. Do you think it makes sense to add a parameter to getFieldSetting(s)(), like $setting_source, that could be one of 3 constants: SETTING_SOURCE_DEFINITION (default), SETTING_SOURCE_INSTANCE, SETTING_SOURCE_FIELD? With those constants defined in ConfigFieldItemInterface and the logic in #1 implemented within ConfigFieldItemBase::getFieldSetting(s)()?

yched’s picture

Mulling over #4 :-)

Meanwhile, opened #2074547: Remove ConfigFieldItemInterface::getInstance().

yched’s picture

re #4: yeah, would make sense I guess. We had considered adding such flags in getFieldSetting(s)() in FieldDefinitionInterface, but that felt wrong / convoluted. Adding that to the helper methods within ConfigField / ConfigFieldItem sounds like it makes more sense and is more manageable.

Not sure we'd need three constants though ? The default behavior + one "field only" option should be enough ? (then, could be a boolean flag or two constants)
An "instance only" mode seems like it would need to support code to spill within Field/FieldInstance::getFieldSettings(), which I'd rather avoid, and we have no use case.

effulgentsia’s picture

An "instance only" mode seems like it would need to support code to spill within Field/FieldInstance::getFieldSettings()

I was thinking $field->settings and $instance->settings would work so long as those are public properties of the config entities. And if we ever make them non-public, we'll need to add method accessors (separate from getFieldSettings()) for it anyway. There's a conceptual difference between "the settings stored in my config entity's yaml file" and "the total effective settings for the field item I'm defining" that we shouldn't remove.

The default behavior + one "field only" option should be enough ?

What this is saying is that a field type in generating an instance settings form could always treat a configured setting for the field as a default for the instance. For example, if an image field's 'default_image' is configured to something at the field level, then on the instance settings form for a new instance, we want that as the default. Which then also means, on saving the instance, that would get saved to the instance's yaml file, and a future change of the field-level 'default_image' wouldn't propagate to the existing instances. I think that's pretty reasonable, but I don't know if we have any Field API history for that being undesirable. If we're ok with this behavior though in all cases where field and instance have the same setting key, then yeah, a boolean instead of 3 constants makes sense.

yched’s picture

Drats, that's right. instanceSettingsForm() has the mirror issue of course.

The behavior you describe in #7 paragraph 2 is not something we want IMO. It would be a semi-wanted byproduct of the current code, I don't think we want to support and maintain such a feature around "settings". It's also not the behavior image field expects for its 'default_image' anyway.

So then yes, this means a way to get "instance settings not merged with field settings", which is not something FieldInstance::getSettings() supports right now. If we want to add support for such flags in FieldItem::getFieldSettings() only, then it means fetching $instance->settings directly - but then we still need to merge 'default instance settings', because some might be missing in $instance->settings...

effulgentsia’s picture

Status: Active » Needs review
FileSize
9.69 KB

How's this?

Status: Needs review » Needs work

The last submitted patch, field-type-settings-forms-9.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
1.74 KB
9.73 KB
andypost’s picture

I think it's no-go because getFieldSettings($source) is hard to understand for developers.
Expected argument is $settings_key not the settings storage...

  1. +++ b/core/modules/field/lib/Drupal/field/Entity/Field.php
    @@ -561,6 +561,14 @@ public function getBundles() {
    +    $settings = $this->settings + $this->getDefaultSettings();
    +    return $settings;
    
    @@ -576,13 +584,7 @@ public function getFieldType() {
    +    $settings = $this->settings + $this->getDefaultSettings() + $this->getDefaultInstanceSettings();
         return $settings;
    

    no reason in intermediate variable just return $this->settings + ...;

  2. +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/FieldType/ConfigFieldItemBase.php
    @@ -8,12 +8,17 @@
    +  const SETTING_SOURCE_DEFINITION = 0;
    +  const SETTING_SOURCE_FIELD = 1;
    +  const SETTING_SOURCE_INSTANCE = 2;
    
    @@ -45,4 +50,44 @@ public function instanceSettingsForm(array $form, array &$form_state) {
    +  protected function getFieldSettings($source = self::SETTING_SOURCE_DEFINITION) {
    ...
    +    if ($source == self::SETTING_SOURCE_DEFINITION) {
    ...
    +    elseif (!($field_definition instanceof FieldInstanceInterface)) {
    ...
    +    elseif ($source == self::SETTING_SOURCE_FIELD) {
    ...
    +    elseif ($source == self::SETTING_SOURCE_INSTANCE) {
    

    Suppose DX-- here

effulgentsia’s picture

Would different methods, like getFieldSettingsFromFieldOnly() and getFieldSettingsFromInstanceOnly() be better DX, and if so, any suggestions for better names than that?

effulgentsia’s picture

Alternatively, we could make every single field type's instanceSettingsForm() method start with:
$settings = $this->getFieldDefinition()->getSettings(), and every settingsForm() method start with $settings = $this->getFieldDefinition()->getField()->getSettings(), and then not need a wrapper method in the base class? But I think we'd then need a comment above that line explaining what we're doing, and we'd be duplicating that for each field type.

claudiu.cristea’s picture

What about disallowing the sharing of setting name between field and instance? What are consequences? What would be wrong with that? This will keep think clean and will drop any confusion.

effulgentsia’s picture

What about disallowing the sharing of setting name between field and instance? What are consequences? What would be wrong with that?

Well, currently, ImageFormatterBase::prepareView() has this code:

$instance = field_info_instance($entity->entityType(), $this->fieldDefinition->getFieldName(), $entity->bundle());
// Use the default for the instance if one is available.
if (!empty($instance['settings']['default_image'])) {
  $fid = array($instance['settings']['default_image']);
}
// Otherwise, use the default for the field.
// Note, that we have to bypass getFieldSetting() as this returns the
// instance-setting default.
elseif (($field = $this->fieldDefinition->getField()) && !empty($field->settings['default_image'])) {
   $fid = array($field->settings['default_image']);
}

That kind of sucks. We should be able to replace all of this with $fid = $this->getFieldSetting('default_image');. Same for any contrib field type that wants to allow a per-instance setting to override a per-field setting.

Here's a patch that I think addresses the DX concerns of #12.2. In other words, for settingsForm() and instanceSettingsForm(), we pass to those functions the settings that that form is responsible for. Everywhere else, getFieldSetting(s)() returns the correctly merged setting(s). Thoughts?

yched’s picture

We should be able to replace all of this with $fid = $this->getFieldSetting('default_image');

Yes, #2075095: Widget / Formatter methods signatures needlessly complex does that while touching ImageFormatterBase::prepareView(). That other issue might be tricky to get in fast though :-).

I've been considering whether we should drop support for "same setting name" too, but I had to agreee that Image use case, while not very frequent, is totally valid, and it would be sad to make it more complicated by forcing the setting to be split in two different names. Two separate settings would also look weirder when the field type is used for a base field.

I need to look more closely at the patch, but I don't really see the DX as particularly awful...

effulgentsia’s picture

This addresses #12.1. I think that covers all feedback so far, so looking forward to more.

effulgentsia’s picture

#18: field-type-settings-forms-18.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, field-type-settings-forms-18.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
26.43 KB

Straight reroll.

yched’s picture

My issue with latest approach is that injecting some $settings into the methods makes the status of the code a bit shaky:
the object still holds a FieldDefinition, but only parts of it should be trusted in those methods, settings should be read from the incoming param.

But more globally - sorry for this :-/, I think I'm turning sides and would advocate we just don't go out of our way to support "same name settings".

@effulgentsia #16 :

Currently, ImageFormatterBase::prepareView() has this code:

if ($instance has a 'default_image')) {
  use that one;
}
elseif ($field has a 'default_image') {
   use that one.
}

That kind of sucks. We should be able to replace all of this with $fid = $this->getFieldSetting('default_image');

Myself #17:

Agreed, patch in #2075095: Widget / Formatter methods signatures needlessly complex does that

... and it turns out it doesn't work of course :-).

We make sure that FieldInstance objects get saved with at least default instance-level settings. So $instance['settings']['default_image'] will always be present (possibly just an empty string). And FieldInstance::getSetting() always makes $instance-level settings, even if "empty" (for "some" definition of empty: NULL, '', 0, FALSE, 'im_empty'...), cover up whatever is at the field level.
There's no logic we can place in there to make "give me the instance setting, if "empty" look in the field" work magically - at least not without reconsidering the auto-population of default values, and I'm frankly not keen on going there only to support what remains an edge case...

So yes, the promise of supporting "same setting name" in instance & field is if consuming code could just "not care" and say "give me the setting" and we read it from wherever it is present. But I don't think we can do that.
Then instead, consuming code has to do extra work for "is there a value at $instance ? if not, I'll grab the $field and check in there" - and this, in code that is supposed to not know about $field and $instance but work only with FieldDefinitionInterface.

So in the end, I agree with @claudiu.cristea in #15. We should just drop that IMO.
The image field type can have a 'default_image' field setting and an 'instance_default_image' instance setting. Consuming code checks both places in order, not the end of the world. ImageItem can even provide a helper getDefaultImage() method that does that and let consuming code be simple.

effulgentsia’s picture

I disagree with #22. Will write up why in a bit. For now, here's a straight reroll. I'll clean up this patch a bit more, but posting the reroll first, so that I can attach interdiffs with subsequent patches.

effulgentsia’s picture

This patch removes adding methods to Field[Instance][Interface]. That's out of scope for this issue and detracts from the main point of the patch.

effulgentsia’s picture

My issue with latest approach is that injecting some $settings into the methods makes the status of the code a bit shaky:
the object still holds a FieldDefinition, but only parts of it should be trusted in those methods, settings should be read from the incoming param.

It's not about trust, it's about meaning. getFieldSettings() is about getting the settings as they should apply to the field data being described by the field definition. It's analogous to getComputedStyle() for working with a DOM element's CSS value in JavaScript. For example, maybe we should add a drupal_alter() step at the end of FieldInstance::getFieldSettings() to allow modules to runtime alter the settings without affecting what's stored in the CMI files. Perhaps we don't need to, since CMI also comes with a context override system, but conceptually, we could add that drupal_alter() and the meaning should still make sense: everything that needs to act on a field data object would just start using those altered settings. However, providing the field or field instance settings forms has nothing to do with that meaning of getFieldSettings(). Instead, the entire purpose of the form is to provide a UI to what is stored in the CMI files. Injecting those settings into the form functions makes sense. Continuing the getComputedStyle() analogy, it would be like a browser plugin that allowed you to directly edit the CSS files of a web page: it would need to present to you the direct values that are in those files, not the net result of getComputedStyle().

There's no logic we can place in there to make "give me the instance setting, if "empty" look in the field" work magically - at least not without reconsidering the auto-population of default values, and I'm frankly not keen on going there only to support what remains an edge case...

That doesn't seem like that big a deal to solve. Surely, we can filter out NULLs either from what we save to the instance's CMI file, or from what is merged in getFieldSettings(). But even if we choose to not do that, and instead drop support for setting inheritance, I still think we should proceed with this patch for the reasons stated above.

ImageItem can even provide a helper getDefaultImage() method that does that and let consuming code be simple.

That's a great workaround if we choose to not support automatic setting inheritance.

yched’s picture

Not sure I'm convinced by the "inject settings" rationale, it's still a bit of a WTF IMO - and the idea of a drupal_alter() on settings makes me cringe :-) But I'll mull over this a bit.

Surely, we can filter out NULLs either from what we save to the instance's CMI file, or from what is merged in getFieldSettings().

Main problem is submitted form values from instanceSettingsForm(). That would mean trusting implementations to generate forms that produce submitted form values that are consistently NULL (or some when the setting is supposed to be "not set, fallback to the field-level setting in case it's set". The current form for the instance 'default_value' returns '0' for "not set", for example. But '0' might be a perfectly valid value for a different setting. 'settings' is the land of field types, we currently don't assign any fixed semantic or behavior to the values. We store them as they're given to us and hand them back when asked for them, that's it.

Honestly, this feels like a can of worms to me, for what is an edge case with an easy workaround :-)

andypost’s picture

Priority: Normal » Major
Issue tags: +DX (Developer Experience)

Working on #2013679-186: Helper issue for #1953408 (Remove ArrayAccess BC layer) I found that existence of getFieldSettings() is really confusing

  • no way to use empty() on setting value, suppose this is a killer of dx
  • you can call it on field and instance but result always merged unpredictable settings
  • same applies to getFieldSetting()
  • a lot of places in core where we have to use ->settings[] to access this by the right way (image and a lot of tests)

Suppose suggested 3rd parameter will bring dx down because most of devs will prefer to access settings directly,
primary for empty() and isset()

So I suggest to get rid of it asap and use plain arrays

yched’s picture

@andypost:
This is not what this issue is about. getFieldSetting() is not going anywhere, and we won't switch back to plain arrays - precisely because we need a merge functionality so that configurable fields' $field & $instance structures can act as FieldDefinitionInterface objects, and features can build on "base fields" & "configurable fields" indistinctly.

Note that you can still get the settings as an array, if that makes more sense for the piece of code you're writing, with getFieldSetting*s*().

andypost’s picture

andypost’s picture

Issue summary: View changes

typos

tim.plunkett’s picture

Priority: Major » Normal
Issue summary: View changes

This was bumped to major in 27, but with a proposal that is out of scope. So demoting.

Status: Needs review » Needs work

The last submitted patch, 24: field-type-settings-forms-24.patch, failed testing.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.