fix: plugin classes fail to load when the Composer classmap is stale - #2956
Open
lucadobrescu wants to merge 2 commits into
Open
fix: plugin classes fail to load when the Composer classmap is stale#2956lucadobrescu wants to merge 2 commits into
lucadobrescu wants to merge 2 commits into
Conversation
Contributor
Bundle Size Diff
|
Contributor
|
Plugin build for fb75ec3 is ready 🛎️!
|
Contributor
There was a problem hiding this comment.
Pull request overview
Prevents frontend fatal errors when an autoload-list class is unavailable.
Changes:
- Guards class instantiation with type and existence checks.
- Adds PHPUnit coverage for invalid and unavailable entries.
- Adds serialized E2E coverage for frontend and admin resilience.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
inc/class-main.php |
Skips unloadable autoload entries. |
tests/test-main-autoload.php |
Tests autoload resilience and bundled classes. |
packages/e2e-tests/mu-plugins/otter-e2e-bootstrap.php |
Adds the broken-autoloader scenario flag. |
src/blocks/test/e2e/blocks/autoloader-resilience.spec.js |
Verifies frontend and admin requests survive. |
src/blocks/test/e2e/playwright.config.js |
Serializes the stateful resilience spec. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Contributor
|
@lucadobrescu let's research if we can fix the loading. Skipping the check is more like a compromise for a broken pipeline. |
Contributor
E2E TestsPlaywright Test Status: See serial and parallel matrix jobs Performance ResultsserverResponse: {"q25":310.2,"q50":337.65,"q75":345.2,"cnt":10}, firstPaint: {"q25":1158.1,"q50":1298.9,"q75":1486.7,"cnt":10}, domContentLoaded: {"q25":3004.2,"q50":3058.4,"q75":3115.3,"cnt":10}, loaded: {"q25":3006.1,"q50":3060.1,"q75":3117,"cnt":10}, firstContentfulPaint: {"q25":3414,"q50":3459.5,"q75":3534.7,"cnt":10}, firstBlock: {"q25":12252.6,"q50":12325.85,"q75":12457.3,"cnt":10}, type: {"q25":22.39,"q50":23.59,"q75":27.02,"cnt":10}, typeWithoutInspector: {"q25":20.54,"q50":21.11,"q75":22.66,"cnt":10}, typeWithTopToolbar: {"q25":29.25,"q50":30.33,"q75":31.34,"cnt":10}, typeContainer: {"q25":15.29,"q50":15.88,"q75":17.88,"cnt":10}, focus: {"q25":123.84,"q50":125.26,"q75":128.57,"cnt":10}, inserterOpen: {"q25":40.03,"q50":40.98,"q75":41.49,"cnt":10}, inserterSearch: {"q25":15.34,"q50":16.4,"q75":17.47,"cnt":10}, inserterHover: {"q25":6.42,"q50":6.9,"q75":7.39,"cnt":20}, loadPatterns: {"q25":1556.22,"q50":1585.17,"q75":1663.37,"cnt":10}, listViewOpen: {"q25":224.78,"q50":229.7,"q75":236.99,"cnt":10} |
lucadobrescu
changed the base branch from
development
to
fix/2929-dynamic-content-globals
August 3, 2026 09:13
Main::autoload_classes() instantiated every entry of its class list without checking that the class is loadable. On a package with a stale Composer classmap the Atomic Wind entry resolved to nothing and `new $classname()` threw an uncaught Error on `init`, taking down every request (#2954). Skip entries that are not loadable so a packaging or third-party filter problem degrades to a missing feature instead of a site-wide fatal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tale Composer exposes `inc/` through a generated classmap in `vendor/`, so a map that does not match the files on disk — an interrupted plugin update, an OPcache entry compiled from the previous version — makes a class that is present unloadable. Register a fallback loader for `ThemeIsle\GutenbergBlocks\*` that resolves a class from its file name. It is appended to the SPL stack, so Composer still answers first and the fallback only runs when Composer has no answer. The class then loads and its feature keeps working, instead of being skipped by the guard in Main::autoload_classes(). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lucadobrescu
force-pushed
the
fix/2954-autoload-missing-class
branch
from
August 3, 2026 09:19
286494b to
fb75ec3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2954.
Summary
Otter initializes a fixed list of classes on
initand callsnew $classname()on each one. A site on 3.2.0 hitClass "...Atomic_Wind_Blocks" not foundatinc/class-main.php:97, so every frontend request ended in a fatal error.The released packages are not the cause. Every wp.org zip since 3.1.6 — the first release with this class — ships
inc/plugins/class-atomic-wind-blocks.phpand its entry in both generated maps,vendor/composer/autoload_classmap.phpandvendor/composer/autoload_static.php. The class reaches runtime only through those generated maps, so the class becomes unloadable when the map on the site stops matching the files on disk: an interrupted or partially applied plugin update, or an OPcache entry compiled from the previous version.This PR fixes the loading first, and keeps a guard for the case where the file is truly absent.
Fallback loader (
inc/class-autoloader.php) — resolvesThemeIsle\GutenbergBlocks\*from the file name, following the plugin'sclass-kebab-case.phpconvention.otter-blocks.phpappends it to the SPL stack after Composer, so Composer still answers first and the fallback runs only when Composer has no answer. A stale map costs nothing: the class loads and its feature keeps working.Main::autoload_classes()— checksclass_exists()before instantiating, and skips non-string entries. This covers what no loader can fix: a file that is missing from disk, or a third-party filter that adds a class it never ships. The request then loses one feature instead of the whole site.Note
The fallback covers every class in the autoload list, including the 17 that
otter-proinjects through theotter_blocks_autoloaderfilter, which carried the same fatal risk. It does not cover 15 files ininc/whose names diverge from the convention (inc/Tracker.php,inc/server/interface-ai-backend.php,inc/integrations/providers/class-mailchimp.php, and similar). Those stay classmap-only. A unit test asserts the list stays inside the convention, so this cannot drift unnoticed.Class loading on init
flowchart LR A[init, priority 9] --> B[otter_blocks_autoloader<br/>filter: Pro + 3rd party] B --> C{Composer<br/>classmap hit?} C -- Yes --> F[Instantiate,<br/>call instance] C -- No --> D{New: file exists<br/>for the class name?}:::added D -- Yes --> F D -- No --> E[New: skip entry,<br/>continue list]:::added E --> G[Request renders] F --> G classDef added fill:#1a7f37,color:#fff,stroke:#116329,stroke-width:3pxTest instructions
Steps 1 and 2 create the state a stale map puts a site in.
Check out this branch and run
composer install. Openvendor/composer/autoload_classmap.phpand delete the line forAtomic_Wind_Blocks. Openvendor/composer/autoload_static.phpand delete the same line. Composer reads the static map first, so both files must lose the entry.Expect: neither file contains
Atomic_Wind_Blocks.Open
wp-config.php. SetWP_DEBUGtotrue, setWP_DEBUG_LOGtotrue, and setWP_DEBUG_DISPLAYtofalse. Deletewp-content/debug.logif the file exists.Expect: no log file is present.
Open the site home page in a browser.
Expect: the page renders with HTTP 200, and
debug.logholds noClass "...Atomic_Wind_Blocks" not foundentry. Ondevelopmentthe same request returns HTTP 500 and writes that fatal atinc/class-main.php:97.Go to
WP Admin → Otter → Dashboard → Modulesand turn on Enable Atomic Wind Blocks. Then run:Expect:
true. The module loads through the fallback loader while the map stays stale.Run
composer dump-autoloadto rebuild the map. Reload the home page.Expect: the page renders with HTTP 200.
Checklist before the final review