Hydrating a component tree with an instance containing garbage values OutOfRangeException when component trees have inputs for non-existent props
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3578865. -->
Reported by: [mglaman](https://www.drupal.org/user/2416470)
Related to !723
>>>
<h3 id="overview">Overview</h3>
<p>When upgrading Canvas sites from 1.0 to 1.2, component trees with old component versions fail to render when those components have new active versions with additional props.</p>
<p>The database contains inputs for component trees that reference old component versions. When these component instances are loaded, the stored inputs include entries for props that don't exist in the stored version, causing <code>OutOfRangeException</code> errors during rendering.</p>
<h4>Steps to reproduce (assumed!)</h4>
<ol>
<li>Start with a Canvas 1.0 site with component trees</li>
<li>Update components to add new props in newer versions</li>
<li>Upgrade to Canvas 1.2</li>
<li>Attempt to render pages with existing component trees</li>
</ol>
<h4>Example error traces</h4>
<pre>OutOfRangeException 'text' is not a prop on this version of the Component 'Code component: <em class="placeholder">Footer</em>'.<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php:209 Drupal\canvas\Plugin\Canvas\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::getDefaultStaticPropSource<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php:1359 Drupal\canvas\Plugin\Canvas\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::uncollapse<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php:338 Drupal\canvas\Plugin\Canvas\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::getExplicitInput<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Field/FieldType/ComponentTreeItemList.php:464 Drupal\canvas\Plugin\Field\FieldType\ComponentTreeItemList::getHydratedValue<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Field/FieldType/ComponentTreeItemList.php:371 Drupal\canvas\Plugin\Field\FieldType\ComponentTreeItemList::getHydratedTree<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Field/FieldType/ComponentTreeItemList.php:200 Drupal\canvas\Plugin\Field\FieldType\ComponentTreeItemList::toRenderable<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/DisplayVariant/CanvasPageVariant.php:167 Drupal\canvas\Plugin\DisplayVariant\CanvasPageVariant::Drupal\canvas\Plugin\DisplayVariant\{closure}<br> [internal] Fiber::start<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/DisplayVariant/CanvasPageVariant.php:168 Drupal\canvas\Plugin\DisplayVariant\CanvasPageVariant::build</pre><pre>OutOfRangeException 'background' is not a prop on this version of the Component 'Code component: <em class="placeholder">Hero</em>'.<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php:210 Drupal\canvas\Plugin\Canvas\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::getDefaultStaticPropSource<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php:1410 Drupal\canvas\Plugin\Canvas\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::uncollapse<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php:350 Drupal\canvas\Plugin\Canvas\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::getExplicitInput<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Field/FieldType/ComponentTreeItemList.php:475 Drupal\canvas\Plugin\Field\FieldType\ComponentTreeItemList::getHydratedValue<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Field/FieldType/ComponentTreeItemList.php:373 Drupal\canvas\Plugin\Field\FieldType\ComponentTreeItemList::getHydratedTree<br> /var/www/html/docroot/modules/contrib/canvas/src/Plugin/Field/FieldType/ComponentTreeItemList.php:200 Drupal\canvas\Plugin\Field\FieldType\ComponentTreeItemList::toRenderable<br> /var/www/html/docroot/modules/contrib/canvas/src/Entity/ContentTemplate.php:360 Drupal\canvas\Entity\ContentTemplate::build<br> [internal] array_map<br> /var/www/html/docroot/modules/contrib/canvas/src/Entity/ContentTemplate.php:379 Drupal\canvas\Entity\ContentTemplate::buildMultiple<br> /var/www/html/docroot/core/lib/Drupal/Core/Entity/EntityViewBuilder.php:341 Drupal\Core\Entity\EntityViewBuilder::buildComponents<br> /var/www/html/docroot/core/modules/node/src/NodeViewBuilder.php:29 Drupal\node\NodeViewBuilder::buildComponents<br> /var/www/html/docroot/modules/contrib/canvas/src/EntityHandlers/ContentTemplateAwareViewBuilder.php:127 Drupal\canvas\EntityHandlers\ContentTemplateAwareViewBuilder::buildComponents<br> /var/www/html/docroot/core/lib/Drupal/Core/Entity/EntityViewBuilder.php:283 Drupal\Core\Entity\EntityViewBuilder::buildMultiple<br> /var/www/html/docroot/core/lib/Drupal/Core/Entity/EntityViewBuilder.php:240 Drupal\Core\Entity\EntityViewBuilder::build<br> [internal] call_user_func_array</pre><p>The error occurs in <code>GeneratedFieldExplicitInputUxComponentSourceBase::getDefaultStaticPropSource()</code> when it tries to uncollapse inputs for props that don't exist in the component version being rendered.</p>
<h3 id="proposed-resolution">Proposed resolution</h3>
<p>Filter stored inputs against the component version's available props before attempting to process them. This prevents the system from trying to process inputs for props that don't exist in the loaded version.</p>
<p>Three approaches have been considered:</p>
<h4>Approach 1: Try-catch in getExplicitInput() (quick fix)</h4>
<p>Wrap the uncollapse call in a try-catch to silently skip invalid props in production while alerting developers:</p>
<pre>diff --git a/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php b/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php<br>index 92fb3bf4..1a1c677a 100644<br>--- a/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php<br>+++ b/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php<br>@@ -347,7 +347,14 @@ abstract class GeneratedFieldExplicitInputUxComponentSourceBase extends Componen<br> $values = $item->getInputs() ?? [];<br> $resolved_values = [];<br> foreach ($values as $prop => $input) {<br>- $values[$prop] = $this->uncollapse($input, $prop)->toArray();<br>+ try {<br>+ $values[$prop] = $this->uncollapse($input, $prop)->toArray();<br>+ }<br>+ catch (\OutOfRangeException $e) {<br>+ // Do not crash in production, but let developers be aware locally.<br>+ \assert(FALSE, $e->getMessage());<br>+ continue;<br>+ }<br> try {<br> $resolved_values[$prop] = PropSource::parse($values[$prop])<br> ->evaluate($fieldable_host_entity, is_required: FALSE);</pre><h4>Approach 2: Check prop existence before processing (cleaner)</h4>
<p>Filter inputs in both <code>getExplicitInput()</code> and <code>optimizeExplicitInput()</code> to skip props not in <code>prop_field_definitions</code>:</p>
<pre>diff --git a/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php b/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php<br>index 92fb3bf4..1a1c677a 100644<br>--- a/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php<br>+++ b/src/Plugin/Canvas/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php<br>@@ -346,6 +346,12 @@<br> $values = $item->getInputs() ?? [];<br> $resolved_values = [];<br> foreach ($values as $prop => $input) {<br>+ // Skip inputs for props that do not exist in this version's<br>+ // prop_field_definitions: the component instance's stored inputs may<br>+ // reference props from a different version than the one loaded.<br>+ if (!\array_key_exists($prop, $this->configuration['prop_field_definitions'])) {<br>+ continue;<br>+ }<br> $values[$prop] = $this->uncollapse($input, $prop)->toArray();<br> try {<br> $resolved_values[$prop] = PropSource::parse($values[$prop])<br>@@ -1301,6 +1307,12 @@<br><br> public function optimizeExplicitInput(array $values): array {<br> foreach ($values as $prop => $input) {<br>+ // Skip and remove inputs for props that do not exist in this version's<br>+ // prop_field_definitions: the component instance's stored inputs may<br>+ // reference props from a different version than the one loaded.<br>+ if (!\array_key_exists($prop, $this->configuration['prop_field_definitions'])) {<br>+ unset($values[$prop]);<br>+ continue;<br>+ }<br> // Every input for a component instance of this ComponentSource plugin<br> // base class MUST be a PropSourceBase, which all are stored as arrays.<br> // @see \Drupal\canvas\PropSource\PropSourceBase::toArray()</pre><h4>Approach 3: Centralize filtering in ComponentInputs (most robust)</h4>
<p>Implement the filtering in <code>Drupal\canvas\Plugin\DataType\ComponentInputs::getValues()</code> to handle this at the data layer, ensuring all code paths benefit from the fix.</p>
<h3 id="remaining-tasks">Remaining tasks</h3>
<ol>
<li>Determine preferred approach (try-catch, explicit checks, or ComponentInputs filtering)</li>
<li>Write patch</li>
<li>Add test coverage for version mismatch scenarios</li>
<li>Review and commit</li>
</ol>
<h3 id="user-interface-changes">User interface changes</h3>
<p>None. This is a backend bug fix with no UI changes.</p>
<h3 id="api-changes">API changes</h3>
<p>None. This maintains backward compatibility while adding resilience to version mismatches.</p>
<h3 id="data-model-changes">Data model changes</h3>
<p>None. The fix handles existing data more gracefully without requiring schema or data migrations.</p>
> Related issue: [Issue #3524401](https://www.drupal.org/node/3524401)
> Related issue: [Issue #3568906](https://www.drupal.org/node/3568906)
> Related issue: [Issue #3573022](https://www.drupal.org/node/3573022)
> Related issue: [Issue #3517941](https://www.drupal.org/node/3517941)
issue