Issue #3591611: Statically enforce that CanvasConfigUpdater Component update paths are wired into ::preSave()
Resolves #3591611 (closed).
What this does
Adds attribute-driven static enforcement so a CanvasConfigUpdater method that
heals a Component cannot silently skip its Component::preSave() wiring — the
bug found while reviewing #3591607 (closed)
(!1175 note 1070980,
suggested in
note 1070982).
#[ComponentPreSaveUpdate(postUpdate: '…')]— marks an updater that must heal a Component on save, and records the one-timepost_updatethat heals already-stored config.#[NotAppliedOnComponentPreSave(reason: '…')]— explicit opt-out, so "must not run on save" is a stated decision rather than an omission.ComponentPreSaveUpdateMethodsRule— every#[ComponentPreSaveUpdate]method must be (a) invoked fromComponent::preSave()and (b) name apost_updatethat exists.ComponentConfigUpdaterMustDeclarePreSaveIntentRule— every public, non-needs*,Component-taking updater must declare exactly one of the two attributes, so forgetting to even consider preSave fails the build.- Annotates the five existing updaters (required-flag, text-value, prop-order, category-unset, multi-branch reference).
Verification
composer phpstanpasses.- Each failure mode fails the build with a distinct identifier: un-wiring a call
→
canvas.componentPreSaveUpdate; naming a missing post_update →canvas.componentPreSaveUpdatePostUpdate; a mutator with no attribute →canvas.componentPreSaveUpdateIntent.
UI changes
None — developer-facing static analysis only.
AI-Generated: Yes (Claude Code (Opus 4.8) drafted the attributes, the two custom PHPStan rules, and this description; human-reviewed.)