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
This commit is contained in:
Nicolas Grekas 2021-02-04 13:00:01 +01:00
commit c92b61857e
17 changed files with 145 additions and 109 deletions

View File

@ -16,9 +16,6 @@ if (!getenv('SYMFONY_PHPUNIT_VERSION')) {
putenv('SYMFONY_PHPUNIT_VERSION=9.5'); putenv('SYMFONY_PHPUNIT_VERSION=9.5');
} }
} }
if (!getenv('SYMFONY_PATCH_TYPE_DECLARATIONS')) {
putenv('SYMFONY_PATCH_TYPE_DECLARATIONS=deprecations=1');
}
if (getcwd() === realpath(__DIR__.'/src/Symfony/Bridge/PhpUnit')) { if (getcwd() === realpath(__DIR__.'/src/Symfony/Bridge/PhpUnit')) {
putenv('SYMFONY_DEPRECATIONS_HELPER=disabled'); putenv('SYMFONY_DEPRECATIONS_HELPER=disabled');
} }

View File

@ -164,11 +164,22 @@ class ContainerAwareEventManager extends EventManager
private function initializeSubscribers() private function initializeSubscribers()
{ {
$this->initializedSubscribers = true; $this->initializedSubscribers = true;
$eventListeners = $this->listeners;
// reset eventListener to respect priority: EventSubscribers have a higher priority
$this->listeners = [];
foreach ($this->subscribers as $id => $subscriber) { foreach ($this->subscribers as $id => $subscriber) {
if (\is_string($subscriber)) { if (\is_string($subscriber)) {
parent::addEventSubscriber($this->subscribers[$id] = $this->container->get($subscriber)); parent::addEventSubscriber($this->subscribers[$id] = $this->container->get($subscriber));
} }
} }
foreach ($eventListeners as $event => $listeners) {
if (!isset($this->listeners[$event])) {
$this->listeners[$event] = [];
}
$this->listeners[$event] += $listeners;
}
$this->subscribers = [];
} }
/** /**

View File

@ -146,11 +146,14 @@ class ContainerAwareEventManagerTest extends TestCase
public function testGetListenersForEvent() public function testGetListenersForEvent()
{ {
$this->evm = new ContainerAwareEventManager($this->container, ['lazy2']);
$this->container->set('lazy', $listener1 = new MyListener()); $this->container->set('lazy', $listener1 = new MyListener());
$this->container->set('lazy2', $subscriber1 = new MySubscriber(['foo']));
$this->evm->addEventListener('foo', 'lazy'); $this->evm->addEventListener('foo', 'lazy');
$this->evm->addEventListener('foo', $listener2 = new MyListener()); $this->evm->addEventListener('foo', $listener2 = new MyListener());
$this->assertSame([$listener1, $listener2], array_values($this->evm->getListeners('foo'))); $this->assertSame([$subscriber1, $listener1, $listener2], array_values($this->evm->getListeners('foo')));
} }
public function testGetListeners() public function testGetListeners()

View File

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

View File

@ -125,43 +125,47 @@ class DeprecationErrorHandler
return \call_user_func(self::getPhpUnitErrorHandler(), $type, $msg, $file, $line, $context); return \call_user_func(self::getPhpUnitErrorHandler(), $type, $msg, $file, $line, $context);
} }
$deprecation = new Deprecation($msg, debug_backtrace(), $file); $trace = debug_backtrace();
if (isset($trace[1]['function'], $trace[1]['args'][0]) && ('trigger_error' === $trace[1]['function'] || 'user_error' === $trace[1]['function'])) {
$msg = $trace[1]['args'][0];
}
$deprecation = new Deprecation($msg, $trace, $file);
if ($deprecation->isMuted()) { if ($deprecation->isMuted()) {
return null; return null;
} }
if ($this->getConfiguration()->isBaselineDeprecation($deprecation)) { if ($this->getConfiguration()->isBaselineDeprecation($deprecation)) {
return null; return null;
} }
$group = 'other';
if ($deprecation->originatesFromAnObject()) { $msg = $deprecation->getMessage();
if (error_reporting() & $type) {
$group = 'unsilenced';
} elseif ($deprecation->isLegacy()) {
$group = 'legacy';
} else {
$group = [
Deprecation::TYPE_SELF => 'self',
Deprecation::TYPE_DIRECT => 'direct',
Deprecation::TYPE_INDIRECT => 'indirect',
Deprecation::TYPE_UNDETERMINED => 'other',
][$deprecation->getType()];
}
if ($this->getConfiguration()->shouldDisplayStackTrace($msg)) {
echo "\n".ucfirst($group).' '.$deprecation->toString();
exit(1);
}
if ('legacy' === $group) {
$this->deprecationGroups[$group]->addNotice();
} else if ($deprecation->originatesFromAnObject()) {
$class = $deprecation->originatingClass(); $class = $deprecation->originatingClass();
$method = $deprecation->originatingMethod(); $method = $deprecation->originatingMethod();
$msg = $deprecation->getMessage(); $this->deprecationGroups[$group]->addNoticeFromObject($msg, $class, $method);
if (error_reporting() & $type) {
$group = 'unsilenced';
} elseif ($deprecation->isLegacy()) {
$group = 'legacy';
} else {
$group = [
Deprecation::TYPE_SELF => 'self',
Deprecation::TYPE_DIRECT => 'direct',
Deprecation::TYPE_INDIRECT => 'indirect',
Deprecation::TYPE_UNDETERMINED => 'other',
][$deprecation->getType()];
}
if ($this->getConfiguration()->shouldDisplayStackTrace($msg)) {
echo "\n".ucfirst($group).' '.$deprecation->toString();
exit(1);
}
if ('legacy' !== $group) {
$this->deprecationGroups[$group]->addNoticeFromObject($msg, $class, $method);
} else {
$this->deprecationGroups[$group]->addNotice();
}
} else { } else {
$this->deprecationGroups[$group]->addNoticeFromProceduralCode($msg); $this->deprecationGroups[$group]->addNoticeFromProceduralCode($msg);
} }

View File

@ -60,25 +60,33 @@ class Deprecation
} }
$this->trace = $trace; $this->trace = $trace;
if ('trigger_error' === (isset($trace[1]['function']) ? $trace[1]['function'] : null)
&& (DebugClassLoader::class === ($class = (isset($trace[2]['class']) ? $trace[2]['class'] : null)) || LegacyDebugClassLoader::class === $class)
&& 'checkClass' === (isset($trace[2]['function']) ? $trace[2]['function'] : null)
&& null !== ($extraFile = (isset($trace[2]['args'][1]) ? $trace[2]['args'][1] : null))
&& '' !== $extraFile
&& false !== $extraFile = realpath($extraFile)
) {
$this->getOriginalFilesStack();
array_splice($this->originalFilesStack, 2, 1, $extraFile);
}
$this->message = $message; $this->message = $message;
$i = \count($this->trace);
while (1 < $i && $this->lineShouldBeSkipped($this->trace[--$i])) { $i = \count($trace);
while (1 < $i && $this->lineShouldBeSkipped($trace[--$i])) {
// No-op // No-op
} }
$line = $this->trace[$i];
$line = $trace[$i];
$this->triggeringFile = $file; $this->triggeringFile = $file;
for ($j = 1; $j < $i; ++$j) {
if (!isset($trace[$j]['function'], $trace[1 + $j]['class'], $trace[1 + $j]['args'][0])) {
continue;
}
if ('trigger_error' === $trace[$j]['function'] && !isset($trace[$j]['class'])) {
if (\in_array($trace[1 + $j]['class'], [DebugClassLoader::class, LegacyDebugClassLoader::class], true)) {
$class = $trace[1 + $j]['args'][0];
$this->triggeringFile = isset($trace[1 + $j]['args'][1]) ? realpath($trace[1 + $j]['args'][1]) : (new \ReflectionClass($class))->getFileName();
$this->getOriginalFilesStack();
array_splice($this->originalFilesStack, 0, $j, [$this->triggeringFile]);
}
break;
}
}
if (isset($line['object']) || isset($line['class'])) { if (isset($line['object']) || isset($line['class'])) {
set_error_handler(function () {}); set_error_handler(function () {});
$parsedMsg = unserialize($this->message); $parsedMsg = unserialize($this->message);
@ -101,12 +109,19 @@ class Deprecation
return; return;
} }
if (isset($line['class']) && 0 === strpos($line['class'], SymfonyTestsListenerFor::class)) { if (!isset($line['class'], $trace[$i - 2]['function']) || 0 !== strpos($line['class'], SymfonyTestsListenerFor::class)) {
$this->originClass = isset($line['object']) ? \get_class($line['object']) : $line['class'];
$this->originMethod = $line['function'];
return; return;
} }
$this->originClass = isset($line['object']) ? \get_class($line['object']) : $line['class']; if ('trigger_error' !== $trace[$i - 2]['function'] || isset($trace[$i - 2]['class'])) {
$this->originMethod = $line['function']; $this->originClass = \get_class($line['args'][0]);
$this->originMethod = $line['args'][0]->getName();
return;
}
} }
} }
@ -140,7 +155,9 @@ class Deprecation
throw new \LogicException('Check with originatesFromAnObject() before calling this method.'); throw new \LogicException('Check with originatesFromAnObject() before calling this method.');
} }
return $this->originClass; $class = $this->originClass;
return false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
} }
/** /**
@ -168,8 +185,7 @@ class Deprecation
*/ */
public function isLegacy() public function isLegacy()
{ {
$class = $this->originatingClass(); if (!$this->originClass || (new \ReflectionClass($this->originClass))->isInternal()) {
if ((new \ReflectionClass($class))->isInternal()) {
return false; return false;
} }
@ -178,8 +194,8 @@ class Deprecation
return 0 === strpos($method, 'testLegacy') return 0 === strpos($method, 'testLegacy')
|| 0 === strpos($method, 'provideLegacy') || 0 === strpos($method, 'provideLegacy')
|| 0 === strpos($method, 'getLegacy') || 0 === strpos($method, 'getLegacy')
|| strpos($class, '\Legacy') || strpos($this->originClass, '\Legacy')
|| \in_array('legacy', Test::getGroups($class, $method), true); || \in_array('legacy', Test::getGroups($this->originClass, $method), true);
} }
/** /**
@ -205,11 +221,10 @@ class Deprecation
*/ */
public function getType() public function getType()
{ {
$triggeringFilePathType = $this->getPathType($this->triggeringFile); if (self::PATH_TYPE_SELF === $pathType = $this->getPathType($this->triggeringFile)) {
if (self::PATH_TYPE_SELF === $triggeringFilePathType) {
return self::TYPE_SELF; return self::TYPE_SELF;
} }
if (self::PATH_TYPE_UNDETERMINED === $triggeringFilePathType) { if (self::PATH_TYPE_UNDETERMINED === $pathType) {
return self::TYPE_UNDETERMINED; return self::TYPE_UNDETERMINED;
} }
$erroringFile = $erroringPackage = null; $erroringFile = $erroringPackage = null;
@ -218,10 +233,10 @@ class Deprecation
if ('-' === $file || 'Standard input code' === $file || !realpath($file)) { if ('-' === $file || 'Standard input code' === $file || !realpath($file)) {
continue; continue;
} }
if (self::PATH_TYPE_SELF === $this->getPathType($file)) { if (self::PATH_TYPE_SELF === $pathType = $this->getPathType($file)) {
return self::TYPE_DIRECT; return self::TYPE_DIRECT;
} }
if (self::PATH_TYPE_UNDETERMINED === $this->getPathType($file)) { if (self::PATH_TYPE_UNDETERMINED === $pathType) {
return self::TYPE_UNDETERMINED; return self::TYPE_UNDETERMINED;
} }
if (null !== $erroringFile && null !== $erroringPackage) { if (null !== $erroringFile && null !== $erroringPackage) {
@ -243,7 +258,7 @@ class Deprecation
if (null === $this->originalFilesStack) { if (null === $this->originalFilesStack) {
$this->originalFilesStack = []; $this->originalFilesStack = [];
foreach ($this->trace as $frame) { foreach ($this->trace as $frame) {
if (!isset($frame['file']) || \in_array($frame['function'], ['require', 'require_once', 'include', 'include_once'], true)) { if (!isset($frame['file'], $frame['function']) || (!isset($frame['class']) && \in_array($frame['function'], ['require', 'require_once', 'include', 'include_once'], true))) {
continue; continue;
} }
@ -269,13 +284,10 @@ class Deprecation
$relativePath = substr($path, \strlen($vendorRoot) + 1); $relativePath = substr($path, \strlen($vendorRoot) + 1);
$vendor = strstr($relativePath, \DIRECTORY_SEPARATOR, true); $vendor = strstr($relativePath, \DIRECTORY_SEPARATOR, true);
if (false === $vendor) { if (false === $vendor) {
throw new \RuntimeException(sprintf('Could not find directory separator "%s" in path "%s".', \DIRECTORY_SEPARATOR, $relativePath)); return 'symfony';
} }
return rtrim($vendor.'/'.strstr(substr( return rtrim($vendor.'/'.strstr(substr($relativePath, \strlen($vendor) + 1), \DIRECTORY_SEPARATOR, true), '/');
$relativePath,
\strlen($vendor) + 1
), \DIRECTORY_SEPARATOR, true), '/');
} }
} }
@ -289,6 +301,13 @@ class Deprecation
{ {
if (null === self::$vendors) { if (null === self::$vendors) {
self::$vendors = $paths = []; self::$vendors = $paths = [];
self::$vendors[] = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Legacy';
if (class_exists(DebugClassLoader::class, false)) {
self::$vendors[] = \dirname((new \ReflectionClass(DebugClassLoader::class))->getFileName());
}
if (class_exists(LegacyDebugClassLoader::class, false)) {
self::$vendors[] = \dirname((new \ReflectionClass(LegacyDebugClassLoader::class))->getFileName());
}
foreach (get_declared_classes() as $class) { foreach (get_declared_classes() as $class) {
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class); $r = new \ReflectionClass($class);
@ -364,10 +383,9 @@ class Deprecation
$reflection->setAccessible(true); $reflection->setAccessible(true);
$reflection->setValue($exception, $this->trace); $reflection->setValue($exception, $this->trace);
return 'deprecation triggered by '.$this->originatingClass().'::'.$this->originatingMethod().':'. return ($this->originatesFromAnObject() ? 'deprecation triggered by '.$this->originatingClass().'::'.$this->originatingMethod().":\n" : '')
"\n".$this->message. .$this->message."\n"
"\nStack trace:". ."Stack trace:\n"
"\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString()). .str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $exception->getTraceAsString())."\n";
"\n";
} }
} }

View File

@ -183,6 +183,11 @@ class DeprecationTest extends TestCase
public function providerGetTypeUsesRightTrace() public function providerGetTypeUsesRightTrace()
{ {
$vendorDir = self::getVendorDir(); $vendorDir = self::getVendorDir();
$fakeTrace = [
['function' => 'trigger_error'],
['class' => SymfonyTestsListenerTrait::class, 'function' => 'endTest'],
['class' => SymfonyTestsListenerForV5::class, 'function' => 'endTest'],
];
return [ return [
'no_file_in_stack' => [Deprecation::TYPE_DIRECT, '', [['function' => 'myfunc1'], ['function' => 'myfunc2']]], 'no_file_in_stack' => [Deprecation::TYPE_DIRECT, '', [['function' => 'myfunc1'], ['function' => 'myfunc2']]],
@ -205,7 +210,7 @@ class DeprecationTest extends TestCase
$vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php', $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php',
], ],
]), ]),
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']], $fakeTrace,
], ],
'serialized_stack_files_from_various_packages' => [ 'serialized_stack_files_from_various_packages' => [
Deprecation::TYPE_INDIRECT, Deprecation::TYPE_INDIRECT,
@ -218,7 +223,7 @@ class DeprecationTest extends TestCase
$vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php', $vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php',
], ],
]), ]),
[['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']], $fakeTrace,
], ],
]; ];
} }

View File

@ -30,16 +30,6 @@ EOPHP
); );
require __DIR__.'/fake_vendor/autoload.php'; 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(); \Symfony\Component\ErrorHandler\DebugClassLoader::enable();
new \App\Services\BarService(); new \App\Services\BarService();

View File

@ -29,13 +29,11 @@ Unsilenced deprecation notices (3)
1x: unsilenced bar deprecation 1x: unsilenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Remaining direct deprecation notices (1) Remaining direct deprecation notices (2)
1x: root deprecation
1x: silenced bar deprecation 1x: silenced bar deprecation
1x in FooTestCase::testNonLegacyBar 1x in FooTestCase::testNonLegacyBar
Legacy deprecation notices (2) Legacy deprecation notices (2)
Other deprecation notices (1)
1x: root deprecation

View File

@ -10,7 +10,7 @@
*/ */
// Please update when phpunit needs to be reinstalled with fresh deps: // Please update when phpunit needs to be reinstalled with fresh deps:
// Cache-Id: 2020-01-31 10:00 UTC // Cache-Id: 2021-02-04 11:00 UTC
error_reporting(-1); error_reporting(-1);

View File

@ -40,7 +40,7 @@ class ExpressionCacheWarmer implements CacheWarmerInterface
public function warmUp(string $cacheDir) public function warmUp(string $cacheDir)
{ {
foreach ($this->expressions as $expression) { foreach ($this->expressions as $expression) {
$this->expressionLanguage->parse($expression, ['token', 'user', 'object', 'subject', 'roles', 'request', 'trust_resolver']); $this->expressionLanguage->parse($expression, ['token', 'user', 'object', 'subject', 'role_names', 'request', 'trust_resolver']);
} }
return []; return [];

View File

@ -26,8 +26,8 @@ class ExpressionCacheWarmerTest extends TestCase
$expressionLang->expects($this->exactly(2)) $expressionLang->expects($this->exactly(2))
->method('parse') ->method('parse')
->withConsecutive( ->withConsecutive(
[$expressions[0], ['token', 'user', 'object', 'subject', 'roles', 'request', 'trust_resolver']], [$expressions[0], ['token', 'user', 'object', 'subject', 'role_names', 'request', 'trust_resolver']],
[$expressions[1], ['token', 'user', 'object', 'subject', 'roles', 'request', 'trust_resolver']] [$expressions[1], ['token', 'user', 'object', 'subject', 'role_names', 'request', 'trust_resolver']]
); );
(new ExpressionCacheWarmer($expressions, $expressionLang))->warmUp(''); (new ExpressionCacheWarmer($expressions, $expressionLang))->warmUp('');

View File

@ -534,7 +534,7 @@ class DebugClassLoader
if (null !== (self::INTERNAL_TYPES[$use] ?? null)) { if (null !== (self::INTERNAL_TYPES[$use] ?? null)) {
foreach (self::INTERNAL_TYPES[$use] as $method => $returnType) { foreach (self::INTERNAL_TYPES[$use] as $method => $returnType) {
if ('void' !== $returnType) { if ('void' !== $returnType) {
self::$returnTypes[$class] += [$method => [$returnType, $returnType, $class, '']]; self::$returnTypes[$class] += [$method => [$returnType, $returnType, $use, '']];
} }
} }
} }
@ -611,7 +611,7 @@ class DebugClassLoader
$this->patchMethod($method, $returnType, $declaringFile, $normalizedType); $this->patchMethod($method, $returnType, $declaringFile, $normalizedType);
} }
if (strncmp($ns, $declaringClass, $len)) { if (false === strpos($doc, '@deprecated') && strncmp($ns, $declaringClass, $len)) {
if ($canAddReturnType && 'docblock' === $this->patchTypes['force'] && false === strpos($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) { if ($canAddReturnType && 'docblock' === $this->patchTypes['force'] && false === strpos($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) {
$this->patchMethod($method, $returnType, $declaringFile, $normalizedType); $this->patchMethod($method, $returnType, $declaringFile, $normalizedType);
} elseif ('' !== $declaringClass && $this->patchTypes['deprecations']) { } elseif ('' !== $declaringClass && $this->patchTypes['deprecations']) {

View File

@ -428,11 +428,7 @@ class ErrorHandler
return false; return false;
} }
if (false !== strpos($message, "@anonymous\0")) { $logMessage = $this->levels[$type].': '.$message;
$logMessage = $this->parseAnonymousClass($message);
} else {
$logMessage = $this->levels[$type].': '.$message;
}
if (null !== self::$toStringException) { if (null !== self::$toStringException) {
$errorAsException = self::$toStringException; $errorAsException = self::$toStringException;
@ -461,6 +457,23 @@ class ErrorHandler
return true; return true;
} }
} else { } else {
if (false !== strpos($message, '@anonymous')) {
$backtrace = debug_backtrace(false, 5);
for ($i = 1; isset($backtrace[$i]); ++$i) {
if (isset($backtrace[$i]['function'], $backtrace[$i]['args'][0])
&& ('trigger_error' === $backtrace[$i]['function'] || 'user_error' === $backtrace[$i]['function'])
) {
if ($backtrace[$i]['args'][0] !== $message) {
$message = $this->parseAnonymousClass($backtrace[$i]['args'][0]);
$logMessage = $this->levels[$type].': '.$message;
}
break;
}
}
}
$errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
if ($throw || $this->tracedErrors & $type) { if ($throw || $this->tracedErrors & $type) {

View File

@ -368,11 +368,12 @@ class ErrorHandlerTest extends TestCase
public function testHandleErrorWithAnonymousClass() public function testHandleErrorWithAnonymousClass()
{ {
$anonymousObject = new class() extends \stdClass {
};
$handler = ErrorHandler::register(); $handler = ErrorHandler::register();
$handler->throwAt(3, true);
try { try {
$handler->handleError(3, 'foo '.\get_class(new class() extends \stdClass { trigger_error('foo '.\get_class($anonymousObject).' bar', \E_USER_WARNING);
}).' bar', 'foo.php', 12);
$this->fail('Exception expected.'); $this->fail('Exception expected.');
} catch (\ErrorException $e) { } catch (\ErrorException $e) {
} finally { } finally {
@ -380,10 +381,7 @@ class ErrorHandlerTest extends TestCase
restore_exception_handler(); restore_exception_handler();
} }
$this->assertSame('foo stdClass@anonymous bar', $e->getMessage()); $this->assertSame('User Warning: foo stdClass@anonymous bar', $e->getMessage());
$this->assertSame(3, $e->getSeverity());
$this->assertSame('foo.php', $e->getFile());
$this->assertSame(12, $e->getLine());
} }
public function testHandleDeprecation() public function testHandleDeprecation()

View File

@ -233,12 +233,12 @@ class SerializableUser implements UserInterface, \Serializable
return null; return null;
} }
public function serialize() public function serialize(): string
{ {
return serialize($this->name); return serialize($this->name);
} }
public function unserialize($serialized) public function unserialize($serialized): void
{ {
$this->name = unserialize($serialized); $this->name = unserialize($serialized);
} }