defaultFieldSettings() causes config drift when new content entity types are added
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3618338. -->
Reported by: [lisotton](https://www.drupal.org/user/1902738)
Related to !61
>>>
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
<p><code>DynamicEntityReferenceItem::defaultFieldSettings()</code> dynamically queries <em>all</em> content entity types in the system and creates a settings entry for each one:</p>
<div class="codeblock">
<pre><span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #007700">public static function </span><span style="color: #0000BB">defaultFieldSettings</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$default_settings </span><span style="color: #007700">= [];<br> </span><span style="color: #0000BB">$labels </span><span style="color: #007700">= \</span><span style="color: #0000BB">Drupal</span><span style="color: #007700">::</span><span style="color: #0000BB">service</span><span style="color: #007700">(</span><span style="color: #DD0000">'entity_type.repository'</span><span style="color: #007700">)-></span><span style="color: #0000BB">getEntityTypeLabels</span><span style="color: #007700">(</span><span style="color: #0000BB">TRUE</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$options </span><span style="color: #007700">= </span><span style="color: #0000BB">$labels</span><span style="color: #007700">[(string) </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Content'</span><span style="color: #007700">, [], [</span><span style="color: #DD0000">'context' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Entity type group'</span><span style="color: #007700">])];<br> foreach (</span><span style="color: #0000BB">array_keys</span><span style="color: #007700">(</span><span style="color: #0000BB">$options</span><span style="color: #007700">) as </span><span style="color: #0000BB">$entity_type_id</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$default_settings</span><span style="color: #007700">[</span><span style="color: #0000BB">$entity_type_id</span><span style="color: #007700">] = [<br> </span><span style="color: #DD0000">'handler' </span><span style="color: #007700">=> </span><span style="color: #DD0000">"default:</span><span style="color: #0000BB">$entity_type_id</span><span style="color: #DD0000">"</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'handler_settings' </span><span style="color: #007700">=> [],<br> ];<br> }<br> return </span><span style="color: #0000BB">$default_settings</span><span style="color: #007700">;<br>}<br></span><span style="color: #0000BB">?></span></span></pre></div>
<p>During <code>FieldConfig::preSave()</code>, Drupal core merges the active field settings with these defaults:</p>
<div class="codeblock">
<pre><span style="color: #000000"><span style="color: #0000BB"><?php<br>$this</span><span style="color: #007700">-></span><span style="color: #0000BB">settings </span><span style="color: #007700">= </span><span style="color: #0000BB">array_intersect_key</span><span style="color: #007700">(</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">settings</span><span style="color: #007700">, </span><span style="color: #0000BB">$default_settings</span><span style="color: #007700">) + </span><span style="color: #0000BB">$default_settings</span><span style="color: #007700">;<br></span><span style="color: #0000BB">?></span></span></pre></div>
<p>This means that every time a new content entity type is added to the system (e.g., by enabling a module that defines one), the active configuration of <em>every</em> dynamic_entity_reference field automatically gains a new entry for that entity type. This causes the active configuration to diverge from the exported YAML in <code>config/sync</code>, forcing a config re-export (<code>drush cex</code>) even though no intentional configuration change was made.</p>
<p>This is particularly problematic in CI/CD pipelines that validate configuration consistency (e.g., checking that <code>drush cim</code> produces no changes), as every new entity type addition breaks the build.</p>
<h4 id="summary-steps-reproduce">Steps to reproduce</h4>
<ol>
<li>Create a dynamic_entity_reference field on any entity type.</li>
<li>Export configuration with <code>drush cex</code>.</li>
<li>Enable a module that defines a new content entity type (or create a new custom entity type).</li>
<li>Run <code>drush cim</code> followed by <code>drush cex</code>.</li>
<li>Observe that the field config YAML now has a new entry for the newly added entity type under <code>settings:</code>, even though no intentional change was made to the field.</li>
</ol>
<h3 id="summary-proposed-resolution">Proposed resolution</h3>
<p>Override <code>fieldSettingsToConfigData()</code> and <code>fieldSettingsFromConfigData()</code> in <code>DynamicEntityReferenceItem</code>:</p>
<ul>
<li><strong><code>fieldSettingsToConfigData()</code></strong>: Only persist entity type entries that have explicitly configured <code>handler_settings</code> (e.g., <code>target_bundles</code>, <code>sort</code>, <code>auto_create</code>). Entity types with empty/default <code>handler_settings</code> are omitted from the exported YAML.</li>
<li><strong><code>fieldSettingsFromConfigData()</code></strong>: When loading from config, fill in default settings for all currently available content entity types that are not explicitly stored. This ensures runtime behavior is unchanged.</li>
</ul>
<p>This approach ensures that:</p>
<ul>
<li>Only intentionally configured entity type references are persisted in config.</li>
<li>New entity types are transparently available at runtime without config drift.</li>
<li>Existing behavior is fully preserved — the runtime settings are identical.</li>
</ul>
<div class="codeblock">
<pre><span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #007700">public static function </span><span style="color: #0000BB">fieldSettingsToConfigData</span><span style="color: #007700">(array </span><span style="color: #0000BB">$settings</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$config_settings </span><span style="color: #007700">= [];<br> foreach (</span><span style="color: #0000BB">$settings </span><span style="color: #007700">as </span><span style="color: #0000BB">$key </span><span style="color: #007700">=> </span><span style="color: #0000BB">$value</span><span style="color: #007700">) {<br> if (!</span><span style="color: #0000BB">is_array</span><span style="color: #007700">(</span><span style="color: #0000BB">$value</span><span style="color: #007700">) || !isset(</span><span style="color: #0000BB">$value</span><span style="color: #007700">[</span><span style="color: #DD0000">'handler'</span><span style="color: #007700">])) {<br> </span><span style="color: #0000BB">$config_settings</span><span style="color: #007700">[</span><span style="color: #0000BB">$key</span><span style="color: #007700">] = </span><span style="color: #0000BB">$value</span><span style="color: #007700">;<br> continue;<br> }<br> </span><span style="color: #FF8000">// Only persist entity types with explicitly configured handler_settings.<br> </span><span style="color: #007700">if (!empty(</span><span style="color: #0000BB">$value</span><span style="color: #007700">[</span><span style="color: #DD0000">'handler_settings'</span><span style="color: #007700">])) {<br> </span><span style="color: #0000BB">$config_settings</span><span style="color: #007700">[</span><span style="color: #0000BB">$key</span><span style="color: #007700">] = </span><span style="color: #0000BB">$value</span><span style="color: #007700">;<br> }<br> }<br> return </span><span style="color: #0000BB">$config_settings</span><span style="color: #007700">;<br>}<br><br>public static function </span><span style="color: #0000BB">fieldSettingsFromConfigData</span><span style="color: #007700">(array </span><span style="color: #0000BB">$settings</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$labels </span><span style="color: #007700">= \</span><span style="color: #0000BB">Drupal</span><span style="color: #007700">::</span><span style="color: #0000BB">service</span><span style="color: #007700">(</span><span style="color: #DD0000">'entity_type.repository'</span><span style="color: #007700">)-></span><span style="color: #0000BB">getEntityTypeLabels</span><span style="color: #007700">(</span><span style="color: #0000BB">TRUE</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$options </span><span style="color: #007700">= </span><span style="color: #0000BB">$labels</span><span style="color: #007700">[(string) </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Content'</span><span style="color: #007700">, [], [</span><span style="color: #DD0000">'context' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Entity type group'</span><span style="color: #007700">])];<br> foreach (</span><span style="color: #0000BB">array_keys</span><span style="color: #007700">(</span><span style="color: #0000BB">$options</span><span style="color: #007700">) as </span><span style="color: #0000BB">$entity_type_id</span><span style="color: #007700">) {<br> if (!isset(</span><span style="color: #0000BB">$settings</span><span style="color: #007700">[</span><span style="color: #0000BB">$entity_type_id</span><span style="color: #007700">])) {<br> </span><span style="color: #0000BB">$settings</span><span style="color: #007700">[</span><span style="color: #0000BB">$entity_type_id</span><span style="color: #007700">] = [<br> </span><span style="color: #DD0000">'handler' </span><span style="color: #007700">=> </span><span style="color: #DD0000">"default:</span><span style="color: #0000BB">$entity_type_id</span><span style="color: #DD0000">"</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'handler_settings' </span><span style="color: #007700">=> [],<br> ];<br> }<br> }<br> return </span><span style="color: #0000BB">$settings</span><span style="color: #007700">;<br>}<br></span><span style="color: #0000BB">?></span></span></pre></div>
<h3 id="summary-remaining-tasks">Remaining tasks</h3>
<ul>
<li>Review the proposed patch.</li>
<li>Add test coverage for the new methods.</li>
<li>Verify that existing fields with mixed explicit/default settings survive a config export/import cycle without data loss.</li>
<li>Ensure the field settings form still works correctly after the change.</li>
</ul>
<h3 id="summary-ui-changes">User interface changes</h3>
<p>None.</p>
<h3 id="summary-api-changes">API changes</h3>
<p>Two new static method overrides are added to <code>DynamicEntityReferenceItem</code>:</p>
<ul>
<li><code>fieldSettingsToConfigData(array $settings)</code></li>
<li><code>fieldSettingsFromConfigData(array $settings)</code></li>
</ul>
<p>These are existing Drupal core API hooks (<code>FieldItemInterface</code>) that were not previously overridden by this module. No breaking changes to the public API.</p>
<h3 id="summary-data-model-changes">Data model changes</h3>
<p>The exported configuration YAML for dynamic_entity_reference fields will be smaller after re-export, as entity types with default/unconfigured handler_settings will no longer be stored. This is a one-time change requiring a <code>drush cex</code> after applying the patch. The runtime data model is unchanged.<br>
```</p>
issue
GitLab AI Context
Project: project/dynamic_entity_reference
Instance: https://git.drupalcode.org
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://git.drupalcode.org/project/dynamic_entity_reference/-/raw/3.x/README.md — project overview and setup
Repository: https://git.drupalcode.org/project/dynamic_entity_reference
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD