Skip to content

Commit fe7c433

Browse files
Merge branch '4.4' into 5.2
* 4.4: [SecurityBundle] role_names variable instead of roles [PhpUnitBridge] fix reporting deprecations when they come from DebugClassLoader [ErrorHandler] fix parsing return types in DebugClassLoader [ErrorHandler] fix handling messages with null bytes from anonymous classes Restore priority for eventSubscribers
2 parents 587f2b6 + d0eb698 commit fe7c433

File tree

8 files changed

+99
-85
lines changed

8 files changed

+99
-85
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
vendor/
22
composer.lock
33
phpunit.xml
4-
Tests/DeprecationErrorHandler/fake_vendor/symfony/error-handler/DebugClassLoader.php

DeprecationErrorHandler.php

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -125,43 +125,47 @@ public function handleError($type, $msg, $file, $line, $context = [])
125125
return \call_user_func(self::getPhpUnitErrorHandler(), $type, $msg, $file, $line, $context);
126126
}
127127

128-
$deprecation = new Deprecation($msg, debug_backtrace(), $file);
128+
$trace = debug_backtrace();
129+
130+
if (isset($trace[1]['function'], $trace[1]['args'][0]) && ('trigger_error' === $trace[1]['function'] || 'user_error' === $trace[1]['function'])) {
131+
$msg = $trace[1]['args'][0];
132+
}
133+
134+
$deprecation = new Deprecation($msg, $trace, $file);
129135
if ($deprecation->isMuted()) {
130136
return null;
131137
}
132138
if ($this->getConfiguration()->isBaselineDeprecation($deprecation)) {
133139
return null;
134140
}
135-
$group = 'other';
136141

137-
if ($deprecation->originatesFromAnObject()) {
138-
$class = $deprecation->originatingClass();
139-
$method = $deprecation->originatingMethod();
140-
$msg = $deprecation->getMessage();
142+
$msg = $deprecation->getMessage();
141143

142-
if (error_reporting() & $type) {
143-
$group = 'unsilenced';
144-
} elseif ($deprecation->isLegacy()) {
145-
$group = 'legacy';
146-
} else {
147-
$group = [
148-
Deprecation::TYPE_SELF => 'self',
149-
Deprecation::TYPE_DIRECT => 'direct',
150-
Deprecation::TYPE_INDIRECT => 'indirect',
151-
Deprecation::TYPE_UNDETERMINED => 'other',
152-
][$deprecation->getType()];
153-
}
144+
if (error_reporting() & $type) {
145+
$group = 'unsilenced';
146+
} elseif ($deprecation->isLegacy()) {
147+
$group = 'legacy';
148+
} else {
149+
$group = [
150+
Deprecation::TYPE_SELF => 'self',
151+
Deprecation::TYPE_DIRECT => 'direct',
152+
Deprecation::TYPE_INDIRECT => 'indirect',
153+
Deprecation::TYPE_UNDETERMINED => 'other',
154+
][$deprecation->getType()];
155+
}
154156

155-
if ($this->getConfiguration()->shouldDisplayStackTrace($msg)) {
156-
echo "\n".ucfirst($group).' '.$deprecation->toString();
157+
if ($this->getConfiguration()->shouldDisplayStackTrace($msg)) {
158+
echo "\n".ucfirst($group).' '.$deprecation->toString();
157159

158-
exit(1);
159-
}
160-
if ('legacy' !== $group) {
161-
$this->deprecationGroups[$group]->addNoticeFromObject($msg, $class, $method);
162-
} else {
163-
$this->deprecationGroups[$group]->addNotice();
164-
}
160+
exit(1);
161+
}
162+
163+
if ('legacy' === $group) {
164+
$this->deprecationGroups[$group]->addNotice();
165+
} else if ($deprecation->originatesFromAnObject()) {
166+
$class = $deprecation->originatingClass();
167+
$method = $deprecation->originatingMethod();
168+
$this->deprecationGroups[$group]->addNoticeFromObject($msg, $class, $method);
165169
} else {
166170
$this->deprecationGroups[$group]->addNoticeFromProceduralCode($msg);
167171
}

DeprecationErrorHandler/Deprecation.php

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -60,25 +60,33 @@ public function __construct($message, array $trace, $file)
6060
}
6161

6262
$this->trace = $trace;
63-
64-
if ('trigger_error' === (isset($trace[1]['function']) ? $trace[1]['function'] : null)
65-
&& (DebugClassLoader::class === ($class = (isset($trace[2]['class']) ? $trace[2]['class'] : null)) || LegacyDebugClassLoader::class === $class)
66-
&& 'checkClass' === (isset($trace[2]['function']) ? $trace[2]['function'] : null)
67-
&& null !== ($extraFile = (isset($trace[2]['args'][1]) ? $trace[2]['args'][1] : null))
68-
&& '' !== $extraFile
69-
&& false !== $extraFile = realpath($extraFile)
70-
) {
71-
$this->getOriginalFilesStack();
72-
array_splice($this->originalFilesStack, 2, 1, $extraFile);
73-
}
74-
7563
$this->message = $message;
76-
$i = \count($this->trace);
77-
while (1 < $i && $this->lineShouldBeSkipped($this->trace[--$i])) {
64+
65+
$i = \count($trace);
66+
while (1 < $i && $this->lineShouldBeSkipped($trace[--$i])) {
7867
// No-op
7968
}
80-
$line = $this->trace[$i];
69+
70+
$line = $trace[$i];
8171
$this->triggeringFile = $file;
72+
73+
for ($j = 1; $j < $i; ++$j) {
74+
if (!isset($trace[$j]['function'], $trace[1 + $j]['class'], $trace[1 + $j]['args'][0])) {
75+
continue;
76+
}
77+
78+
if ('trigger_error' === $trace[$j]['function'] && !isset($trace[$j]['class'])) {
79+
if (\in_array($trace[1 + $j]['class'], [DebugClassLoader::class, LegacyDebugClassLoader::class], true)) {
80+
$class = $trace[1 + $j]['args'][0];
81+
$this->triggeringFile = isset($trace[1 + $j]['args'][1]) ? realpath($trace[1 + $j]['args'][1]) : (new \ReflectionClass($class))->getFileName();
82+
$this->getOriginalFilesStack();
83+
array_splice($this->originalFilesStack, 0, $j, [$this->triggeringFile]);
84+
}
85+
86+
break;
87+
}
88+
}
89+
8290
if (isset($line['object']) || isset($line['class'])) {
8391
set_error_handler(function () {});
8492
$parsedMsg = unserialize($this->message);
@@ -101,12 +109,19 @@ public function __construct($message, array $trace, $file)
101109
return;
102110
}
103111

104-
if (isset($line['class']) && 0 === strpos($line['class'], SymfonyTestsListenerFor::class)) {
112+
if (!isset($line['class'], $trace[$i - 2]['function']) || 0 !== strpos($line['class'], SymfonyTestsListenerFor::class)) {
113+
$this->originClass = isset($line['object']) ? \get_class($line['object']) : $line['class'];
114+
$this->originMethod = $line['function'];
115+
105116
return;
106117
}
107118

108-
$this->originClass = isset($line['object']) ? \get_class($line['object']) : $line['class'];
109-
$this->originMethod = $line['function'];
119+
if ('trigger_error' !== $trace[$i - 2]['function'] || isset($trace[$i - 2]['class'])) {
120+
$this->originClass = \get_class($line['args'][0]);
121+
$this->originMethod = $line['args'][0]->getName();
122+
123+
return;
124+
}
110125
}
111126
}
112127

@@ -140,7 +155,9 @@ public function originatingClass()
140155
throw new \LogicException('Check with originatesFromAnObject() before calling this method.');
141156
}
142157

143-
return $this->originClass;
158+
$class = $this->originClass;
159+
160+
return false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
144161
}
145162

146163
/**
@@ -168,8 +185,7 @@ public function getMessage()
168185
*/
169186
public function isLegacy()
170187
{
171-
$class = $this->originatingClass();
172-
if ((new \ReflectionClass($class))->isInternal()) {
188+
if (!$this->originClass || (new \ReflectionClass($this->originClass))->isInternal()) {
173189
return false;
174190
}
175191

@@ -178,8 +194,8 @@ public function isLegacy()
178194
return 0 === strpos($method, 'testLegacy')
179195
|| 0 === strpos($method, 'provideLegacy')
180196
|| 0 === strpos($method, 'getLegacy')
181-
|| strpos($class, '\Legacy')
182-
|| \in_array('legacy', Test::getGroups($class, $method), true);
197+
|| strpos($this->originClass, '\Legacy')
198+
|| \in_array('legacy', Test::getGroups($this->originClass, $method), true);
183199
}
184200

185201
/**
@@ -205,11 +221,10 @@ public function isMuted()
205221
*/
206222
public function getType()
207223
{
208-
$triggeringFilePathType = $this->getPathType($this->triggeringFile);
209-
if (self::PATH_TYPE_SELF === $triggeringFilePathType) {
224+
if (self::PATH_TYPE_SELF === $pathType = $this->getPathType($this->triggeringFile)) {
210225
return self::TYPE_SELF;
211226
}
212-
if (self::PATH_TYPE_UNDETERMINED === $triggeringFilePathType) {
227+
if (self::PATH_TYPE_UNDETERMINED === $pathType) {
213228
return self::TYPE_UNDETERMINED;
214229
}
215230
$erroringFile = $erroringPackage = null;
@@ -218,10 +233,10 @@ public function getType()
218233
if ('-' === $file || 'Standard input code' === $file || !realpath($file)) {
219234
continue;
220235
}
221-
if (self::PATH_TYPE_SELF === $this->getPathType($file)) {
236+
if (self::PATH_TYPE_SELF === $pathType = $this->getPathType($file)) {
222237
return self::TYPE_DIRECT;
223238
}
224-
if (self::PATH_TYPE_UNDETERMINED === $this->getPathType($file)) {
239+
if (self::PATH_TYPE_UNDETERMINED === $pathType) {
225240
return self::TYPE_UNDETERMINED;
226241
}
227242
if (null !== $erroringFile && null !== $erroringPackage) {
@@ -243,7 +258,7 @@ private function getOriginalFilesStack()
243258
if (null === $this->originalFilesStack) {
244259
$this->originalFilesStack = [];
245260
foreach ($this->trace as $frame) {
246-
if (!isset($frame['file']) || \in_array($frame['function'], ['require', 'require_once', 'include', 'include_once'], true)) {
261+
if (!isset($frame['file'], $frame['function']) || (!isset($frame['class']) && \in_array($frame['function'], ['require', 'require_once', 'include', 'include_once'], true))) {
247262
continue;
248263
}
249264

@@ -269,13 +284,10 @@ private function getPackage($path)
269284
$relativePath = substr($path, \strlen($vendorRoot) + 1);
270285
$vendor = strstr($relativePath, \DIRECTORY_SEPARATOR, true);
271286
if (false === $vendor) {
272-
throw new \RuntimeException(sprintf('Could not find directory separator "%s" in path "%s".', \DIRECTORY_SEPARATOR, $relativePath));
287+
return 'symfony';
273288
}
274289

275-
return rtrim($vendor.'/'.strstr(substr(
276-
$relativePath,
277-
\strlen($vendor) + 1
278-
), \DIRECTORY_SEPARATOR, true), '/');
290+
return rtrim($vendor.'/'.strstr(substr($relativePath, \strlen($vendor) + 1), \DIRECTORY_SEPARATOR, true), '/');
279291
}
280292
}
281293

@@ -289,6 +301,13 @@ private static function getVendors()
289301
{
290302
if (null === self::$vendors) {
291303
self::$vendors = $paths = [];
304+
self::$vendors[] = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Legacy';
305+
if (class_exists(DebugClassLoader::class, false)) {
306+
self::$vendors[] = \dirname((new \ReflectionClass(DebugClassLoader::class))->getFileName());
307+
}
308+
if (class_exists(LegacyDebugClassLoader::class, false)) {
309+
self::$vendors[] = \dirname((new \ReflectionClass(LegacyDebugClassLoader::class))->getFileName());
310+
}
292311
foreach (get_declared_classes() as $class) {
293312
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
294313
$r = new \ReflectionClass($class);
@@ -364,10 +383,9 @@ public function toString()
364383
$reflection->setAccessible(true);
365384
$reflection->setValue($exception, $this->trace);
366385

367-
return 'deprecation triggered by '.$this->originatingClass().'::'.$this->originatingMethod().':'.
368-
"\n".$this->message.
369-
"\nStack trace:".
370-
"\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString()).
371-
"\n";
386+
return ($this->originatesFromAnObject() ? 'deprecation triggered by '.$this->originatingClass().'::'.$this->originatingMethod().":\n" : '')
387+
.$this->message."\n"
388+
."Stack trace:\n"
389+
.str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString())."\n";
372390
}
373391
}

Tests/DeprecationErrorHandler/DeprecationTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ public function testGetTypeDetectsSelf(string $expectedType, string $message, st
183183
public function providerGetTypeUsesRightTrace()
184184
{
185185
$vendorDir = self::getVendorDir();
186+
$fakeTrace = [
187+
['function' => 'trigger_error'],
188+
['class' => SymfonyTestsListenerTrait::class, 'function' => 'endTest'],
189+
['class' => SymfonyTestsListenerForV5::class, 'function' => 'endTest'],
190+
];
186191

187192
return [
188193
'no_file_in_stack' => [Deprecation::TYPE_DIRECT, '', [['function' => 'myfunc1'], ['function' => 'myfunc2']]],
@@ -205,7 +210,7 @@ public function providerGetTypeUsesRightTrace()
205210
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php',
206211
],
207212
]),
208-
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']],
213+
$fakeTrace,
209214
],
210215
'serialized_stack_files_from_various_packages' => [
211216
Deprecation::TYPE_INDIRECT,
@@ -218,7 +223,7 @@ public function providerGetTypeUsesRightTrace()
218223
$vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php',
219224
],
220225
]),
221-
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']],
226+
$fakeTrace,
222227
],
223228
];
224229
}

Tests/DeprecationErrorHandler/debug_class_loader_autoload.phpt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,6 @@ EOPHP
3030
);
3131
require __DIR__.'/fake_vendor/autoload.php';
3232

33-
// We need the real DebugClassLoader FQCN but in a vendor path.
34-
if (!file_exists($errorHandlerRootDir = __DIR__.'/../../../../Component/ErrorHandler')) {
35-
if (!file_exists($errorHandlerRootDir = __DIR__.'/../../vendor/symfony/error-handler')) {
36-
die('Could not find the ErrorHandler component root directory.');
37-
}
38-
}
39-
40-
file_put_contents($fakeDebugClassLoadPath = __DIR__.'/fake_vendor/symfony/error-handler/DebugClassLoader.php', file_get_contents($errorHandlerRootDir.'/DebugClassLoader.php'));
41-
require $fakeDebugClassLoadPath;
42-
4333
\Symfony\Component\ErrorHandler\DebugClassLoader::enable();
4434
new \App\Services\BarService();
4535

Tests/DeprecationErrorHandler/fake_vendor/symfony/error-handler/.gitkeep

Whitespace-only changes.

Tests/DeprecationErrorHandler/weak_vendors_on_vendor.phpt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,11 @@ Unsilenced deprecation notices (3)
2929
1x: unsilenced bar deprecation
3030
1x in FooTestCase::testNonLegacyBar
3131

32-
Remaining direct deprecation notices (1)
32+
Remaining direct deprecation notices (2)
33+
34+
1x: root deprecation
3335

3436
1x: silenced bar deprecation
3537
1x in FooTestCase::testNonLegacyBar
3638

3739
Legacy deprecation notices (2)
38-
39-
Other deprecation notices (1)
40-
41-
1x: root deprecation

bin/simple-phpunit.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*/
1111

1212
// Please update when phpunit needs to be reinstalled with fresh deps:
13-
// Cache-Id: 2020-01-31 10:00 UTC
13+
// Cache-Id: 2021-02-04 11:00 UTC
1414

1515
error_reporting(-1);
1616

0 commit comments

Comments
 (0)