Issue reporting deprecations when calls being made from 3rd party code
I am trying to collect deprecation notices while running the PHPUnit test suite in a Symfony application.
In general, I‘d like to ignoreIndirectDeprecations="true", since there are a lot of them between 3rd party packages I don‘t want to care about right now. But that currently also suppresses deprecations when my code is being called by framework code.
For example, there might be a Controller class that lives in my src and that triggers a deprecation notice. This controller, however, is not called directly by my test or other own source code, but during functional tests.
In the case of a controller, it‘s probably Symfony‘s HttpKernel invoking the controller method. In other cases (like Symfony Console commands), it may be other Symfony components calling my code.
From looking at \PHPUnit\Runner\ErrorHandler::trigger, it seems to me that since calls are not being made directly from my own (first party) code, this is always considered the indirect deprecation case which I‘d like to suppress.
I can try to work around this by adding the HttpKernel and other relevant parts from vendor as source files in the PHPUnit config. However, that does not exactly fit all cases since I don‘t want to see the cases where the Kernel is calling 3rd party code (which would be a direct deprecation if the Kernel is considered first party code).
[Edit: Moved second group of issues to #6435, since I think the cause and potential solutions are different.]
Additional information
PHPUnit 12.4.5, PHP 8.4.15
phpunit.xml.dist:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.0/phpunit.xsd"
bootstrap="tests/bootstrap.php"
>
<source ignoreSuppressionOfDeprecations="true" ignoreIndirectDeprecations="true">
<deprecationTrigger>
<function>trigger_deprecation</function>
</deprecationTrigger>
<include>
<directory>src</directory>
</include>
</source>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
test/bootstrap.php:
<?php
use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\ErrorHandler\ErrorHandler;
require_once __DIR__.'/../vendor/autoload.php';
DebugClassLoader::enable();
For reference, the symfony/phpunit-bridge classified deprecations in 3 categories (rather than 2):
self: deprecations triggered in first-party code (whatever the caller)direct: deprecations triggered in third-party code called from first-party codeindirect: deprecations triggered in third-party code called from third-party code
In practice, I don't think we need different handling between self and direct deprecations, so PHPUnit might not need to introduce 3 different kinds. But it should probably avoid classifying some self deprecations as indirect.
Background information on the current implementation is in #5689.
If we cannot easily change the current definition of indirect or self for BC reasons, we could also add own as new terminology for the class of calls we're discussing here.
| called code | |||
|---|---|---|---|
| 1st party | 3rd party | ||
| calling code | 1st party | self | direct |
| 3rd party | ~indirect~ own | indirect |
For reference, the symfony/phpunit-bridge classified deprecations in 3 categories (rather than 2)
PHPUnit supports 3 categories named self, direct, and indirect. This is inspired by symfony/phpunit-bridge. This is documented at https://docs.phpunit.de/en/12.5/error-handling.html#limiting-issues-to-your-code.
Back when I implemented this functionality, I understood the following desired semantics:
selfshould mean deprecations triggered by first-party code in first-party codedirectshould mean deprecations triggered by first-party code in third-party codeindirectshould mean deprecations triggered by third-party code
This understanding is based on what I learned from https://github.com/sebastianbergmann/phpunit/issues/5689#issuecomment-1923738085. I rephrased @stof's explanation in https://github.com/sebastianbergmann/phpunit/issues/5689#issuecomment-1999043469 which was then thumb-up'd by @nicolas-grekas.
self: deprecations triggered in first-party code (whatever the caller)
This is not what I understood from https://github.com/sebastianbergmann/phpunit/issues/5689#issuecomment-1923738085.
Do I understand this issue correctly as "PHPUnit interprets self wrong"? And "wrong" probably means "different from symfony/phpunit-bridge".
If so, then I am, of course, open to align PHPUnit's behaviour with that of symfony/phpunit-bridge. Now whether that means changing how PHPUnit interprets self or whether we adopt @mpdude's idea of introducing own, I cannot yet say.
Maybe there are some more situations we need to consider when autoloading comes into play:
#6165 and #6357 is about deprecation notices triggered from PHP userland, but only once and at a very early stage when a class/file is first loaded (runtime notices about e. g. implementing deprecated interfaces). In the stack trace, this is preceded by autoloader code. Further up, it may either be PHPUnit initializing, but can also be triggered during test runs.
We can classify the file where the deprecation is emitted as either first or third party, but determining the caller is more involved. We'd need to filter out the entire autoload stack. When we find test code above that, fine. Otherwise, is it PHPUnit or some other mechanism scanning for all classes in general, accidentally finding the file? Or, is it third party code, but that is loading the file because I directly tell it to?
What if my test code directly uses such a class after it has been loaded for other, generic reasons before? The error is not triggered again after the file has been loaded once.
#6432 is similar, but this time the error is triggered from PHP core while loading the file, so I am not sure if the file causing the problem is visible in the stack trace at that point.
#6435 has the autoloader trigger a deprecation notice about code it is loading, so for sure we cannot find the file in question in the stack trace.
The general pattern is that
- those three cases have the same classification problem of
selfvsdirectvsindirect - we get the error only on first use, possibly in another context than we'd see later on at runtime, so the caller is not clear
- we might not even know the called file.