feat: #3621557 Option 2 - run without the Canvas patch by supplying ComponentTreeLoader

Issue: https://www.drupal.org/project/canvas_override/issues/3621557

This is Option 2 on issue #3621557. Option 1 is !26 (merged) (fail safely with a 403 when Canvas cannot edit an entity, accepting that the feature stays off without the patch). This MR instead makes the feature actually work with no Canvas patch applied. They are alternatives — pick one, or take !26 (merged) as the safety net and this as the capability.

Summary

Canvas ships Drupal\canvas\Storage\ComponentTreeLoader as a final class, and six Canvas services type-hint that concrete class, so no module-side substitution is possible and per-content layouts normally require the #3567225 patch.

Option 2 ships a copy of that class — canvas-compat/ComponentTreeLoader.php, which is Canvas's own file with final removed and the promoted constructor properties made protected (exactly the #3567225 PHP patch) — and loads it under Canvas's own FQCN through a prepended autoloader registered from a Composer files-autoloaded bootstrap (canvas_override.class_override.php). Registering via files means it is in place before the class is first used and before the service container is compiled, on every request including warm-container ones. The existing CanvasOverrideComponentTreeLoader subclass and the service swap in CanvasOverrideServiceProvider then work unchanged — no other module code changes.

The replacement is gated on detecting final class ComponentTreeLoader in the installed Canvas file. On a patched Canvas, or any future Canvas release that no longer marks it final, the autoloader stands aside and Canvas's real class is loaded — so a patched site never runs the copy and can never run a stale one.

Verification

On Drupal CMS 2.1 (Drupal 11.4.6) with Canvas 1.10.1 deliberately left unpatched (final class ComponentTreeLoader still on disk), installed via Composer so autoload.files is genuinely active:

  • ReflectionClass on the FQCN resolves to canvas-compat/ComponentTreeLoader.php, isFinal() is false, isComponentTreeLoaderExtendable() is true
  • the loader service is Drupal\canvas_override\Storage\CanvasOverrideComponentTreeLoader
  • the editor opens and seeds 4 components from the ContentTemplate, a dropped component publishes, the per-content layout renders on the front end, and reset restores the default
  • no TypeError or LogicException logged anywhere
  • re-applying the patch flips the FQCN back to Canvas's own file (verified), proving the gate

PHP lint clean; the 12 PHPUnit unit tests pass.

Trade-offs (documented in the README)

  • The copy must be re-synced with Canvas's src/Storage/ComponentTreeLoader.php on every Canvas release (re-apply the same two changes). The file records the Canvas version it was last synced with.
  • Because it occupies another project's class name it relies on normal Composer autoloading; it will not take effect under opcache.preload or a classmap-authoritative autoloader that has already frozen Canvas's class.

The durable fix remains upstream: land #3567225, ideally alongside extracting a ComponentTreeLoaderInterface and re-hinting the six consumers, which would make a module-side swap legitimate permanently.

AI-Generated: Yes (authored with Claude Code; reviewed by Rajab Natshah.)

Checkpoints

  • File an issue about this project
  • Addition/Change/Update/Fix to this project
  • Testing to ensure no regression
  • Automated unit/functional testing coverage
  • Developer Documentation support on feature change/addition
  • User Guide Documentation support on feature change/addition
  • UX/UI designer responsibilities
  • Accessibility and Readability
  • Reviewed by a human
  • Code review by maintainers
  • Full testing and approval
  • Credit contributors
  • Review with the product owner
  • Update Release Notes
  • Release
Edited by Rajab Natshah

Merge request reports

Loading
Loading