-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
This is a follow-up of my comment in #6489 (comment) as we agreed with @sebastianbergmann to keep it out of scope of that previous PR.
There are some cases where a deprecation being triggered by a class does not actually mean this deprecation is defined by that class, or that the reason to trigger it is related to the PHP caller of that class. Such use cases are currently classified badly (often being classified as indirect while they should be direct, the opposite case is very rare). Such bad classification cause issues for projects that want to fail on deprecations that they can act on but not on indirect deprecations (made worse by #6484 as they cannot display deprecations that they don't fail on).
I identified 2 cases that would benefit from such extension points:
- the DebugClassLoader of
symfony/error-handlertriggers some deprecations related to class definitions themselves. This is done based on declarative phpdoc tags in the parent class, implemented interfaces and used traits. Such deprecations are semantically meant to be triggered from the file defining the class definition instead of from the class loader. For some of them, we consider them as being triggered from the file defining the parent class with the class definition as caller. This use case was reported in Dealing with deprecations from Symfony DebugClassLoader #6435 - config file loaders that are processing config files might trigger deprecations due to the content of the config being processed. As far as the classification is concerned, we should treat the config file as the caller of the loader (as if the config file that was imported was actually doing something like
$configLoader->process($myConfig)), which would allow classifying the deprecation as direct if the config file is part of the project source code (the case of config files in other formats than PHP would probably also related toincludeInCodeCoverageattribute for<directory>and<file>children of<source>#6517, so that such non-PHP file can be considered project files rather than vendor files)
The deprecation reporting feature of symfony/phpunit-bridge (targeting PHPUnit 9 and older) was implementing the logic for the first case. We never implemented logic for the second case (but we were still displaying indirect deprecations by default even when not failing on them, so bad classification for them was less impactful).
Hardcoding specific support for the DebugClassLoader of symfony/error-handler in PHPUnit itself is probably not an option (as being too specific). However, Symfony already has a PHPUnit extension (targeting PHPUnit 11+) in the symfony/phpunit-bridge so we could ship this logic in Symfony if we have extension points allowing to do that.
I see 2 possible options for such extension points:
- allowing to customize the logic determining the caller and/or the callee
- allowing to customize the categorization of the caller and callee directly (i.e. the
Codeenum case, as introduced in Classification of self/direct/indirect deprecation triggers is not aligned with Symfony's bridge for PHPUnit #6489).
In order to ensure that the classification is consistent, I would suggest going for the first option, so that PHPUnit remains in control of how files are classified (ensuring the PHPUnit config is respected for that classification).
Here are the requirements I see for this API:
- making it possible for multiple extensions to register for this extension point
- making it possible for an extension to abstain from processing a given deprecation (letting next extensions a chance to process it)
- (maybe) stopping the processing of next extensions once an extension has applied an override already (to make it simpler to reason about how this behaves)
- the extension must be able to replace the caller, the callee or both (could be implemented in a way always requiring to return both, if the input contains the caller and callee determined by the PHPunit ErrorHandler to use them when wanting to replace only 1)
- the extension must have access to the stack trace of the deprecation to be able to use it in its processing
- the stack trace should include arguments (i.e. not use the
DEBUG_BACKTRACE_IGNORE_ARGSflag when getting it, as done today), as the logic to determine the new caller and callee will generally depend on them.
The use cases I described are all related to the handling of E_USER_DEPRECATED, not to E_DEPRECATED which is triggered by the PHP runtime.