diff --git a/src/Symfony/Bridge/PhpUnit/.gitignore b/src/Symfony/Bridge/PhpUnit/.gitignore index c49a5d8df5..9d8c4aadaf 100644 --- a/src/Symfony/Bridge/PhpUnit/.gitignore +++ b/src/Symfony/Bridge/PhpUnit/.gitignore @@ -1,3 +1,4 @@ vendor/ composer.lock phpunit.xml +Tests/DeprecationErrorHandler/fake_vendor/symfony/error-handler/DebugClassLoader.php diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index 911d37495e..9eded1a14e 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -13,6 +13,8 @@ namespace Symfony\Bridge\PhpUnit\DeprecationErrorHandler; use PHPUnit\Util\Test; use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor; +use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; +use Symfony\Component\ErrorHandler\DebugClassLoader; /** * @internal @@ -53,6 +55,18 @@ class Deprecation public function __construct($message, array $trace, $file) { $this->trace = $trace; + + if ('trigger_error' === ($trace[1]['function'] ?? null) + && (DebugClassLoader::class === ($class = $trace[2]['class'] ?? null) || LegacyDebugClassLoader::class === $class) + && 'checkClass' === ($trace[2]['function'] ?? null) + && null !== ($extraFile = $trace[2]['args'][1] ?? null) + && '' !== $extraFile + && false !== $extraFile = realpath($extraFile) + ) { + $this->getOriginalFilesStack(); + array_splice($this->originalFilesStack, 2, 1, $extraFile); + } + $this->message = $message; $i = \count($trace); while (1 < $i && $this->lineShouldBeSkipped($trace[--$i])) { diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/debug_class_loader_autoload.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/debug_class_loader_autoload.phpt new file mode 100644 index 0000000000..781027e84f --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/debug_class_loader_autoload.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test that a deprecation from the DebugClassLoader on a vendor class autoload triggered by an app class is considered indirect. +--FILE-- + +--EXPECTF-- +Remaining indirect deprecation notices (1) + + 1x: The "acme\lib\ExtendsDeprecatedClassFromOtherVendor" class extends "fcy\lib\DeprecatedClass" that is deprecated. + 1x in BarService::__construct from App\Services diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/BarService.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/BarService.php new file mode 100644 index 0000000000..868de5bd44 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/BarService.php @@ -0,0 +1,13 @@ + [__DIR__.'/../../fake_app/'], 'acme\\lib\\' => [__DIR__.'/../acme/lib/'], + 'fcy\\lib\\' => [__DIR__.'/../fcy/lib/'], ]; } public function loadClass($className) + { + if ($file = $this->findFile($className)) { + require $file; + } + } + + public function findFile($class) { foreach ($this->getPrefixesPsr4() as $prefix => $baseDirs) { - if (strpos($className, $prefix) !== 0) { + if (strpos($class, $prefix) !== 0) { continue; } foreach ($baseDirs as $baseDir) { - $file = str_replace([$prefix, '\\'], [$baseDir, '/'], $className.'.php'); + $file = str_replace([$prefix, '\\'], [$baseDir, '/'], $class.'.php'); if (file_exists($file)) { - require $file; + return $file; } } } + + return false; } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/fcy/lib/DeprecatedClass.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/fcy/lib/DeprecatedClass.php new file mode 100644 index 0000000000..f6672cea20 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/fcy/lib/DeprecatedClass.php @@ -0,0 +1,10 @@ +=5.5.9" }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0" + }, "suggest": { "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" },