bug #38597 [PhpUnitBridge] Fix qualification of deprecations triggered by the debug class loader (fancyweb)

This PR was merged into the 4.4 branch.

Discussion
----------

[PhpUnitBridge] Fix qualification of deprecations triggered by the debug class loader

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | https://github.com/symfony/symfony/issues/36036
| License       | MIT
| Doc PR        | -

I tested it successfully against the two examples stof gave in the issue. However, I don't see how to write a working real test for this.

The solution I found is to add the missing checked file in the original files stack so when the type of the deprecation is computed instead of having "self" -> "self", we have "vendor" -> "self".

Commits
-------

dff539434e [PhpUnitBridge] Fix qualification of deprecations triggered by the debug class loader
This commit is contained in:
Alexander M. Turek 2020-11-26 19:03:26 +01:00
commit 6224ffa5a3
9 changed files with 115 additions and 3 deletions

View File

@ -1,3 +1,4 @@
vendor/
composer.lock
phpunit.xml
Tests/DeprecationErrorHandler/fake_vendor/symfony/error-handler/DebugClassLoader.php

View File

@ -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])) {

View File

@ -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--
<?php
$k = 'SYMFONY_DEPRECATIONS_HELPER';
putenv($k.'='.$_SERVER[$k] = $_ENV[$k] = 'max[total]=0');
putenv('ANSICON');
putenv('ConEmuANSI');
putenv('TERM');
$vendor = __DIR__;
while (!file_exists($vendor.'/vendor')) {
$vendor = dirname($vendor);
}
define('PHPUNIT_COMPOSER_INSTALL', $vendor.'/vendor/autoload.php');
require PHPUNIT_COMPOSER_INSTALL;
require_once __DIR__.'/../../bootstrap.php';
eval(<<<'EOPHP'
namespace PHPUnit\Util;
class Test
{
public static function getGroups()
{
return array();
}
}
EOPHP
);
require __DIR__.'/fake_vendor/autoload.php';
// We need the real DebugClassLoader FQCN but in a vendor path.
if (!file_exists($errorHandlerRootDir = __DIR__.'/../../../../Component/ErrorHandler')) {
if (!file_exists($errorHandlerRootDir = __DIR__.'/../../vendor/symfony/error-handler')) {
die('Could not find the ErrorHandler component root directory.');
}
}
file_put_contents($fakeDebugClassLoadPath = __DIR__.'/fake_vendor/symfony/error-handler/DebugClassLoader.php', file_get_contents($errorHandlerRootDir.'/DebugClassLoader.php'));
require $fakeDebugClassLoadPath;
\Symfony\Component\ErrorHandler\DebugClassLoader::enable();
new \App\Services\BarService();
?>
--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

View File

@ -0,0 +1,13 @@
<?php
namespace App\Services;
use acme\lib\ExtendsDeprecatedClassFromOtherVendor;
final class BarService
{
public function __construct()
{
ExtendsDeprecatedClassFromOtherVendor::FOO;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace acme\lib;
use fcy\lib\DeprecatedClass;
final class ExtendsDeprecatedClassFromOtherVendor extends DeprecatedClass
{
public const FOO = 'bar';
}

View File

@ -12,23 +12,33 @@ class ComposerLoaderFake
return [
'App\\Services\\' => [__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;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace fcy\lib;
/**
* @deprecated
*/
class DeprecatedClass
{
}

View File

@ -20,6 +20,9 @@
"php": "THIS BRIDGE WHEN TESTING LOWEST SYMFONY VERSIONS.",
"php": ">=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"
},