Fix AssertionError crash during component tree reconstruction when a component fails hydration
## Problem/Motivation When a component fails hydration (e.g., due to a missing component definition, a version mismatch, or invalid/extraneous input properties), `ComponentTreeItemList::getHydratedValue()` catches the exception and plans to defer it so that it can be displayed as an inline error box instead of crashing the page. However, during the subsequent tree reconstruction loop where children are nested into parent slots, the module asserts: ```php \assert(\array_key_exists('slots', $hydrated[$parent_uuid]) && \is_array($hydrated[$parent_uuid]['slots'])); ``` If a parent component failed hydration, it has no `'slots'` key. This assertion fails, crashing the entire page render process with a PHP Fatal Error (AssertionError) and preventing the site from loading. NOTE: My PHP skills are pretty much non-existent, so I had the AI help me look into this issue and generate the patch. Also, maybe this error is very specific of the way I modified the theme I was using and will not occur to other people. In my case, the errors where fixed. ### Traceback ``` AssertionError: assert(\array_key_exists('slots', $hydrated[$parent_uuid]) && \is_array($hydrated[$parent_uuid]['slots'])) in assert() (line 525 of modules/contrib/canvas/src/Plugin/Field/FieldType/ComponentTreeItemList.php). Drupal\canvas\Plugin\Field\FieldType\ComponentTreeItemList->getHydratedValue() (Line: 389) Drupal\canvas\Plugin\Field\FieldType\ComponentTreeItemList->getHydratedTree() (Line: 207) Drupal\canvas\Plugin\Field\FieldType\ComponentTreeItemList->toRenderable() (Line: 30) Drupal\canvas\Plugin\Field\FieldFormatter\NaiveComponentTreeFormatter->viewElements() (Line: 91) Drupal\Core\Field\FormatterBase->view() (Line: 275) ... ``` --- ## Cause In `ComponentTreeItemList.php`, hydration errors are caught and stored with the key `self::HYDRATION_EXCEPTION_KEY` instead of generating a `slots` array: ```php try { $explicit_input = $source->getExplicitInput($uuid, $item); } catch (\Throwable $e) { $hydrated[$uuid] = [ 'component' => $component_id, self::HYDRATION_EXCEPTION_KEY => $e, ]; continue; } ``` During tree reconstruction, the loop expects all parent components to have populated `slots`: ```php foreach ($this->getSlotChildrenDepthFirst() as $parent_uuid => ['slot' => $slot, 'uuid' => $uuid]) { if ($parent_uuid === self::ROOT_UUID) { continue; } \assert(\array_key_exists('slots', $hydrated[$parent_uuid]) && \is_array($hydrated[$parent_uuid]['slots'])); ``` If `$parent_uuid` points to a parent component that threw a hydration exception, `$hydrated[$parent_uuid]` lacks the `'slots'` key, causing the assertion to fail and crash the page. --- ## Proposed Resolution Modify `ComponentTreeItemList::getHydratedValue()` around line 525 to safely check if the parent component exists and contains the `'slots'` key. If it doesn't (because it failed to hydrate), skip and clean up the child: ```diff foreach ($this->getSlotChildrenDepthFirst() as $parent_uuid => ['slot' => $slot, 'uuid' => $uuid]) { if ($parent_uuid === self::ROOT_UUID) { continue; } - \assert(\array_key_exists('slots', $hydrated[$parent_uuid]) && \is_array($hydrated[$parent_uuid]['slots'])); + if (!\array_key_exists($parent_uuid, $hydrated) || !\array_key_exists('slots', $hydrated[$parent_uuid]) || !\is_array($hydrated[$parent_uuid]['slots'])) { + unset($hydrated[$uuid]); + continue; + } // Remove default slot value: this slot is populated. ``` --- ## Remaining Tasks - Review patch. - Apply patch and verify. [canvas-assertion-error.patch](/uploads/89ad722d8bbe0aaac4589aaf0ea6fff3/canvas-assertion-error.patch)
issue