Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 223e847

Browse filesBrowse files
committed
[PhpunitBridge] Fix deprecation type detection
When using several vendor directories
1 parent 77418e7 commit 223e847
Copy full SHA for 223e847

File tree

6 files changed

+266
-57
lines changed
Filter options

6 files changed

+266
-57
lines changed

‎src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php
+6-3Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,10 @@ private static function getVendors()
264264
if (file_exists($v.'/composer/installed.json')) {
265265
self::$vendors[] = $v;
266266
$loader = require $v.'/autoload.php';
267-
$paths = self::getSourcePathsFromPrefixes(array_merge($loader->getPrefixes(), $loader->getPrefixesPsr4()));
267+
self::addSourcePathsFromPrefixes(
268+
array_merge($loader->getPrefixes(), $loader->getPrefixesPsr4()),
269+
$paths
270+
);
268271
}
269272
}
270273
}
@@ -280,12 +283,12 @@ private static function getVendors()
280283
return self::$vendors;
281284
}
282285

283-
private static function getSourcePathsFromPrefixes(array $prefixesByNamespace)
286+
private static function addSourcePathsFromPrefixes(array $prefixesByNamespace, array &$paths)
284287
{
285288
foreach ($prefixesByNamespace as $prefixes) {
286289
foreach ($prefixes as $prefix) {
287290
if (false !== realpath($prefix)) {
288-
yield realpath($prefix);
291+
$paths[] = realpath($prefix);
289292
}
290293
}
291294
}

‎src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php
+73-54Lines changed: 73 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,48 @@
1111

1212
namespace Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler;
1313

14+
use App\Services\AppService;
1415
use PHPUnit\Framework\TestCase;
1516
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
1617
use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation;
1718
use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5;
1819
use Symfony\Bridge\PhpUnit\SetUpTearDownTrait;
20+
use Symfony\Bridge\PhpUnit\Tests\TestUtils\FakeVendor;
21+
use Symfony\Component\Filesystem\Filesystem;
1922

2023
class DeprecationTest extends TestCase
2124
{
2225
use SetUpTearDownTrait;
2326

24-
private static $vendorDir;
25-
private static $prefixDirsPsr4;
27+
/**
28+
* @var FakeVendor
29+
*/
30+
private static $fakeVendor;
2631

2732
private static function getVendorDir()
2833
{
29-
if (null !== self::$vendorDir) {
30-
return self::$vendorDir;
34+
if (null !== self::$fakeVendor) {
35+
return self::$fakeVendor->getRootDir();
3136
}
3237

33-
foreach (get_declared_classes() as $class) {
34-
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
35-
$r = new \ReflectionClass($class);
36-
$vendorDir = \dirname(\dirname($r->getFileName()));
37-
if (file_exists($vendorDir.'/composer/installed.json') && @mkdir($vendorDir.'/myfakevendor/myfakepackage1', 0777, true)) {
38-
break;
39-
}
40-
}
41-
}
38+
self::$fakeVendor = FakeVendor::create()
39+
->addPackage(null, 'myfakevendor', 'myfakepackage1', 'Fake\\Package1\\')
40+
->addPackage(null, 'myfakevendor', 'myfakepackage2', 'Fake\\Package2\\')
41+
->generate();
42+
$rootDir = self::$fakeVendor->getRootDir();
4243

43-
self::$vendorDir = $vendorDir;
44-
mkdir($vendorDir.'/myfakevendor/myfakepackage2');
45-
touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php');
46-
touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php');
47-
touch($vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php');
44+
$fs = new Filesystem();
45+
$fs->mkdir([
46+
$rootDir.'/myfakevendor/myfakepackage1',
47+
$rootDir.'/myfakevendor/myfakepackage2',
48+
]);
49+
$fs->touch([
50+
$rootDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php',
51+
$rootDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php',
52+
$rootDir.'/myfakevendor/myfakepackage2/MyFakeFile.php',
53+
]);
4854

49-
return self::$vendorDir;
55+
return $rootDir;
5056
}
5157

5258
public function testItCanDetermineTheClassWhereTheDeprecationHappened()
@@ -231,6 +237,9 @@ public function providerGetTypeUsesRightTrace()
231237
*/
232238
public function testGetTypeUsesRightTrace(string $expectedType, string $message, array $trace)
233239
{
240+
$this->resetStaticVendors();
241+
require_once self::getVendorDir().'/autoload.php';
242+
234243
$deprecation = new Deprecation(
235244
$message,
236245
$trace,
@@ -239,54 +248,64 @@ public function testGetTypeUsesRightTrace(string $expectedType, string $message,
239248
$this->assertSame($expectedType, $deprecation->getType());
240249
}
241250

242-
/**
243-
* This method is here to simulate the extra level from the piece of code
244-
* triggering an error to the error handler.
245-
*/
246-
public function debugBacktrace()
251+
private function resetStaticVendors()
247252
{
248-
return debug_backtrace();
253+
$reflectionClass = new \ReflectionClass(Deprecation::class);
254+
$vendorsProp = $reflectionClass->getProperty('vendors');
255+
$vendorsProp->setAccessible(true);
256+
$vendorsProp->setValue(null);
249257
}
250258

251-
private static function removeDir($dir)
259+
public function testWithMultipleVendorDirs()
252260
{
253-
$files = glob($dir.'/*');
254-
foreach ($files as $file) {
255-
if (is_file($file)) {
256-
unlink($file);
257-
} else {
258-
self::removeDir($file);
261+
$this->resetStaticVendors();
262+
263+
$changed = null;
264+
try {
265+
$sut = new DeprecationErrorHandler();
266+
$changed = set_error_handler([$sut, 'handleError']);
267+
268+
$fakeVendor1 = FakeVendor::create()
269+
->addPsr4Dir(__DIR__.'/../Fixtures/fake_app', 'App\\Services\\')
270+
->addPackage(__DIR__.'/fake_vendor/acme/lib', 'acme', 'lib', 'acme\\lib\\')
271+
->generate();
272+
273+
require $fakeVendor1->getRootDir().'/autoload.php';
274+
275+
$fakeVendor2 = FakeVendor::create()
276+
->addPackage(__DIR__.'/fake_vendor/foo/lib', 'foo', 'lib', 'foo\\lib\\')
277+
->generate();
278+
279+
require $fakeVendor2->getRootDir().'/autoload.php';
280+
281+
(new AppService())->directDeprecations();
282+
283+
ob_start();
284+
$sut->shutdown();
285+
$deprecations = ob_get_clean();
286+
$this->assertStringContainsString('Remaining direct deprecation notices (2)', $deprecations);
287+
} finally {
288+
$fakeVendor1->clear();
289+
$fakeVendor2->clear();
290+
if ($changed) {
291+
restore_error_handler();
259292
}
260293
}
261-
rmdir($dir);
262294
}
263295

264-
private static function doSetupBeforeClass()
296+
/**
297+
* This method is here to simulate the extra level from the piece of code
298+
* triggering an error to the error handler.
299+
*/
300+
public function debugBacktrace()
265301
{
266-
foreach (get_declared_classes() as $class) {
267-
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
268-
$r = new \ReflectionClass($class);
269-
$v = \dirname(\dirname($r->getFileName()));
270-
if (file_exists($v.'/composer/installed.json')) {
271-
$loader = require $v.'/autoload.php';
272-
$reflection = new \ReflectionClass($loader);
273-
$prop = $reflection->getProperty('prefixDirsPsr4');
274-
$prop->setAccessible(true);
275-
$currentValue = $prop->getValue($loader);
276-
self::$prefixDirsPsr4[] = [$prop, $loader, $currentValue];
277-
$currentValue['Symfony\\Bridge\\PhpUnit\\'] = [realpath(__DIR__.'/../..')];
278-
$prop->setValue($loader, $currentValue);
279-
}
280-
}
281-
}
302+
return debug_backtrace();
282303
}
283304

284305
private static function doTearDownAfterClass()
285306
{
286-
foreach (self::$prefixDirsPsr4 as [$prop, $loader, $prefixDirsPsr4]) {
287-
$prop->setValue($loader, $prefixDirsPsr4);
307+
if (self::$fakeVendor) {
308+
self::$fakeVendor->clear();
288309
}
289-
290-
self::removeDir(self::getVendorDir().'/myfakevendor');
291310
}
292311
}
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace foo\lib;
4+
5+
class SomeOtherService
6+
{
7+
public function deprecatedApi()
8+
{
9+
@trigger_error(
10+
__FUNCTION__.' from foo is deprecated! You should stop relying on it!',
11+
E_USER_DEPRECATED
12+
);
13+
}
14+
}
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use acme\lib\SomeService;
6+
use foo\lib\SomeOtherService;
7+
8+
final class AppService
9+
{
10+
public function directDeprecations()
11+
{
12+
$service1 = new SomeService();
13+
$service1->deprecatedApi();
14+
15+
$service2 = new SomeOtherService();
16+
$service2->deprecatedApi();
17+
}
18+
}
19+
+151Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\PhpUnit\Tests\TestUtils;
4+
5+
use Symfony\Component\Filesystem\Filesystem;
6+
7+
/**
8+
* Allow to create fake composer vendor directories for tests.
9+
*
10+
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
11+
*/
12+
final class FakeVendor
13+
{
14+
/**
15+
* Root dir path where packages are stored.
16+
*
17+
* @var string
18+
*/
19+
private $rootDir;
20+
21+
/**
22+
* @var Filesystem
23+
*/
24+
private $fs;
25+
26+
/**
27+
* @var array
28+
*/
29+
private $packagesData = [];
30+
31+
/**
32+
* @var array
33+
*/
34+
private $psr4Prefixes = [];
35+
36+
private function __construct()
37+
{
38+
$this->fs = new Filesystem();
39+
}
40+
41+
public static function create()
42+
{
43+
return new self();
44+
}
45+
46+
public function addPackage($packagePath, $vendorName, $packageName, $namespace)
47+
{
48+
$this->packagesData[] = \func_get_args();
49+
50+
return $this;
51+
}
52+
53+
public function addPsr4Dir($path, $namespace)
54+
{
55+
$this->psr4Prefixes[$namespace] = [$path.'/'];
56+
57+
return $this;
58+
}
59+
60+
public function generate()
61+
{
62+
$uniqid = uniqid(time());
63+
$this->rootDir = sys_get_temp_dir().'/fake_vendor_'.$uniqid;
64+
65+
foreach ($this->packagesData as list($packagePath, $vendorName, $packageName, $namespace)) {
66+
$copyTarget = $this->rootDir.'/'.$vendorName.'/'.$packageName;
67+
if ($packagePath) {
68+
$this->fs->mirror($packagePath, $copyTarget);
69+
}
70+
71+
$this->psr4Prefixes[$namespace] = [$copyTarget.'/'];
72+
}
73+
74+
$this->fs->dumpFile($this->rootDir.'/composer/installed.json', json_encode([
75+
'just here' => 'for the detection',
76+
]));
77+
78+
$psr4PrefixesExported = var_export($this->psr4Prefixes, true);
79+
$content = <<<PHP
80+
<?php
81+
82+
class ComposerLoaderFake$uniqid
83+
{
84+
public function getPrefixes()
85+
{
86+
return [];
87+
}
88+
89+
public function getPrefixesPsr4()
90+
{
91+
return $psr4PrefixesExported;
92+
}
93+
94+
public function loadClass(\$className)
95+
{
96+
foreach (\$this->getPrefixesPsr4() as \$prefix => \$baseDirs) {
97+
if (strpos(\$className, \$prefix) !== 0) {
98+
continue;
99+
}
100+
101+
foreach (\$baseDirs as \$baseDir) {
102+
\$file = str_replace([\$prefix, '\\\'], [\$baseDir, '/'], \$className.'.php');
103+
if (file_exists(\$file)) {
104+
require \$file;
105+
}
106+
}
107+
}
108+
}
109+
}
110+
111+
class ComposerAutoloaderInitFake$uniqid
112+
{
113+
private static \$loader;
114+
115+
public static function getLoader()
116+
{
117+
if (null === self::\$loader) {
118+
self::\$loader = new ComposerLoaderFake$uniqid();
119+
120+
spl_autoload_register([self::\$loader, 'loadClass']);
121+
}
122+
123+
return self::\$loader;
124+
}
125+
}
126+
PHP;
127+
128+
$this->fs->dumpFile($this->rootDir.'/composer/autoload_real.php', $content);
129+
$content = <<<PHP
130+
<?php
131+
require_once __DIR__.'/composer/autoload_real.php';
132+
133+
return ComposerAutoloaderInitFake$uniqid::getloader();
134+
PHP;
135+
$this->fs->dumpFile($this->rootDir.'/autoload.php', $content);
136+
137+
return $this;
138+
}
139+
140+
public function clear()
141+
{
142+
$this->fs->remove($this->rootDir);
143+
144+
return $this;
145+
}
146+
147+
public function getRootDir()
148+
{
149+
return $this->rootDir;
150+
}
151+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.