Skip to content

Commit 6d02565

Browse files
bug #36914 Parse and render anonymous classes correctly on php 8 (derrabus)
This PR was merged into the 4.4 branch. Discussion ---------- Parse and render anonymous classes correctly on php 8 | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | #36872 | License | MIT | Doc PR | N/A The format of the value that `get_class()` returns for anonymous classes has changed in php 8. This PR attempts to detect both formats, with the help of the PHP80 polyfill where possible. Commits ------- 9d702fd Parse and render anonymous classes correctly on php 8
2 parents 7224c1d + 9d702fd commit 6d02565

24 files changed

+135
-56
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ matrix:
3131
- php: nightly
3232
services: [memcached]
3333
fast_finish: true
34-
allow_failures:
35-
- php: nightly
3634

3735
cache:
3836
directories:

src/Symfony/Component/Console/Application.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -863,17 +863,16 @@ private function doActuallyRenderThrowable(\Throwable $e, OutputInterface $outpu
863863
do {
864864
$message = trim($e->getMessage());
865865
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
866-
$class = \get_class($e);
867-
$class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
866+
$class = get_debug_type($e);
868867
$title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
869868
$len = Helper::strlen($title);
870869
} else {
871870
$len = 0;
872871
}
873872

874-
if (false !== strpos($message, "class@anonymous\0")) {
875-
$message = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
876-
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
873+
if (false !== strpos($message, "@anonymous\0")) {
874+
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
875+
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
877876
}, $message);
878877
}
879878

src/Symfony/Component/Console/Tests/ApplicationTest.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,8 @@ public function testRenderAnonymousException()
897897
$application = new Application();
898898
$application->setAutoExit(false);
899899
$application->register('foo')->setCode(function () {
900-
throw new class('') extends \InvalidArgumentException { };
900+
throw new class('') extends \InvalidArgumentException {
901+
};
901902
});
902903
$tester = new ApplicationTester($application);
903904

@@ -907,20 +908,22 @@ public function testRenderAnonymousException()
907908
$application = new Application();
908909
$application->setAutoExit(false);
909910
$application->register('foo')->setCode(function () {
910-
throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() { })));
911+
throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() {
912+
})));
911913
});
912914
$tester = new ApplicationTester($application);
913915

914916
$tester->run(['command' => 'foo'], ['decorated' => false]);
915-
$this->assertStringContainsString('Dummy type "@anonymous" is invalid.', $tester->getDisplay(true));
917+
$this->assertStringContainsString('Dummy type "class@anonymous" is invalid.', $tester->getDisplay(true));
916918
}
917919

918920
public function testRenderExceptionStackTraceContainsRootException()
919921
{
920922
$application = new Application();
921923
$application->setAutoExit(false);
922924
$application->register('foo')->setCode(function () {
923-
throw new class('') extends \InvalidArgumentException { };
925+
throw new class('') extends \InvalidArgumentException {
926+
};
924927
});
925928
$tester = new ApplicationTester($application);
926929

@@ -930,12 +933,13 @@ public function testRenderExceptionStackTraceContainsRootException()
930933
$application = new Application();
931934
$application->setAutoExit(false);
932935
$application->register('foo')->setCode(function () {
933-
throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() { })));
936+
throw new \InvalidArgumentException(sprintf('Dummy type "%s" is invalid.', \get_class(new class() {
937+
})));
934938
});
935939
$tester = new ApplicationTester($application);
936940

937941
$tester->run(['command' => 'foo'], ['decorated' => false]);
938-
$this->assertStringContainsString('Dummy type "@anonymous" is invalid.', $tester->getDisplay(true));
942+
$this->assertStringContainsString('Dummy type "class@anonymous" is invalid.', $tester->getDisplay(true));
939943
}
940944

941945
public function testRun()

src/Symfony/Component/Console/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"php": ">=7.1.3",
2020
"symfony/polyfill-mbstring": "~1.0",
2121
"symfony/polyfill-php73": "^1.8",
22+
"symfony/polyfill-php80": "^1.15",
2223
"symfony/service-contracts": "^1.1|^2"
2324
},
2425
"require-dev": {

src/Symfony/Component/Debug/ErrorHandler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ public function handleError($type, $message, $file, $line)
415415
$context = $e;
416416
}
417417

418-
if (false !== strpos($message, "class@anonymous\0")) {
418+
if (false !== strpos($message, "@anonymous\0")) {
419419
$logMessage = $this->levels[$type].': '.(new FlattenException())->setMessage($message)->getMessage();
420420
} else {
421421
$logMessage = $this->levels[$type].': '.$message;
@@ -540,7 +540,7 @@ public function handleException($exception, array $error = null)
540540
$handlerException = null;
541541

542542
if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
543-
if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) {
543+
if (false !== strpos($message = $exception->getMessage(), "@anonymous\0")) {
544544
$message = (new FlattenException())->setMessage($message)->getMessage();
545545
}
546546
if ($exception instanceof FatalErrorException) {

src/Symfony/Component/Debug/Exception/FatalThrowableError.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class FatalThrowableError extends FatalErrorException
2626

2727
public function __construct(\Throwable $e)
2828
{
29-
$this->originalClassName = \get_class($e);
29+
$this->originalClassName = get_debug_type($e);
3030

3131
if ($e instanceof \ParseError) {
3232
$severity = E_PARSE;

src/Symfony/Component/Debug/Exception/FlattenException.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public static function createFromThrowable(\Throwable $exception, int $statusCod
6767
$e->setStatusCode($statusCode);
6868
$e->setHeaders($headers);
6969
$e->setTraceFromThrowable($exception);
70-
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
70+
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : get_debug_type($exception));
7171
$e->setFile($exception->getFile());
7272
$e->setLine($exception->getLine());
7373

@@ -134,7 +134,7 @@ public function getClass()
134134
*/
135135
public function setClass($class)
136136
{
137-
$this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
137+
$this->class = false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
138138

139139
return $this;
140140
}
@@ -179,9 +179,9 @@ public function getMessage()
179179
*/
180180
public function setMessage($message)
181181
{
182-
if (false !== strpos($message, "class@anonymous\0")) {
183-
$message = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
184-
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
182+
if (false !== strpos($message, "@anonymous\0")) {
183+
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
184+
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
185185
}, $message);
186186
}
187187

src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,26 @@ public function testHandleUserError()
325325
}
326326
}
327327

328+
public function testHandleErrorWithAnonymousClass()
329+
{
330+
$handler = ErrorHandler::register();
331+
$handler->throwAt(E_USER_WARNING, true);
332+
try {
333+
$handler->handleError(E_USER_WARNING, 'foo '.\get_class(new class() extends \stdClass {
334+
}).' bar', 'foo.php', 12);
335+
$this->fail('Exception expected.');
336+
} catch (\ErrorException $e) {
337+
} finally {
338+
restore_error_handler();
339+
restore_exception_handler();
340+
}
341+
342+
$this->assertSame('User Warning: foo stdClass@anonymous bar', $e->getMessage());
343+
$this->assertSame(E_USER_WARNING, $e->getSeverity());
344+
$this->assertSame('foo.php', $e->getFile());
345+
$this->assertSame(12, $e->getLine());
346+
}
347+
328348
public function testHandleDeprecation()
329349
{
330350
$logArgCheck = function ($level, $message, $context) {

src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,11 @@ public function testAnonymousClass()
355355

356356
$this->assertSame('RuntimeException@anonymous', $flattened->getClass());
357357

358+
$flattened->setClass(\get_class(new class('Oops') extends NotFoundHttpException {
359+
}));
360+
361+
$this->assertSame('Symfony\Component\HttpKernel\Exception\NotFoundHttpException@anonymous', $flattened->getClass());
362+
358363
$flattened = FlattenException::create(new \Exception(sprintf('Class "%s" blah.', \get_class(new class() extends \RuntimeException {
359364
}))));
360365

src/Symfony/Component/Debug/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
],
1818
"require": {
1919
"php": ">=7.1.3",
20-
"psr/log": "~1.0"
20+
"psr/log": "~1.0",
21+
"symfony/polyfill-php80": "^1.15"
2122
},
2223
"conflict": {
2324
"symfony/http-kernel": "<3.4"

0 commit comments

Comments
 (0)