Do not let a bootstrap autoloader's exception abort the analysis - #6294
Open
SanderMuller wants to merge 1 commit into
Open
Do not let a bootstrap autoloader's exception abort the analysis#6294SanderMuller wants to merge 1 commit into
SanderMuller wants to merge 1 commit into
Conversation
Both source locators that ask the registered autoloaders for a class walk the whole spl queue, which is not the order PHP uses: at runtime the class loader that resolves the class first means the other autoloaders are never invoked for that name. An autoloader that throws when the file it found does not declare the class it was asked for therefore throws here for names it can never see at runtime, and the exception surfaces as an internal error that aborts the file. Two widely used ones do that - symfony/error-handler's DebugClassLoader, which throws a RuntimeException in four conditions, and Yii 2's Yii::autoload(), which throws an UnknownClassException when the file it included did not declare the class and YII_DEBUG is on. Swallowing it lets the remaining autoloaders and source locators take their turn, so the class is resolved the way it is at runtime, or reported as not found. The trapped loop in AutoloadSourceLocator has the same defect; it predates the recently reverted work and is fixed here too, since fixing only one of the two leaves any name no static locator resolves still aborting. Unlike the reverted attempts this changes neither the order of the locators nor the way autoloaders are run, and it declines nothing by name. e2e/debug-class-loader is the DebugClassLoader case with the real package: an internal error before, the unknown class reported after. e2e/bug-12972b, the project phpstan/phpstan#14976 is about, is re-enabled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SanderMuller
force-pushed
the
autoload-swallow-autoloader-exceptions
branch
from
August 27, 2026 21:30
b5a6d39 to
43f6dde
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.
After #6292 reverted the whole bootstrap-autoloader line, this is a fresh, much smaller attempt at one half
of what was reopened. It changes neither the order of the source locators nor the way autoloaders are run,
and it declines nothing by name - the three things that kept breaking other people's projects.
The problem. Both source locators that consult the registered autoloaders walk the entire spl queue.
That is not the order PHP uses: at runtime the class loader that resolves the class first means the other
autoloaders are never invoked for that name at all. An autoloader that throws when the file it found does
not declare the class it was asked for therefore throws here for names it can never see at runtime, and the
exception surfaces as
Internal error: ..., aborting the file.Two widely used autoloaders do exactly that:
symfony/error-handler'sDebugClassLoader-RuntimeExceptionin four conditions: case mismatchbetween the loaded and the declared name, a name containing
/, "the autoloader expected class X to bedefined in file Y", and case mismatch between the class and the real file name.
BaseYii::autoload()-UnknownClassExceptionwhen the file it included did not declare theclass and
YII_DEBUGis on. It also plain-includes, so it carries the Cannot redeclare function: AutoloadFunctionsSourceLocator includes PSR-4 function files untrapped phpstan#14988 shape too.The fix swallows the exception so the remaining autoloaders and source locators get their turn: the class
is then resolved the way it is at runtime, or reported as not found. Both call sites are fixed - the loop in
AutoloadSourceLocator::locateClassByName()that runs the autoloaders inside the file-read trap has the samedefect and predates the reverted work, and fixing only one leaves every name no static locator resolves
still aborting.
Measured against the guard corpus from #6293 (thanks for merging it - this is what makes the change
bounded rather than another guess):
bug-12972b(phpstan/phpstan#14976)debug-class-loader(new, realsymfony/error-handler)bug-14988bug-12972cbug-15102,bug-15102b,bug-15102cclass-alias-loader,robot-loadere2e/bug-12972bis re-enabled ine2e-tests.yml;e2e/bug-14988stays commented out.What this does not fix. phpstan/phpstan#14988 is untouched. Its real-world carrier is
squizlabs/php_codesniffer, whose autoloader falls back to Composer'sfindFile()and then plain-includesthe result, with a loaded-files cache that cannot know about files Composer's
filesautoload alreadyloaded. That is a fatal error, not an exception, so no
catchcan reach it.No
Closesfor phpstan/phpstan#14976 either, deliberately. The autoloader is still invoked out ofruntime order and its side effects still happen; only the internal error goes away. Whether that is enough
to close the issue is your call.
Objection worth answering up front: swallowing hides a signal. The strongest case is DebugClassLoader's
case-mismatch throw - and PHPStan reports that itself, via
class.nameCase, so the information is not lost.Performance: no new calls; a
try/catchcosts nothing on the path where nothing throws, andautoloadFunctions()is empty unless the project registers autoloaders frombootstrapFiles.Three unit tests, each verified failing before the change. Full suite, self-analysis and phpcs green.