Skip to content

Commit ce625dc

Browse files
committed
Parse and render anonymous classes correctly on php 8.
1 parent a14d8f9 commit ce625dc

23 files changed

+156
-51
lines changed

src/Symfony/Component/Console/Application.php

+8-5
Original file line numberDiff line numberDiff line change
@@ -863,17 +863,20 @@ 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 (str_contains($message, "@anonymous\0")) {
874+
$message = preg_replace_callback('/([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
875+
if ('class' === $m[1]) {
876+
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: 'class').'@anonymous' : $m[0];
877+
}
878+
879+
return $m[1].'@anonymous';
877880
}, $message);
878881
}
879882

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ public function testRenderAnonymousException()
912912
$tester = new ApplicationTester($application);
913913

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

918918
public function testRenderExceptionStackTraceContainsRootException()
@@ -935,7 +935,7 @@ public function testRenderExceptionStackTraceContainsRootException()
935935
$tester = new ApplicationTester($application);
936936

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

941941
public function testRun()

src/Symfony/Component/Console/composer.json

+1
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.17",
2223
"symfony/service-contracts": "^1.1|^2"
2324
},
2425
"require-dev": {

src/Symfony/Component/Debug/ErrorHandler.php

+2-2
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

+1-1
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

+13-5
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,11 @@ 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+
if (preg_match('/^([\\\\\w]+)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', $class, $matches)) {
138+
$this->class = ('class' === $matches[1] ? get_parent_class($matches[0]) : $matches[1]).'@anonymous';
139+
} else {
140+
$this->class = $class;
141+
}
138142

139143
return $this;
140144
}
@@ -179,9 +183,13 @@ public function getMessage()
179183
*/
180184
public function setMessage($message)
181185
{
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];
186+
if (str_contains($message, "@anonymous\0")) {
187+
$message = preg_replace_callback('/([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
188+
if ('class' === $m[1]) {
189+
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: 'class').'@anonymous' : $m[0];
190+
}
191+
192+
return $m[1].'@anonymous';
185193
}, $message);
186194
}
187195

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

+18
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,24 @@ public function testHandleUserError()
325325
}
326326
}
327327

328+
public function testHandleErrorWithAnonymousClass(): void
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+
$this->assertSame('User Warning: foo stdClass@anonymous bar', $e->getMessage());
338+
$this->assertSame(E_USER_WARNING, $e->getSeverity());
339+
$this->assertSame('foo.php', $e->getFile());
340+
$this->assertSame(12, $e->getLine());
341+
} finally {
342+
restore_error_handler();
343+
}
344+
}
345+
328346
public function testHandleDeprecation()
329347
{
330348
$logArgCheck = function ($level, $message, $context) {

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

+5
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

+2-1
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.17"
2122
},
2223
"conflict": {
2324
"symfony/http-kernel": "<3.4"

src/Symfony/Component/ErrorHandler/DebugClassLoader.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
407407
}
408408
$deprecations = [];
409409

410-
$className = isset($class[15]) && "\0" === $class[15] && 0 === strpos($class, "class@anonymous\x00") ? get_parent_class($class).'@anonymous' : $class;
410+
$className = $refl->isAnonymous() ? (($parentRefl = $refl->getParentClass()) ? $parentRefl->name : 'class').'@anonymous' : $class;
411411

412412
// Don't trigger deprecations for classes in the same vendor
413413
if ($class !== $className) {

src/Symfony/Component/ErrorHandler/ErrorHandler.php

+8-4
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ public function handleError(int $type, string $message, string $file, int $line)
435435
$context = $e;
436436
}
437437

438-
if (false !== strpos($message, "class@anonymous\0")) {
438+
if (false !== strpos($message, "@anonymous\0")) {
439439
$logMessage = $this->parseAnonymousClass($message);
440440
} else {
441441
$logMessage = $this->levels[$type].': '.$message;
@@ -558,7 +558,7 @@ public function handleException(\Throwable $exception)
558558
}
559559

560560
if ($this->loggedErrors & $type) {
561-
if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) {
561+
if (false !== strpos($message = $exception->getMessage(), "@anonymous\0")) {
562562
$message = $this->parseAnonymousClass($message);
563563
}
564564

@@ -768,8 +768,12 @@ private function cleanTrace(array $backtrace, int $type, string $file, int $line
768768
*/
769769
private function parseAnonymousClass(string $message): string
770770
{
771-
return preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
772-
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
771+
return preg_replace_callback('/([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
772+
if ('class' === $m[1]) {
773+
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: 'class').'@anonymous' : $m[0];
774+
}
775+
776+
return $m[1].'@anonymous';
773777
}, $message);
774778
}
775779
}

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

+13-5
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public static function createFromThrowable(\Throwable $exception, int $statusCod
7171
$e->setStatusCode($statusCode);
7272
$e->setHeaders($headers);
7373
$e->setTraceFromThrowable($exception);
74-
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
74+
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : get_debug_type($exception));
7575
$e->setFile($exception->getFile());
7676
$e->setLine($exception->getLine());
7777

@@ -138,7 +138,11 @@ public function getClass(): string
138138
*/
139139
public function setClass($class): self
140140
{
141-
$this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
141+
if (preg_match('/^([\\\\\w]+)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', $class, $matches)) {
142+
$this->class = ('class' === $matches[1] ? get_parent_class($matches[0]) : $matches[1]).'@anonymous';
143+
} else {
144+
$this->class = $class;
145+
}
142146

143147
return $this;
144148
}
@@ -195,9 +199,13 @@ public function getMessage(): string
195199
*/
196200
public function setMessage($message): self
197201
{
198-
if (false !== strpos($message, "class@anonymous\0")) {
199-
$message = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
200-
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
202+
if (str_contains($message, "@anonymous\0")) {
203+
$message = preg_replace_callback('/([\\\\\w]+)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
204+
if ('class' === $m[1]) {
205+
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
206+
}
207+
208+
return $m[1].'@anonymous';
201209
}, $message);
202210
}
203211

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

+22
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,24 @@ public function testHandleUserError()
365365
}
366366
}
367367

368+
public function testHandleErrorWithAnonymousClass(): void
369+
{
370+
$handler = ErrorHandler::register();
371+
$handler->throwAt(3, true);
372+
try {
373+
$handler->handleError(3, 'foo '.\get_class(new class() extends \stdClass {
374+
}).' bar', 'foo.php', 12);
375+
$this->fail('Exception expected.');
376+
} catch (\ErrorException $e) {
377+
$this->assertSame('foo stdClass@anonymous bar', $e->getMessage());
378+
$this->assertSame(3, $e->getSeverity());
379+
$this->assertSame('foo.php', $e->getFile());
380+
$this->assertSame(12, $e->getLine());
381+
} finally {
382+
restore_error_handler();
383+
}
384+
}
385+
368386
public function testHandleDeprecation()
369387
{
370388
$logArgCheck = function ($level, $message, $context) {
@@ -433,6 +451,10 @@ public function handleExceptionProvider(): array
433451
{
434452
return [
435453
['Uncaught Exception: foo', new \Exception('foo')],
454+
['Uncaught Exception: foo', new class('foo') extends \RuntimeException {
455+
}],
456+
['Uncaught Exception: foo stdClass@anonymous bar', new \RuntimeException('foo '.\get_class(new class() extends \stdClass {
457+
}).' bar')],
436458
['Uncaught Error: bar', new \Error('bar')],
437459
['Uncaught ccc', new \ErrorException('ccc')],
438460
];

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

+5
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@ public function testAnonymousClass()
373373

374374
$this->assertSame('RuntimeException@anonymous', $flattened->getClass());
375375

376+
$flattened->setClass(\get_class(new class('Oops') extends NotFoundHttpException {
377+
}));
378+
379+
$this->assertSame('Symfony\Component\HttpKernel\Exception\NotFoundHttpException@anonymous', $flattened->getClass());
380+
376381
$flattened = FlattenException::createFromThrowable(new \Exception(sprintf('Class "%s" blah.', \get_class(new class() extends \RuntimeException {
377382
}))));
378383

src/Symfony/Component/ErrorHandler/composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"php": ">=7.1.3",
2020
"psr/log": "~1.0",
2121
"symfony/debug": "^4.4.5",
22+
"symfony/polyfill-php80": "^1.17",
2223
"symfony/var-dumper": "^4.4|^5.0"
2324
},
2425
"require-dev": {

src/Symfony/Component/HttpKernel/Kernel.php

+3-6
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,7 @@ public function getBundles()
228228
public function getBundle($name)
229229
{
230230
if (!isset($this->bundles[$name])) {
231-
$class = static::class;
232-
$class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
233-
234-
throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, $class));
231+
throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, get_debug_type($this)));
235232
}
236233

237234
return $this->bundles[$name];
@@ -473,8 +470,8 @@ protected function build(ContainerBuilder $container)
473470
*/
474471
protected function getContainerClass()
475472
{
476-
$class = static::class;
477-
$class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class)) : $class;
473+
$class = get_debug_type($this);
474+
$class = str_ends_with($class, '@anonymous') ? get_parent_class($this).str_replace('.', '_', ContainerBuilder::hash($class)) : $class;
478475
$class = $this->name.str_replace('\\', '_', $class).ucfirst($this->environment).($this->debug ? 'Debug' : '').'Container';
479476

480477
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) {

src/Symfony/Component/HttpKernel/Tests/KernelTest.php

+21
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,27 @@ public function testKernelStartTimeIsResetWhileBootingAlreadyBootedKernel()
640640
$this->assertGreaterThan($preReBoot, $kernel->getStartTime());
641641
}
642642

643+
public function testAnonymousKernelGeneratesValidContainerClass(): void
644+
{
645+
$kernel = new class('test', true) extends Kernel {
646+
public function registerBundles(): iterable
647+
{
648+
return [];
649+
}
650+
651+
public function registerContainerConfiguration(LoaderInterface $loader): void
652+
{
653+
}
654+
655+
public function getContainerClass(): string
656+
{
657+
return parent::getContainerClass();
658+
}
659+
};
660+
661+
$this->assertRegExp('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*TestDebugContainer$/', $kernel->getContainerClass());
662+
}
663+
643664
/**
644665
* Returns a mock for the BundleInterface.
645666
*/

src/Symfony/Component/HttpKernel/composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"symfony/http-foundation": "^4.4|^5.0",
2323
"symfony/polyfill-ctype": "^1.8",
2424
"symfony/polyfill-php73": "^1.9",
25+
"symfony/polyfill-php80": "^1.17",
2526
"psr/log": "~1.0"
2627
},
2728
"require-dev": {

src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,7 @@ public function next(): MiddlewareInterface
7878
if ($this->stack === $nextMiddleware = $this->stack->next()) {
7979
$this->currentEvent = 'Tail';
8080
} else {
81-
$class = \get_class($nextMiddleware);
82-
$this->currentEvent = sprintf('"%s"', 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class);
81+
$this->currentEvent = sprintf('"%s"', get_debug_type($nextMiddleware));
8382
}
8483
$this->currentEvent .= sprintf(' on "%s"', $this->busName);
8584

0 commit comments

Comments
 (0)