diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md
index 8ffb10d4091ff..e4f3a025d4000 100644
--- a/UPGRADE-4.4.md
+++ b/UPGRADE-4.4.md
@@ -6,6 +6,12 @@ Cache
* Added argument `$prefix` to `AdapterInterface::clear()`
+Debug
+-----
+
+ * Deprecated `FlattenException`, use the `FlattenException` of the `ErrorRenderer` component
+ * Deprecated the whole component in favor of `ErrorHandler` component
+
DependencyInjection
-------------------
diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md
index 36ed24ff692a4..e178f252cfce5 100644
--- a/UPGRADE-5.0.md
+++ b/UPGRADE-5.0.md
@@ -51,6 +51,11 @@ Console
$processHelper->run($output, Process::fromShellCommandline('ls -l'));
```
+Debug
+-----
+
+ * Removed the component
+
DependencyInjection
-------------------
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php
index bb1e8ab4c2f0a..803f7114b8129 100644
--- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php
@@ -18,7 +18,8 @@
use PHPUnit\Util\Blacklist;
use Symfony\Bridge\PhpUnit\ClockMock;
use Symfony\Bridge\PhpUnit\DnsMock;
-use Symfony\Component\Debug\DebugClassLoader;
+use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader;
+use Symfony\Component\ErrorHandler\DebugClassLoader;
/**
* PHP 5.3 compatible trait-like shared implementation.
@@ -53,7 +54,7 @@ public function __construct(array $mockedNamespaces = array())
Blacklist::$blacklistedClassNames['\Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait'] = 2;
}
- $enableDebugClassLoader = class_exists('Symfony\Component\Debug\DebugClassLoader');
+ $enableDebugClassLoader = class_exists(DebugClassLoader::class) || class_exists(LegacyDebugClassLoader::class);
foreach ($mockedNamespaces as $type => $namespaces) {
if (!\is_array($namespaces)) {
@@ -74,7 +75,11 @@ public function __construct(array $mockedNamespaces = array())
}
}
if ($enableDebugClassLoader) {
- DebugClassLoader::enable();
+ if (class_exists(DebugClassLoader::class)) {
+ DebugClassLoader::enable();
+ } else {
+ LegacyDebugClassLoader::enable();
+ }
}
if (self::$globallyEnabled) {
$this->state = -2;
diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json
index 1518be6503b55..6d06cb3fefc51 100644
--- a/src/Symfony/Bridge/PhpUnit/composer.json
+++ b/src/Symfony/Bridge/PhpUnit/composer.json
@@ -21,7 +21,7 @@
"php": ">=5.5.9"
},
"suggest": {
- "symfony/debug": "For tracking deprecated interfaces usages at runtime with DebugClassLoader"
+ "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader"
},
"conflict": {
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php
index 319b9be9dd364..0fdb7ecd44abf 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php
@@ -19,8 +19,8 @@
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
-use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;
diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
index f9a9d56b56526..b6ae6b7ecf22a 100644
--- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
@@ -29,10 +29,10 @@
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
-use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\ErrorRenderer\DependencyInjection\ErrorRendererPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\Form\DependencyInjection\FormPass;
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index cb4bec153ce75..1cc09092b34d7 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -70,7 +70,6 @@
"symfony/asset": "<3.4",
"symfony/browser-kit": "<4.3",
"symfony/console": "<4.3",
- "symfony/debug": "<4.4",
"symfony/dotenv": "<4.2",
"symfony/dom-crawler": "<4.3",
"symfony/form": "<4.3",
diff --git a/src/Symfony/Component/Debug/BufferingLogger.php b/src/Symfony/Component/Debug/BufferingLogger.php
index e7db3a4ce4c6a..6e308f2247286 100644
--- a/src/Symfony/Component/Debug/BufferingLogger.php
+++ b/src/Symfony/Component/Debug/BufferingLogger.php
@@ -13,10 +13,14 @@
use Psr\Log\AbstractLogger;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', BufferingLogger::class, \Symfony\Component\ErrorHandler\BufferingLogger::class), E_USER_DEPRECATED);
+
/**
* A buffering logger that stacks logs for later.
*
* @author Nicolas Grekas
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\BufferingLogger instead.
*/
class BufferingLogger extends AbstractLogger
{
diff --git a/src/Symfony/Component/Debug/CHANGELOG.md b/src/Symfony/Component/Debug/CHANGELOG.md
index 437113854e668..b2e17f38211a8 100644
--- a/src/Symfony/Component/Debug/CHANGELOG.md
+++ b/src/Symfony/Component/Debug/CHANGELOG.md
@@ -5,6 +5,7 @@ CHANGELOG
-----
* deprecated `FlattenException`, use the `FlattenException` of the `ErrorRenderer` component
+ * deprecated the whole component in favor of the `ErrorHandler` component
4.3.0
-----
diff --git a/src/Symfony/Component/Debug/Debug.php b/src/Symfony/Component/Debug/Debug.php
index 5d2d55cf9f021..788ad7d9243ff 100644
--- a/src/Symfony/Component/Debug/Debug.php
+++ b/src/Symfony/Component/Debug/Debug.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\Debug;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', Debug::class, \Symfony\Component\ErrorHandler\Debug::class), E_USER_DEPRECATED);
+
/**
* Registers all the debug tools.
*
* @author Fabien Potencier
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Debug instead.
*/
class Debug
{
diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php
index ff9a8d72f903f..84a94b691253a 100644
--- a/src/Symfony/Component/Debug/DebugClassLoader.php
+++ b/src/Symfony/Component/Debug/DebugClassLoader.php
@@ -13,6 +13,8 @@
use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', DebugClassLoader::class, \Symfony\Component\ErrorHandler\DebugClassLoader::class), E_USER_DEPRECATED);
+
/**
* Autoloader checking if the class is really defined in the file found.
*
@@ -24,6 +26,8 @@
* @author Christophe Coevoet
* @author Nicolas Grekas
* @author Guilhem Niot
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\DebugClassLoader instead.
*/
class DebugClassLoader
{
diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php
index a99a000b07fa9..0134a71423053 100644
--- a/src/Symfony/Component/Debug/ErrorHandler.php
+++ b/src/Symfony/Component/Debug/ErrorHandler.php
@@ -23,6 +23,8 @@
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ErrorHandler::class, \Symfony\Component\ErrorHandler\ErrorHandler::class), E_USER_DEPRECATED);
+
/**
* A generic ErrorHandler for the PHP engine.
*
@@ -47,6 +49,8 @@
* @author Grégoire Pineau
*
* @final since Symfony 4.3
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\ErrorHandler instead.
*/
class ErrorHandler
{
diff --git a/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php b/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php
index fa98c4975d02a..21e2c0db53a93 100644
--- a/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php
+++ b/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\Debug\Exception;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundException::class, \Symfony\Component\ErrorHandler\Exception\ClassNotFoundException::class), E_USER_DEPRECATED);
+
/**
* Class (or Trait or Interface) Not Found Exception.
*
* @author Konstanton Myakshin
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\ClassNotFoundException instead.
*/
class ClassNotFoundException extends FatalErrorException
{
diff --git a/src/Symfony/Component/Debug/Exception/FatalErrorException.php b/src/Symfony/Component/Debug/Exception/FatalErrorException.php
index 93880fbc323cd..23c2ede7eb964 100644
--- a/src/Symfony/Component/Debug/Exception/FatalErrorException.php
+++ b/src/Symfony/Component/Debug/Exception/FatalErrorException.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\Debug\Exception;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorException::class, \Symfony\Component\ErrorHandler\Exception\FatalErrorException::class), E_USER_DEPRECATED);
+
/**
* Fatal Error Exception.
*
* @author Konstanton Myakshin
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FatalErrorException instead.
*/
class FatalErrorException extends \ErrorException
{
diff --git a/src/Symfony/Component/Debug/Exception/FatalThrowableError.php b/src/Symfony/Component/Debug/Exception/FatalThrowableError.php
index cdafb2a56842d..d7d36ac17e312 100644
--- a/src/Symfony/Component/Debug/Exception/FatalThrowableError.php
+++ b/src/Symfony/Component/Debug/Exception/FatalThrowableError.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\Debug\Exception;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalThrowableError::class, \Symfony\Component\ErrorHandler\Exception\FatalThrowableError::class), E_USER_DEPRECATED);
+
/**
* Fatal Throwable Error.
*
* @author Nicolas Grekas
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FatalThrowableError instead.
*/
class FatalThrowableError extends FatalErrorException
{
diff --git a/src/Symfony/Component/Debug/Exception/FlattenException.php b/src/Symfony/Component/Debug/Exception/FlattenException.php
index eaed1f1fa6f90..f7bdcde9656c1 100644
--- a/src/Symfony/Component/Debug/Exception/FlattenException.php
+++ b/src/Symfony/Component/Debug/Exception/FlattenException.php
@@ -14,7 +14,7 @@
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
-@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "Symfony\Component\ErrorRenderer\Exception\FlattenException" instead.', FlattenException::class), E_USER_DEPRECATED);
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FlattenException::class, \Symfony\Component\ErrorRenderer\Exception\FlattenException::class), E_USER_DEPRECATED);
/**
* FlattenException wraps a PHP Error or Exception to be able to serialize it.
diff --git a/src/Symfony/Component/Debug/Exception/OutOfMemoryException.php b/src/Symfony/Component/Debug/Exception/OutOfMemoryException.php
index fec1979836450..5b02d52ad8bb2 100644
--- a/src/Symfony/Component/Debug/Exception/OutOfMemoryException.php
+++ b/src/Symfony/Component/Debug/Exception/OutOfMemoryException.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\Debug\Exception;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', OutOfMemoryException::class, \Symfony\Component\ErrorHandler\Exception\OutOfMemoryException::class), E_USER_DEPRECATED);
+
/**
* Out of memory exception.
*
* @author Nicolas Grekas
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException instead.
*/
class OutOfMemoryException extends FatalErrorException
{
diff --git a/src/Symfony/Component/Debug/Exception/SilencedErrorContext.php b/src/Symfony/Component/Debug/Exception/SilencedErrorContext.php
index 236c56ed0e2e1..7ed3d7eb52546 100644
--- a/src/Symfony/Component/Debug/Exception/SilencedErrorContext.php
+++ b/src/Symfony/Component/Debug/Exception/SilencedErrorContext.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\Debug\Exception;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', SilencedErrorContext::class, \Symfony\Component\ErrorHandler\Exception\SilencedErrorContext::class), E_USER_DEPRECATED);
+
/**
* Data Object that represents a Silenced Error.
*
* @author Grégoire Pineau
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext instead.
*/
class SilencedErrorContext implements \JsonSerializable
{
diff --git a/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php b/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php
index d936c8759e36c..fefd7d248edaa 100644
--- a/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php
+++ b/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\Debug\Exception;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionException::class, \Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException::class), E_USER_DEPRECATED);
+
/**
* Undefined Function Exception.
*
* @author Konstanton Myakshin
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException instead.
*/
class UndefinedFunctionException extends FatalErrorException
{
diff --git a/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php b/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php
index f627561fe16e2..48559415868de 100644
--- a/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php
+++ b/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\Debug\Exception;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodException::class, \Symfony\Component\ErrorHandler\Exception\UndefinedMethodException::class), E_USER_DEPRECATED);
+
/**
* Undefined Method Exception.
*
* @author Grégoire Pineau
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\UndefinedMethodException instead.
*/
class UndefinedMethodException extends FatalErrorException
{
diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php
index e36610c82e14a..2be65ab6fe46d 100644
--- a/src/Symfony/Component/Debug/ExceptionHandler.php
+++ b/src/Symfony/Component/Debug/ExceptionHandler.php
@@ -15,6 +15,8 @@
use Symfony\Component\Debug\Exception\OutOfMemoryException;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ExceptionHandler::class, \Symfony\Component\ErrorHandler\ExceptionHandler::class), E_USER_DEPRECATED);
+
/**
* ExceptionHandler converts an exception to a Response object.
*
@@ -28,6 +30,8 @@
* @author Nicolas Grekas
*
* @final since Symfony 4.3
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\ExceptionHandler instead.
*/
class ExceptionHandler
{
diff --git a/src/Symfony/Component/Debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php b/src/Symfony/Component/Debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
index a0e2f770f0015..8211d5d0e2676 100644
--- a/src/Symfony/Component/Debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
+++ b/src/Symfony/Component/Debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
@@ -17,10 +17,14 @@
use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\Debug\Exception\FatalErrorException;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundFatalErrorHandler::class, \Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler::class), E_USER_DEPRECATED);
+
/**
* ErrorHandler for classes that do not exist.
*
* @author Fabien Potencier
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler instead.
*/
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
{
diff --git a/src/Symfony/Component/Debug/FatalErrorHandler/FatalErrorHandlerInterface.php b/src/Symfony/Component/Debug/FatalErrorHandler/FatalErrorHandlerInterface.php
index 6b87eb30a126e..a1c4a8ce547cd 100644
--- a/src/Symfony/Component/Debug/FatalErrorHandler/FatalErrorHandlerInterface.php
+++ b/src/Symfony/Component/Debug/FatalErrorHandler/FatalErrorHandlerInterface.php
@@ -13,10 +13,14 @@
use Symfony\Component\Debug\Exception\FatalErrorException;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorHandlerInterface::class, \Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface::class), E_USER_DEPRECATED);
+
/**
* Attempts to convert fatal errors to exceptions.
*
* @author Fabien Potencier
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface instead.
*/
interface FatalErrorHandlerInterface
{
diff --git a/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php b/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
index 9eddeba5a64a3..dc7cb85bf360c 100644
--- a/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
+++ b/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
@@ -14,10 +14,14 @@
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionFatalErrorHandler::class, \Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler::class), E_USER_DEPRECATED);
+
/**
* ErrorHandler for undefined functions.
*
* @author Fabien Potencier
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler instead.
*/
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
{
diff --git a/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php b/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
index 1318cb13baf8c..5972327c498a8 100644
--- a/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
+++ b/src/Symfony/Component/Debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
@@ -14,10 +14,14 @@
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\Exception\UndefinedMethodException;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodFatalErrorHandler::class, \Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler::class), E_USER_DEPRECATED);
+
/**
* ErrorHandler for undefined methods.
*
* @author Grégoire Pineau
+ *
+ * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler instead.
*/
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
{
diff --git a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php
index 6ee20ae8a7e46..6cca7a2e19a3f 100644
--- a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php
+++ b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php
@@ -14,6 +14,9 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\Debug\DebugClassLoader;
+/**
+ * @group legacy
+ */
class DebugClassLoaderTest extends TestCase
{
/**
diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
index f758d21e0e989..c9c28097d60fd 100644
--- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
@@ -25,6 +25,8 @@
*
* @author Robert Schönthal
* @author Nicolas Grekas
+ *
+ * @group legacy
*/
class ErrorHandlerTest extends TestCase
{
diff --git a/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php
index 31f9a90bc4e4f..0bfb680d9bba0 100644
--- a/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php
@@ -19,6 +19,9 @@
require_once __DIR__.'/HeaderMock.php';
+/**
+ * @group legacy
+ */
class ExceptionHandlerTest extends TestCase
{
protected function setUp()
diff --git a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
index 8e615ac640be9..498fafd47cd04 100644
--- a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
@@ -17,6 +17,9 @@
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
+/**
+ * @group legacy
+ */
class ClassNotFoundFatalErrorHandlerTest extends TestCase
{
public static function setUpBeforeClass()
@@ -71,6 +74,7 @@ public function provideClassNotFoundData()
{
$autoloader = new ComposerClassLoader();
$autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception'));
+ $autoloader->add('Symfony_Component_Debug_Tests_Fixtures', realpath(__DIR__.'/../../Tests/Fixtures'));
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
@@ -101,6 +105,7 @@ public function provideClassNotFoundData()
'message' => 'Class \'UndefinedFunctionException\' not found',
],
"Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
+ [$debugClassLoader, 'loadClass'],
],
[
[
@@ -110,6 +115,7 @@ public function provideClassNotFoundData()
'message' => 'Class \'PEARClass\' not found',
],
"Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?",
+ [$debugClassLoader, 'loadClass'],
],
[
[
@@ -119,6 +125,7 @@ public function provideClassNotFoundData()
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
],
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
+ [$debugClassLoader, 'loadClass'],
],
[
[
diff --git a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
index de9994e447fed..b827c4c67aebb 100644
--- a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
@@ -15,6 +15,9 @@
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
+/**
+ * @group legacy
+ */
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
{
/**
diff --git a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
index 268a841351ec4..7ea1b1f95e786 100644
--- a/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
+++ b/src/Symfony/Component/Debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
@@ -15,6 +15,9 @@
use Symfony\Component\Debug\Exception\FatalErrorException;
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
+/**
+ * @group legacy
+ */
class UndefinedMethodFatalErrorHandlerTest extends TestCase
{
/**
diff --git a/src/Symfony/Component/Debug/Tests/phpt/debug_class_loader.phpt b/src/Symfony/Component/Debug/Tests/phpt/debug_class_loader.phpt
index 53839c4899b0c..041affc14df65 100644
--- a/src/Symfony/Component/Debug/Tests/phpt/debug_class_loader.phpt
+++ b/src/Symfony/Component/Debug/Tests/phpt/debug_class_loader.phpt
@@ -23,5 +23,6 @@ class_exists(ExtendedFinalMethod::class);
?>
--EXPECTF--
+%A
The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".
The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod2()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".
diff --git a/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt b/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt
index 9cd44388c3166..25167a2c6aaee 100644
--- a/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt
+++ b/src/Symfony/Component/Debug/Tests/phpt/decorate_exception_hander.phpt
@@ -26,6 +26,7 @@ if (true) {
?>
--EXPECTF--
+%A
object(Symfony\Component\Debug\Exception\ClassNotFoundException)#%d (8) {
["message":protected]=>
string(131) "Attempted to load class "missing" from namespace "Symfony\Component\Debug".
diff --git a/src/Symfony/Component/Debug/Tests/phpt/fatal_with_nested_handlers.phpt b/src/Symfony/Component/Debug/Tests/phpt/fatal_with_nested_handlers.phpt
index 1736a3b5f2a73..d0fa2411e6bd9 100644
--- a/src/Symfony/Component/Debug/Tests/phpt/fatal_with_nested_handlers.phpt
+++ b/src/Symfony/Component/Debug/Tests/phpt/fatal_with_nested_handlers.phpt
@@ -35,6 +35,7 @@ array(1) {
[0]=>
string(37) "Error and exception handlers do match"
}
+%A
object(Symfony\Component\Debug\Exception\FatalErrorException)#%d (%d) {
["message":protected]=>
string(179) "Error: Class Symfony\Component\Debug\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
diff --git a/src/Symfony/Component/ErrorHandler/.gitignore b/src/Symfony/Component/ErrorHandler/.gitignore
new file mode 100644
index 0000000000000..c49a5d8df5c65
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/src/Symfony/Component/ErrorHandler/BufferingLogger.php b/src/Symfony/Component/ErrorHandler/BufferingLogger.php
new file mode 100644
index 0000000000000..fef10d16e5a16
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/BufferingLogger.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler;
+
+use Psr\Log\AbstractLogger;
+
+/**
+ * A buffering logger that stacks logs for later.
+ *
+ * @author Nicolas Grekas
+ */
+class BufferingLogger extends AbstractLogger
+{
+ private $logs = [];
+
+ public function log($level, $message, array $context = [])
+ {
+ $this->logs[] = [$level, $message, $context];
+ }
+
+ public function cleanLogs()
+ {
+ $logs = $this->logs;
+ $this->logs = [];
+
+ return $logs;
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/CHANGELOG.md b/src/Symfony/Component/ErrorHandler/CHANGELOG.md
new file mode 100644
index 0000000000000..094072510d707
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/CHANGELOG.md
@@ -0,0 +1,7 @@
+CHANGELOG
+=========
+
+4.4.0
+-----
+
+ * added the component
diff --git a/src/Symfony/Component/ErrorHandler/Debug.php b/src/Symfony/Component/ErrorHandler/Debug.php
new file mode 100644
index 0000000000000..392abdb475288
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Debug.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler;
+
+/**
+ * Registers all the debug tools.
+ *
+ * @author Fabien Potencier
+ */
+class Debug
+{
+ private static $enabled = false;
+
+ /**
+ * Enables the debug tools.
+ *
+ * This method registers an error handler and an exception handler.
+ *
+ * @param int $errorReportingLevel The level of error reporting you want
+ * @param bool $displayErrors Whether to display errors (for development) or just log them (for production)
+ */
+ public static function enable($errorReportingLevel = E_ALL, $displayErrors = true)
+ {
+ if (static::$enabled) {
+ return;
+ }
+
+ static::$enabled = true;
+
+ if (null !== $errorReportingLevel) {
+ error_reporting($errorReportingLevel);
+ } else {
+ error_reporting(E_ALL);
+ }
+
+ if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
+ ini_set('display_errors', 0);
+ ExceptionHandler::register();
+ } elseif ($displayErrors && (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log'))) {
+ // CLI - display errors only if they're not already logged to STDERR
+ ini_set('display_errors', 1);
+ }
+ if ($displayErrors) {
+ ErrorHandler::register(new ErrorHandler(new BufferingLogger()));
+ } else {
+ ErrorHandler::register()->throwAt(0, true);
+ }
+
+ DebugClassLoader::enable();
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/DebugClassLoader.php b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php
new file mode 100644
index 0000000000000..e1108a51bbc7a
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/DebugClassLoader.php
@@ -0,0 +1,525 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler;
+
+use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
+
+/**
+ * Autoloader checking if the class is really defined in the file found.
+ *
+ * The ClassLoader will wrap all registered autoloaders
+ * and will throw an exception if a file is found but does
+ * not declare the class.
+ *
+ * @author Fabien Potencier
+ * @author Christophe Coevoet
+ * @author Nicolas Grekas
+ * @author Guilhem Niot
+ */
+class DebugClassLoader
+{
+ private $classLoader;
+ private $isFinder;
+ private $loaded = [];
+ private static $caseCheck;
+ private static $checkedClasses = [];
+ private static $final = [];
+ private static $finalMethods = [];
+ private static $deprecated = [];
+ private static $internal = [];
+ private static $internalMethods = [];
+ private static $annotatedParameters = [];
+ private static $darwinCache = ['/' => ['/', []]];
+ private static $method = [];
+
+ public function __construct(callable $classLoader)
+ {
+ $this->classLoader = $classLoader;
+ $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile');
+
+ if (!isset(self::$caseCheck)) {
+ $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR);
+ $i = strrpos($file, \DIRECTORY_SEPARATOR);
+ $dir = substr($file, 0, 1 + $i);
+ $file = substr($file, 1 + $i);
+ $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
+ $test = realpath($dir.$test);
+
+ if (false === $test || false === $i) {
+ // filesystem is case sensitive
+ self::$caseCheck = 0;
+ } elseif (substr($test, -\strlen($file)) === $file) {
+ // filesystem is case insensitive and realpath() normalizes the case of characters
+ self::$caseCheck = 1;
+ } elseif (false !== stripos(PHP_OS, 'darwin')) {
+ // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
+ self::$caseCheck = 2;
+ } else {
+ // filesystem case checks failed, fallback to disabling them
+ self::$caseCheck = 0;
+ }
+ }
+ }
+
+ /**
+ * Gets the wrapped class loader.
+ *
+ * @return callable The wrapped class loader
+ */
+ public function getClassLoader()
+ {
+ return $this->classLoader;
+ }
+
+ /**
+ * Wraps all autoloaders.
+ */
+ public static function enable()
+ {
+ // Ensures we don't hit https://bugs.php.net/42098
+ class_exists('Symfony\Component\ErrorHandler\ErrorHandler');
+ class_exists('Psr\Log\LogLevel');
+
+ if (!\is_array($functions = spl_autoload_functions())) {
+ return;
+ }
+
+ foreach ($functions as $function) {
+ spl_autoload_unregister($function);
+ }
+
+ foreach ($functions as $function) {
+ if (!\is_array($function) || !$function[0] instanceof self) {
+ $function = [new static($function), 'loadClass'];
+ }
+
+ spl_autoload_register($function);
+ }
+ }
+
+ /**
+ * Disables the wrapping.
+ */
+ public static function disable()
+ {
+ if (!\is_array($functions = spl_autoload_functions())) {
+ return;
+ }
+
+ foreach ($functions as $function) {
+ spl_autoload_unregister($function);
+ }
+
+ foreach ($functions as $function) {
+ if (\is_array($function) && $function[0] instanceof self) {
+ $function = $function[0]->getClassLoader();
+ }
+
+ spl_autoload_register($function);
+ }
+ }
+
+ /**
+ * @return string|null
+ */
+ public function findFile($class)
+ {
+ return $this->isFinder ? $this->classLoader[0]->findFile($class) ?: null : null;
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ *
+ * @throws \RuntimeException
+ */
+ public function loadClass($class)
+ {
+ $e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
+
+ try {
+ if ($this->isFinder && !isset($this->loaded[$class])) {
+ $this->loaded[$class] = true;
+ if (!$file = $this->classLoader[0]->findFile($class) ?: false) {
+ // no-op
+ } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) {
+ include $file;
+
+ return;
+ } elseif (false === include $file) {
+ return;
+ }
+ } else {
+ ($this->classLoader)($class);
+ $file = false;
+ }
+ } finally {
+ error_reporting($e);
+ }
+
+ $this->checkClass($class, $file);
+ }
+
+ private function checkClass($class, $file = null)
+ {
+ $exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
+
+ if (null !== $file && $class && '\\' === $class[0]) {
+ $class = substr($class, 1);
+ }
+
+ if ($exists) {
+ if (isset(self::$checkedClasses[$class])) {
+ return;
+ }
+ self::$checkedClasses[$class] = true;
+
+ $refl = new \ReflectionClass($class);
+ if (null === $file && $refl->isInternal()) {
+ return;
+ }
+ $name = $refl->getName();
+
+ if ($name !== $class && 0 === strcasecmp($name, $class)) {
+ throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
+ }
+
+ $deprecations = $this->checkAnnotations($refl, $name);
+
+ foreach ($deprecations as $message) {
+ @trigger_error($message, E_USER_DEPRECATED);
+ }
+ }
+
+ if (!$file) {
+ return;
+ }
+
+ if (!$exists) {
+ if (false !== strpos($class, '/')) {
+ throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
+ }
+
+ throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
+ }
+
+ if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) {
+ throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2]));
+ }
+ }
+
+ public function checkAnnotations(\ReflectionClass $refl, $class)
+ {
+ $deprecations = [];
+
+ // Don't trigger deprecations for classes in the same vendor
+ if (2 > $len = 1 + (strpos($class, '\\') ?: strpos($class, '_'))) {
+ $len = 0;
+ $ns = '';
+ } else {
+ $ns = str_replace('_', '\\', substr($class, 0, $len));
+ }
+
+ // Detect annotations on the class
+ if (false !== $doc = $refl->getDocComment()) {
+ foreach (['final', 'deprecated', 'internal'] as $annotation) {
+ if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
+ self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
+ }
+ }
+
+ if ($refl->isInterface() && false !== strpos($doc, 'method') && preg_match_all('#\n \* @method\s+(static\s+)?+(?:[\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\)]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#', $doc, $notice, PREG_SET_ORDER)) {
+ foreach ($notice as $method) {
+ $static = '' !== $method[1];
+ $name = $method[2];
+ $description = $method[3] ?? null;
+ if (false === strpos($name, '(')) {
+ $name .= '()';
+ }
+ if (null !== $description) {
+ $description = trim($description);
+ if (!isset($method[4])) {
+ $description .= '.';
+ }
+ }
+ self::$method[$class][] = [$class, $name, $static, $description];
+ }
+ }
+ }
+
+ $parent = get_parent_class($class);
+ $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent);
+ if ($parent) {
+ $parentAndOwnInterfaces[$parent] = $parent;
+
+ if (!isset(self::$checkedClasses[$parent])) {
+ $this->checkClass($parent);
+ }
+
+ if (isset(self::$final[$parent])) {
+ $deprecations[] = sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $class);
+ }
+ }
+
+ // Detect if the parent is annotated
+ foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) {
+ if (!isset(self::$checkedClasses[$use])) {
+ $this->checkClass($use);
+ }
+ if (isset(self::$deprecated[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len) && !isset(self::$deprecated[$class])) {
+ $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
+ $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
+
+ $deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]);
+ }
+ if (isset(self::$internal[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len)) {
+ $deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class);
+ }
+ if (isset(self::$method[$use])) {
+ if ($refl->isAbstract()) {
+ if (isset(self::$method[$class])) {
+ self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]);
+ } else {
+ self::$method[$class] = self::$method[$use];
+ }
+ } elseif (!$refl->isInterface()) {
+ $hasCall = $refl->hasMethod('__call');
+ $hasStaticCall = $refl->hasMethod('__callStatic');
+ foreach (self::$method[$use] as $method) {
+ list($interface, $name, $static, $description) = $method;
+ if ($static ? $hasStaticCall : $hasCall) {
+ continue;
+ }
+ $realName = substr($name, 0, strpos($name, '('));
+ if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) {
+ $deprecations[] = sprintf('Class "%s" should implement method "%s::%s"%s', $class, ($static ? 'static ' : '').$interface, $name, null == $description ? '.' : ': '.$description);
+ }
+ }
+ }
+ }
+ }
+
+ if (trait_exists($class)) {
+ return $deprecations;
+ }
+
+ // Inherit @final, @internal and @param annotations for methods
+ self::$finalMethods[$class] = [];
+ self::$internalMethods[$class] = [];
+ self::$annotatedParameters[$class] = [];
+ foreach ($parentAndOwnInterfaces as $use) {
+ foreach (['finalMethods', 'internalMethods', 'annotatedParameters'] as $property) {
+ if (isset(self::${$property}[$use])) {
+ self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
+ }
+ }
+ }
+
+ foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
+ if ($method->class !== $class) {
+ continue;
+ }
+
+ if ($parent && isset(self::$finalMethods[$parent][$method->name])) {
+ list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
+ $deprecations[] = sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class);
+ }
+
+ if (isset(self::$internalMethods[$class][$method->name])) {
+ list($declaringClass, $message) = self::$internalMethods[$class][$method->name];
+ if (strncmp($ns, $declaringClass, $len)) {
+ $deprecations[] = sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class);
+ }
+ }
+
+ // To read method annotations
+ $doc = $method->getDocComment();
+
+ if (isset(self::$annotatedParameters[$class][$method->name])) {
+ $definedParameters = [];
+ foreach ($method->getParameters() as $parameter) {
+ $definedParameters[$parameter->name] = true;
+ }
+
+ foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) {
+ if (!isset($definedParameters[$parameterName]) && !($doc && preg_match("/\\n\\s+\\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\\\${$parameterName}\\b/", $doc))) {
+ $deprecations[] = sprintf($deprecation, $class);
+ }
+ }
+ }
+
+ if (!$doc) {
+ continue;
+ }
+
+ $finalOrInternal = false;
+
+ foreach (['final', 'internal'] as $annotation) {
+ if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
+ $message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
+ self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message];
+ $finalOrInternal = true;
+ }
+ }
+
+ if ($finalOrInternal || $method->isConstructor() || false === strpos($doc, '@param') || StatelessInvocation::class === $class) {
+ continue;
+ }
+ if (!preg_match_all('#\n\s+\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\$([a-zA-Z0-9_\x7f-\xff]++)#', $doc, $matches, PREG_SET_ORDER)) {
+ continue;
+ }
+ if (!isset(self::$annotatedParameters[$class][$method->name])) {
+ $definedParameters = [];
+ foreach ($method->getParameters() as $parameter) {
+ $definedParameters[$parameter->name] = true;
+ }
+ }
+ foreach ($matches as list(, $parameterType, $parameterName)) {
+ if (!isset($definedParameters[$parameterName])) {
+ $parameterType = trim($parameterType);
+ self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its parent class "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, $method->class);
+ }
+ }
+ }
+
+ return $deprecations;
+ }
+
+ public function checkCase(\ReflectionClass $refl, $file, $class)
+ {
+ $real = explode('\\', $class.strrchr($file, '.'));
+ $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file));
+
+ $i = \count($tail) - 1;
+ $j = \count($real) - 1;
+
+ while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
+ --$i;
+ --$j;
+ }
+
+ array_splice($tail, 0, $i + 1);
+
+ if (!$tail) {
+ return;
+ }
+
+ $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail);
+ $tailLen = \strlen($tail);
+ $real = $refl->getFileName();
+
+ if (2 === self::$caseCheck) {
+ $real = $this->darwinRealpath($real);
+ }
+
+ if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
+ && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
+ ) {
+ return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)];
+ }
+ }
+
+ /**
+ * `realpath` on MacOSX doesn't normalize the case of characters.
+ */
+ private function darwinRealpath($real)
+ {
+ $i = 1 + strrpos($real, '/');
+ $file = substr($real, $i);
+ $real = substr($real, 0, $i);
+
+ if (isset(self::$darwinCache[$real])) {
+ $kDir = $real;
+ } else {
+ $kDir = strtolower($real);
+
+ if (isset(self::$darwinCache[$kDir])) {
+ $real = self::$darwinCache[$kDir][0];
+ } else {
+ $dir = getcwd();
+ chdir($real);
+ $real = getcwd().'/';
+ chdir($dir);
+
+ $dir = $real;
+ $k = $kDir;
+ $i = \strlen($dir) - 1;
+ while (!isset(self::$darwinCache[$k])) {
+ self::$darwinCache[$k] = [$dir, []];
+ self::$darwinCache[$dir] = &self::$darwinCache[$k];
+
+ while ('/' !== $dir[--$i]) {
+ }
+ $k = substr($k, 0, ++$i);
+ $dir = substr($dir, 0, $i--);
+ }
+ }
+ }
+
+ $dirFiles = self::$darwinCache[$kDir][1];
+
+ if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) {
+ // Get the file name from "file_name.php(123) : eval()'d code"
+ $file = substr($file, 0, strrpos($file, '(', -17));
+ }
+
+ if (isset($dirFiles[$file])) {
+ return $real .= $dirFiles[$file];
+ }
+
+ $kFile = strtolower($file);
+
+ if (!isset($dirFiles[$kFile])) {
+ foreach (scandir($real, 2) as $f) {
+ if ('.' !== $f[0]) {
+ $dirFiles[$f] = $f;
+ if ($f === $file) {
+ $kFile = $k = $file;
+ } elseif ($f !== $k = strtolower($f)) {
+ $dirFiles[$k] = $f;
+ }
+ }
+ }
+ self::$darwinCache[$kDir][1] = $dirFiles;
+ }
+
+ return $real .= $dirFiles[$kFile];
+ }
+
+ /**
+ * `class_implements` includes interfaces from the parents so we have to manually exclude them.
+ *
+ * @param string $class
+ * @param string|false $parent
+ *
+ * @return string[]
+ */
+ private function getOwnInterfaces($class, $parent)
+ {
+ $ownInterfaces = class_implements($class, false);
+
+ if ($parent) {
+ foreach (class_implements($parent, false) as $interface) {
+ unset($ownInterfaces[$interface]);
+ }
+ }
+
+ foreach ($ownInterfaces as $interface) {
+ foreach (class_implements($interface) as $interface) {
+ unset($ownInterfaces[$interface]);
+ }
+ }
+
+ return $ownInterfaces;
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php
new file mode 100644
index 0000000000000..2a2a8ca384bab
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php
@@ -0,0 +1,715 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler;
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
+use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
+use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException;
+use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
+use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
+use Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface;
+use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
+use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
+use Symfony\Component\ErrorRenderer\Exception\FlattenException;
+
+/**
+ * A generic ErrorHandler for the PHP engine.
+ *
+ * Provides five bit fields that control how errors are handled:
+ * - thrownErrors: errors thrown as \ErrorException
+ * - loggedErrors: logged errors, when not @-silenced
+ * - scopedErrors: errors thrown or logged with their local context
+ * - tracedErrors: errors logged with their stack trace
+ * - screamedErrors: never @-silenced errors
+ *
+ * Each error level can be logged by a dedicated PSR-3 logger object.
+ * Screaming only applies to logging.
+ * Throwing takes precedence over logging.
+ * Uncaught exceptions are logged as E_ERROR.
+ * E_DEPRECATED and E_USER_DEPRECATED levels never throw.
+ * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
+ * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
+ * As errors have a performance cost, repeated errors are all logged, so that the developer
+ * can see them and weight them as more important to fix than others of the same level.
+ *
+ * @author Nicolas Grekas
+ * @author Grégoire Pineau
+ *
+ * @final since Symfony 4.3
+ */
+class ErrorHandler
+{
+ private $levels = [
+ E_DEPRECATED => 'Deprecated',
+ E_USER_DEPRECATED => 'User Deprecated',
+ E_NOTICE => 'Notice',
+ E_USER_NOTICE => 'User Notice',
+ E_STRICT => 'Runtime Notice',
+ E_WARNING => 'Warning',
+ E_USER_WARNING => 'User Warning',
+ E_COMPILE_WARNING => 'Compile Warning',
+ E_CORE_WARNING => 'Core Warning',
+ E_USER_ERROR => 'User Error',
+ E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
+ E_COMPILE_ERROR => 'Compile Error',
+ E_PARSE => 'Parse Error',
+ E_ERROR => 'Error',
+ E_CORE_ERROR => 'Core Error',
+ ];
+
+ private $loggers = [
+ E_DEPRECATED => [null, LogLevel::INFO],
+ E_USER_DEPRECATED => [null, LogLevel::INFO],
+ E_NOTICE => [null, LogLevel::WARNING],
+ E_USER_NOTICE => [null, LogLevel::WARNING],
+ E_STRICT => [null, LogLevel::WARNING],
+ E_WARNING => [null, LogLevel::WARNING],
+ E_USER_WARNING => [null, LogLevel::WARNING],
+ E_COMPILE_WARNING => [null, LogLevel::WARNING],
+ E_CORE_WARNING => [null, LogLevel::WARNING],
+ E_USER_ERROR => [null, LogLevel::CRITICAL],
+ E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
+ E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
+ E_PARSE => [null, LogLevel::CRITICAL],
+ E_ERROR => [null, LogLevel::CRITICAL],
+ E_CORE_ERROR => [null, LogLevel::CRITICAL],
+ ];
+
+ private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
+ private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
+ private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
+ private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
+ private $loggedErrors = 0;
+ private $traceReflector;
+
+ private $isRecursive = 0;
+ private $isRoot = false;
+ private $exceptionHandler;
+ private $bootstrappingLogger;
+
+ private static $reservedMemory;
+ private static $toStringException = null;
+ private static $silencedErrorCache = [];
+ private static $silencedErrorCount = 0;
+ private static $exitCode = 0;
+
+ /**
+ * Registers the error handler.
+ *
+ * @param self|null $handler The handler to register
+ * @param bool $replace Whether to replace or not any existing handler
+ *
+ * @return self The registered error handler
+ */
+ public static function register(self $handler = null, $replace = true)
+ {
+ if (null === self::$reservedMemory) {
+ self::$reservedMemory = str_repeat('x', 10240);
+ register_shutdown_function(__CLASS__.'::handleFatalError');
+ }
+
+ if ($handlerIsNew = null === $handler) {
+ $handler = new static();
+ }
+
+ if (null === $prev = set_error_handler([$handler, 'handleError'])) {
+ restore_error_handler();
+ // Specifying the error types earlier would expose us to https://bugs.php.net/63206
+ set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors);
+ $handler->isRoot = true;
+ }
+
+ if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) {
+ $handler = $prev[0];
+ $replace = false;
+ }
+ if (!$replace && $prev) {
+ restore_error_handler();
+ $handlerIsRegistered = \is_array($prev) && $handler === $prev[0];
+ } else {
+ $handlerIsRegistered = true;
+ }
+ if (\is_array($prev = set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) {
+ restore_exception_handler();
+ if (!$handlerIsRegistered) {
+ $handler = $prev[0];
+ } elseif ($handler !== $prev[0] && $replace) {
+ set_exception_handler([$handler, 'handleException']);
+ $p = $prev[0]->setExceptionHandler(null);
+ $handler->setExceptionHandler($p);
+ $prev[0]->setExceptionHandler($p);
+ }
+ } else {
+ $handler->setExceptionHandler($prev);
+ }
+
+ $handler->throwAt(E_ALL & $handler->thrownErrors, true);
+
+ return $handler;
+ }
+
+ public function __construct(BufferingLogger $bootstrappingLogger = null)
+ {
+ if ($bootstrappingLogger) {
+ $this->bootstrappingLogger = $bootstrappingLogger;
+ $this->setDefaultLogger($bootstrappingLogger);
+ }
+ $this->traceReflector = new \ReflectionProperty('Exception', 'trace');
+ $this->traceReflector->setAccessible(true);
+ }
+
+ /**
+ * Sets a logger to non assigned errors levels.
+ *
+ * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels
+ * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
+ * @param bool $replace Whether to replace or not any existing logger
+ */
+ public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false)
+ {
+ $loggers = [];
+
+ if (\is_array($levels)) {
+ foreach ($levels as $type => $logLevel) {
+ if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
+ $loggers[$type] = [$logger, $logLevel];
+ }
+ }
+ } else {
+ if (null === $levels) {
+ $levels = E_ALL;
+ }
+ foreach ($this->loggers as $type => $log) {
+ if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
+ $log[0] = $logger;
+ $loggers[$type] = $log;
+ }
+ }
+ }
+
+ $this->setLoggers($loggers);
+ }
+
+ /**
+ * Sets a logger for each error level.
+ *
+ * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
+ *
+ * @return array The previous map
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setLoggers(array $loggers)
+ {
+ $prevLogged = $this->loggedErrors;
+ $prev = $this->loggers;
+ $flush = [];
+
+ foreach ($loggers as $type => $log) {
+ if (!isset($prev[$type])) {
+ throw new \InvalidArgumentException('Unknown error type: '.$type);
+ }
+ if (!\is_array($log)) {
+ $log = [$log];
+ } elseif (!\array_key_exists(0, $log)) {
+ throw new \InvalidArgumentException('No logger provided');
+ }
+ if (null === $log[0]) {
+ $this->loggedErrors &= ~$type;
+ } elseif ($log[0] instanceof LoggerInterface) {
+ $this->loggedErrors |= $type;
+ } else {
+ throw new \InvalidArgumentException('Invalid logger provided');
+ }
+ $this->loggers[$type] = $log + $prev[$type];
+
+ if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) {
+ $flush[$type] = $type;
+ }
+ }
+ $this->reRegister($prevLogged | $this->thrownErrors);
+
+ if ($flush) {
+ foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
+ $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR;
+ if (!isset($flush[$type])) {
+ $this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
+ } elseif ($this->loggers[$type][0]) {
+ $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
+ }
+ }
+ }
+
+ return $prev;
+ }
+
+ /**
+ * Sets a user exception handler.
+ *
+ * @param callable $handler A handler that will be called on Exception
+ *
+ * @return callable|null The previous exception handler
+ */
+ public function setExceptionHandler(callable $handler = null)
+ {
+ $prev = $this->exceptionHandler;
+ $this->exceptionHandler = $handler;
+
+ return $prev;
+ }
+
+ /**
+ * Sets the PHP error levels that throw an exception when a PHP error occurs.
+ *
+ * @param int $levels A bit field of E_* constants for thrown errors
+ * @param bool $replace Replace or amend the previous value
+ *
+ * @return int The previous value
+ */
+ public function throwAt($levels, $replace = false)
+ {
+ $prev = $this->thrownErrors;
+ $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
+ if (!$replace) {
+ $this->thrownErrors |= $prev;
+ }
+ $this->reRegister($prev | $this->loggedErrors);
+
+ return $prev;
+ }
+
+ /**
+ * Sets the PHP error levels for which local variables are preserved.
+ *
+ * @param int $levels A bit field of E_* constants for scoped errors
+ * @param bool $replace Replace or amend the previous value
+ *
+ * @return int The previous value
+ */
+ public function scopeAt($levels, $replace = false)
+ {
+ $prev = $this->scopedErrors;
+ $this->scopedErrors = (int) $levels;
+ if (!$replace) {
+ $this->scopedErrors |= $prev;
+ }
+
+ return $prev;
+ }
+
+ /**
+ * Sets the PHP error levels for which the stack trace is preserved.
+ *
+ * @param int $levels A bit field of E_* constants for traced errors
+ * @param bool $replace Replace or amend the previous value
+ *
+ * @return int The previous value
+ */
+ public function traceAt($levels, $replace = false)
+ {
+ $prev = $this->tracedErrors;
+ $this->tracedErrors = (int) $levels;
+ if (!$replace) {
+ $this->tracedErrors |= $prev;
+ }
+
+ return $prev;
+ }
+
+ /**
+ * Sets the error levels where the @-operator is ignored.
+ *
+ * @param int $levels A bit field of E_* constants for screamed errors
+ * @param bool $replace Replace or amend the previous value
+ *
+ * @return int The previous value
+ */
+ public function screamAt($levels, $replace = false)
+ {
+ $prev = $this->screamedErrors;
+ $this->screamedErrors = (int) $levels;
+ if (!$replace) {
+ $this->screamedErrors |= $prev;
+ }
+
+ return $prev;
+ }
+
+ /**
+ * Re-registers as a PHP error handler if levels changed.
+ */
+ private function reRegister($prev)
+ {
+ if ($prev !== $this->thrownErrors | $this->loggedErrors) {
+ $handler = set_error_handler('var_dump');
+ $handler = \is_array($handler) ? $handler[0] : null;
+ restore_error_handler();
+ if ($handler === $this) {
+ restore_error_handler();
+ if ($this->isRoot) {
+ set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors);
+ } else {
+ set_error_handler([$this, 'handleError']);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles errors by filtering then logging them according to the configured bit fields.
+ *
+ * @param int $type One of the E_* constants
+ * @param string $message
+ * @param string $file
+ * @param int $line
+ *
+ * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
+ *
+ * @throws \ErrorException When $this->thrownErrors requests so
+ *
+ * @internal
+ */
+ public function handleError($type, $message, $file, $line)
+ {
+ // @deprecated to be removed in Symfony 5.0
+ if (\PHP_VERSION_ID >= 70300 && $message && '"' === $message[0] && 0 === strpos($message, '"continue') && preg_match('/^"continue(?: \d++)?" targeting switch is equivalent to "break(?: \d++)?"\. Did you mean to use "continue(?: \d++)?"\?$/', $message)) {
+ $type = E_DEPRECATED;
+ }
+
+ // Level is the current error reporting level to manage silent error.
+ $level = error_reporting();
+ $silenced = 0 === ($level & $type);
+ // Strong errors are not authorized to be silenced.
+ $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
+ $log = $this->loggedErrors & $type;
+ $throw = $this->thrownErrors & $type & $level;
+ $type &= $level | $this->screamedErrors;
+
+ if (!$type || (!$log && !$throw)) {
+ return !$silenced && $type && $log;
+ }
+ $scope = $this->scopedErrors & $type;
+
+ if (4 < $numArgs = \func_num_args()) {
+ $context = $scope ? (func_get_arg(4) ?: []) : [];
+ } else {
+ $context = [];
+ }
+
+ if (isset($context['GLOBALS']) && $scope) {
+ $e = $context; // Whatever the signature of the method,
+ unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
+ $context = $e;
+ }
+
+ if (false !== strpos($message, "class@anonymous\0")) {
+ $logMessage = $this->levels[$type].': '.(new FlattenException())->setMessage($message)->getMessage();
+ } else {
+ $logMessage = $this->levels[$type].': '.$message;
+ }
+
+ if (null !== self::$toStringException) {
+ $errorAsException = self::$toStringException;
+ self::$toStringException = null;
+ } elseif (!$throw && !($type & $level)) {
+ if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) {
+ $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, false) : [];
+ $errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? [$lightTrace[0]] : $lightTrace);
+ } elseif (isset(self::$silencedErrorCache[$id][$message])) {
+ $lightTrace = null;
+ $errorAsException = self::$silencedErrorCache[$id][$message];
+ ++$errorAsException->count;
+ } else {
+ $lightTrace = [];
+ $errorAsException = null;
+ }
+
+ if (100 < ++self::$silencedErrorCount) {
+ self::$silencedErrorCache = $lightTrace = [];
+ self::$silencedErrorCount = 1;
+ }
+ if ($errorAsException) {
+ self::$silencedErrorCache[$id][$message] = $errorAsException;
+ }
+ if (null === $lightTrace) {
+ return;
+ }
+ } else {
+ $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
+
+ if ($throw || $this->tracedErrors & $type) {
+ $backtrace = $errorAsException->getTrace();
+ $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
+ $this->traceReflector->setValue($errorAsException, $lightTrace);
+ } else {
+ $this->traceReflector->setValue($errorAsException, []);
+ $backtrace = [];
+ }
+ }
+
+ if ($throw) {
+ if (E_USER_ERROR & $type) {
+ for ($i = 1; isset($backtrace[$i]); ++$i) {
+ if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
+ && '__toString' === $backtrace[$i]['function']
+ && '->' === $backtrace[$i]['type']
+ && !isset($backtrace[$i - 1]['class'])
+ && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
+ ) {
+ // Here, we know trigger_error() has been called from __toString().
+ // PHP triggers a fatal error when throwing from __toString().
+ // A small convention allows working around the limitation:
+ // given a caught $e exception in __toString(), quitting the method with
+ // `return trigger_error($e, E_USER_ERROR);` allows this error handler
+ // to make $e get through the __toString() barrier.
+
+ foreach ($context as $e) {
+ if ($e instanceof \Throwable && $e->__toString() === $message) {
+ self::$toStringException = $e;
+
+ return true;
+ }
+ }
+
+ // Display the original error message instead of the default one.
+ $this->handleException($errorAsException);
+
+ // Stop the process by giving back the error to the native handler.
+ return false;
+ }
+ }
+ }
+
+ throw $errorAsException;
+ }
+
+ if ($this->isRecursive) {
+ $log = 0;
+ } else {
+ if (!\defined('HHVM_VERSION')) {
+ $currentErrorHandler = set_error_handler('var_dump');
+ restore_error_handler();
+ }
+
+ try {
+ $this->isRecursive = true;
+ $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG;
+ $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []);
+ } finally {
+ $this->isRecursive = false;
+
+ if (!\defined('HHVM_VERSION')) {
+ set_error_handler($currentErrorHandler);
+ }
+ }
+ }
+
+ return !$silenced && $type && $log;
+ }
+
+ /**
+ * Handles an exception by logging then forwarding it to another handler.
+ *
+ * @param \Exception|\Throwable $exception An exception to handle
+ * @param array $error An array as returned by error_get_last()
+ *
+ * @internal
+ */
+ public function handleException($exception, array $error = null)
+ {
+ if (null === $error) {
+ self::$exitCode = 255;
+ }
+ if (!$exception instanceof \Exception) {
+ $exception = new FatalThrowableError($exception);
+ }
+ $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
+ $handlerException = null;
+
+ if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
+ if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) {
+ $message = (new FlattenException())->setMessage($message)->getMessage();
+ }
+ if ($exception instanceof FatalErrorException) {
+ if ($exception instanceof FatalThrowableError) {
+ $error = [
+ 'type' => $type,
+ 'message' => $message,
+ 'file' => $exception->getFile(),
+ 'line' => $exception->getLine(),
+ ];
+ } else {
+ $message = 'Fatal '.$message;
+ }
+ } elseif ($exception instanceof \ErrorException) {
+ $message = 'Uncaught '.$message;
+ } else {
+ $message = 'Uncaught Exception: '.$message;
+ }
+ }
+ if ($this->loggedErrors & $type) {
+ try {
+ $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]);
+ } catch (\Throwable $handlerException) {
+ }
+ }
+ if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
+ foreach ($this->getFatalErrorHandlers() as $handler) {
+ if ($e = $handler->handleError($error, $exception)) {
+ $exception = $e;
+ break;
+ }
+ }
+ }
+ $exceptionHandler = $this->exceptionHandler;
+ $this->exceptionHandler = null;
+ try {
+ if (null !== $exceptionHandler) {
+ return $exceptionHandler($exception);
+ }
+ $handlerException = $handlerException ?: $exception;
+ } catch (\Throwable $handlerException) {
+ }
+ if ($exception === $handlerException) {
+ self::$reservedMemory = null; // Disable the fatal error handler
+ throw $exception; // Give back $exception to the native handler
+ }
+ $this->handleException($handlerException);
+ }
+
+ /**
+ * Shutdown registered function for handling PHP fatal errors.
+ *
+ * @param array $error An array as returned by error_get_last()
+ *
+ * @internal
+ */
+ public static function handleFatalError(array $error = null)
+ {
+ if (null === self::$reservedMemory) {
+ return;
+ }
+
+ $handler = self::$reservedMemory = null;
+ $handlers = [];
+ $previousHandler = null;
+ $sameHandlerLimit = 10;
+
+ while (!\is_array($handler) || !$handler[0] instanceof self) {
+ $handler = set_exception_handler('var_dump');
+ restore_exception_handler();
+
+ if (!$handler) {
+ break;
+ }
+ restore_exception_handler();
+
+ if ($handler !== $previousHandler) {
+ array_unshift($handlers, $handler);
+ $previousHandler = $handler;
+ } elseif (0 === --$sameHandlerLimit) {
+ $handler = null;
+ break;
+ }
+ }
+ foreach ($handlers as $h) {
+ set_exception_handler($h);
+ }
+ if (!$handler) {
+ return;
+ }
+ if ($handler !== $h) {
+ $handler[0]->setExceptionHandler($h);
+ }
+ $handler = $handler[0];
+ $handlers = [];
+
+ if ($exit = null === $error) {
+ $error = error_get_last();
+ }
+
+ if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
+ // Let's not throw anymore but keep logging
+ $handler->throwAt(0, true);
+ $trace = isset($error['backtrace']) ? $error['backtrace'] : null;
+
+ if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
+ $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
+ } else {
+ $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
+ }
+ } else {
+ $exception = null;
+ }
+
+ try {
+ if (null !== $exception) {
+ self::$exitCode = 255;
+ $handler->handleException($exception, $error);
+ }
+ } catch (FatalErrorException $e) {
+ // Ignore this re-throw
+ }
+
+ if ($exit && self::$exitCode) {
+ $exitCode = self::$exitCode;
+ register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
+ }
+ }
+
+ /**
+ * Gets the fatal error handlers.
+ *
+ * Override this method if you want to define more fatal error handlers.
+ *
+ * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
+ */
+ protected function getFatalErrorHandlers()
+ {
+ return [
+ new UndefinedFunctionFatalErrorHandler(),
+ new UndefinedMethodFatalErrorHandler(),
+ new ClassNotFoundFatalErrorHandler(),
+ ];
+ }
+
+ /**
+ * Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader.
+ */
+ private function cleanTrace($backtrace, $type, $file, $line, $throw)
+ {
+ $lightTrace = $backtrace;
+
+ for ($i = 0; isset($backtrace[$i]); ++$i) {
+ if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) {
+ $lightTrace = \array_slice($lightTrace, 1 + $i);
+ break;
+ }
+ }
+ if (class_exists(DebugClassLoader::class, false)) {
+ for ($i = \count($lightTrace) - 2; 0 < $i; --$i) {
+ if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) {
+ array_splice($lightTrace, --$i, 2);
+ }
+ }
+ }
+ if (!($throw || $this->scopedErrors & $type)) {
+ for ($i = 0; isset($lightTrace[$i]); ++$i) {
+ unset($lightTrace[$i]['args'], $lightTrace[$i]['object']);
+ }
+ }
+
+ return $lightTrace;
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Exception/ClassNotFoundException.php b/src/Symfony/Component/ErrorHandler/Exception/ClassNotFoundException.php
new file mode 100644
index 0000000000000..b0638826d6414
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Exception/ClassNotFoundException.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Exception;
+
+/**
+ * Class (or Trait or Interface) Not Found Exception.
+ *
+ * @author Konstanton Myakshin
+ */
+class ClassNotFoundException extends FatalErrorException
+{
+ public function __construct(string $message, \ErrorException $previous)
+ {
+ parent::__construct(
+ $message,
+ $previous->getCode(),
+ $previous->getSeverity(),
+ $previous->getFile(),
+ $previous->getLine(),
+ null,
+ true,
+ null,
+ $previous->getPrevious()
+ );
+ $this->setTrace($previous->getTrace());
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Exception/FatalErrorException.php b/src/Symfony/Component/ErrorHandler/Exception/FatalErrorException.php
new file mode 100644
index 0000000000000..4269356d5724d
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Exception/FatalErrorException.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Exception;
+
+/**
+ * Fatal Error Exception.
+ *
+ * @author Konstanton Myakshin
+ */
+class FatalErrorException extends \ErrorException
+{
+ public function __construct(string $message, int $code, int $severity, string $filename, int $lineno, int $traceOffset = null, bool $traceArgs = true, array $trace = null, \Throwable $previous = null)
+ {
+ parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
+
+ if (null !== $trace) {
+ if (!$traceArgs) {
+ foreach ($trace as &$frame) {
+ unset($frame['args'], $frame['this'], $frame);
+ }
+ }
+
+ $this->setTrace($trace);
+ } elseif (null !== $traceOffset) {
+ if (\function_exists('xdebug_get_function_stack')) {
+ $trace = xdebug_get_function_stack();
+ if (0 < $traceOffset) {
+ array_splice($trace, -$traceOffset);
+ }
+
+ foreach ($trace as &$frame) {
+ if (!isset($frame['type'])) {
+ // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695
+ if (isset($frame['class'])) {
+ $frame['type'] = '::';
+ }
+ } elseif ('dynamic' === $frame['type']) {
+ $frame['type'] = '->';
+ } elseif ('static' === $frame['type']) {
+ $frame['type'] = '::';
+ }
+
+ // XDebug also has a different name for the parameters array
+ if (!$traceArgs) {
+ unset($frame['params'], $frame['args']);
+ } elseif (isset($frame['params']) && !isset($frame['args'])) {
+ $frame['args'] = $frame['params'];
+ unset($frame['params']);
+ }
+ }
+
+ unset($frame);
+ $trace = array_reverse($trace);
+ } else {
+ $trace = [];
+ }
+
+ $this->setTrace($trace);
+ }
+ }
+
+ protected function setTrace($trace)
+ {
+ $traceReflector = new \ReflectionProperty('Exception', 'trace');
+ $traceReflector->setAccessible(true);
+ $traceReflector->setValue($this, $trace);
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Exception/FatalThrowableError.php b/src/Symfony/Component/ErrorHandler/Exception/FatalThrowableError.php
new file mode 100644
index 0000000000000..a690c835975f8
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Exception/FatalThrowableError.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Exception;
+
+/**
+ * Fatal Throwable Error.
+ *
+ * @author Nicolas Grekas
+ */
+class FatalThrowableError extends FatalErrorException
+{
+ private $originalClassName;
+
+ public function __construct(\Throwable $e)
+ {
+ $this->originalClassName = \get_class($e);
+
+ if ($e instanceof \ParseError) {
+ $severity = E_PARSE;
+ } elseif ($e instanceof \TypeError) {
+ $severity = E_RECOVERABLE_ERROR;
+ } else {
+ $severity = E_ERROR;
+ }
+
+ \ErrorException::__construct(
+ $e->getMessage(),
+ $e->getCode(),
+ $severity,
+ $e->getFile(),
+ $e->getLine(),
+ $e->getPrevious()
+ );
+
+ $this->setTrace($e->getTrace());
+ }
+
+ public function getOriginalClassName(): string
+ {
+ return $this->originalClassName;
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Exception/OutOfMemoryException.php b/src/Symfony/Component/ErrorHandler/Exception/OutOfMemoryException.php
new file mode 100644
index 0000000000000..18c367596f630
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Exception/OutOfMemoryException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Exception;
+
+/**
+ * Out of memory exception.
+ *
+ * @author Nicolas Grekas
+ */
+class OutOfMemoryException extends FatalErrorException
+{
+}
diff --git a/src/Symfony/Component/ErrorHandler/Exception/SilencedErrorContext.php b/src/Symfony/Component/ErrorHandler/Exception/SilencedErrorContext.php
new file mode 100644
index 0000000000000..2c4ae69db419d
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Exception/SilencedErrorContext.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Exception;
+
+/**
+ * Data Object that represents a Silenced Error.
+ *
+ * @author Grégoire Pineau
+ */
+class SilencedErrorContext implements \JsonSerializable
+{
+ public $count = 1;
+
+ private $severity;
+ private $file;
+ private $line;
+ private $trace;
+
+ public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1)
+ {
+ $this->severity = $severity;
+ $this->file = $file;
+ $this->line = $line;
+ $this->trace = $trace;
+ $this->count = $count;
+ }
+
+ public function getSeverity()
+ {
+ return $this->severity;
+ }
+
+ public function getFile()
+ {
+ return $this->file;
+ }
+
+ public function getLine()
+ {
+ return $this->line;
+ }
+
+ public function getTrace()
+ {
+ return $this->trace;
+ }
+
+ public function JsonSerialize()
+ {
+ return [
+ 'severity' => $this->severity,
+ 'file' => $this->file,
+ 'line' => $this->line,
+ 'trace' => $this->trace,
+ 'count' => $this->count,
+ ];
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Exception/UndefinedFunctionException.php b/src/Symfony/Component/ErrorHandler/Exception/UndefinedFunctionException.php
new file mode 100644
index 0000000000000..bb2f46564d4d7
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Exception/UndefinedFunctionException.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Exception;
+
+/**
+ * Undefined Function Exception.
+ *
+ * @author Konstanton Myakshin
+ */
+class UndefinedFunctionException extends FatalErrorException
+{
+ public function __construct(string $message, \ErrorException $previous)
+ {
+ parent::__construct(
+ $message,
+ $previous->getCode(),
+ $previous->getSeverity(),
+ $previous->getFile(),
+ $previous->getLine(),
+ null,
+ true,
+ null,
+ $previous->getPrevious()
+ );
+ $this->setTrace($previous->getTrace());
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Exception/UndefinedMethodException.php b/src/Symfony/Component/ErrorHandler/Exception/UndefinedMethodException.php
new file mode 100644
index 0000000000000..12efdc716c5dc
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Exception/UndefinedMethodException.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Exception;
+
+/**
+ * Undefined Method Exception.
+ *
+ * @author Grégoire Pineau
+ */
+class UndefinedMethodException extends FatalErrorException
+{
+ public function __construct(string $message, \ErrorException $previous)
+ {
+ parent::__construct(
+ $message,
+ $previous->getCode(),
+ $previous->getSeverity(),
+ $previous->getFile(),
+ $previous->getLine(),
+ null,
+ true,
+ null,
+ $previous->getPrevious()
+ );
+ $this->setTrace($previous->getTrace());
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/ExceptionHandler.php b/src/Symfony/Component/ErrorHandler/ExceptionHandler.php
new file mode 100644
index 0000000000000..577234a8d9b59
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/ExceptionHandler.php
@@ -0,0 +1,187 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler;
+
+use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException;
+use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
+use Symfony\Component\ErrorRenderer\Exception\FlattenException;
+use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
+
+/**
+ * ExceptionHandler converts an exception to a Response object.
+ *
+ * It is mostly useful in debug mode to replace the default PHP/XDebug
+ * output with something prettier and more useful.
+ *
+ * As this class is mainly used during Kernel boot, where nothing is yet
+ * available, the Response content is always HTML.
+ *
+ * @author Fabien Potencier
+ * @author Nicolas Grekas
+ *
+ * @final since Symfony 4.3
+ */
+class ExceptionHandler
+{
+ private $debug;
+ private $charset;
+ private $handler;
+ private $caughtBuffer;
+ private $caughtLength;
+ private $fileLinkFormat;
+ private $htmlErrorRenderer;
+
+ public function __construct(bool $debug = true, string $charset = null, $fileLinkFormat = null)
+ {
+ $this->debug = $debug;
+ $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
+ $this->fileLinkFormat = $fileLinkFormat;
+ }
+
+ /**
+ * Registers the exception handler.
+ *
+ * @param bool $debug Enable/disable debug mode, where the stack trace is displayed
+ * @param string|null $charset The charset used by exception messages
+ * @param string|null $fileLinkFormat The IDE link template
+ *
+ * @return static
+ */
+ public static function register($debug = true, $charset = null, $fileLinkFormat = null)
+ {
+ $handler = new static($debug, $charset, $fileLinkFormat);
+
+ $prev = set_exception_handler([$handler, 'handle']);
+ if (\is_array($prev) && $prev[0] instanceof ErrorHandler) {
+ restore_exception_handler();
+ $prev[0]->setExceptionHandler([$handler, 'handle']);
+ }
+
+ return $handler;
+ }
+
+ /**
+ * Sets a user exception handler.
+ *
+ * @param callable $handler An handler that will be called on Exception
+ *
+ * @return callable|null The previous exception handler if any
+ */
+ public function setHandler(callable $handler = null)
+ {
+ $old = $this->handler;
+ $this->handler = $handler;
+
+ return $old;
+ }
+
+ /**
+ * Sets the format for links to source files.
+ *
+ * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
+ *
+ * @return string The previous file link format
+ */
+ public function setFileLinkFormat($fileLinkFormat)
+ {
+ $old = $this->fileLinkFormat;
+ $this->fileLinkFormat = $fileLinkFormat;
+
+ return $old;
+ }
+
+ /**
+ * Sends a response for the given Exception.
+ *
+ * To be as fail-safe as possible, the exception is first handled
+ * by our simple exception handler, then by the user exception handler.
+ * The latter takes precedence and any output from the former is cancelled,
+ * if and only if nothing bad happens in this handling path.
+ */
+ public function handle(\Exception $exception)
+ {
+ if (null === $this->handler || $exception instanceof OutOfMemoryException) {
+ $this->sendPhpResponse($exception);
+
+ return;
+ }
+
+ $caughtLength = $this->caughtLength = 0;
+
+ ob_start(function ($buffer) {
+ $this->caughtBuffer = $buffer;
+
+ return '';
+ });
+
+ $this->sendPhpResponse($exception);
+ while (null === $this->caughtBuffer && ob_end_flush()) {
+ // Empty loop, everything is in the condition
+ }
+ if (isset($this->caughtBuffer[0])) {
+ ob_start(function ($buffer) {
+ if ($this->caughtLength) {
+ // use substr_replace() instead of substr() for mbstring overloading resistance
+ $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
+ if (isset($cleanBuffer[0])) {
+ $buffer = $cleanBuffer;
+ }
+ }
+
+ return $buffer;
+ });
+
+ echo $this->caughtBuffer;
+ $caughtLength = ob_get_length();
+ }
+ $this->caughtBuffer = null;
+
+ try {
+ ($this->handler)($exception);
+ $this->caughtLength = $caughtLength;
+ } catch (\Exception $e) {
+ if (!$caughtLength) {
+ // All handlers failed. Let PHP handle that now.
+ throw $exception;
+ }
+ }
+ }
+
+ /**
+ * Sends the error associated with the given Exception as a plain PHP response.
+ *
+ * This method uses plain PHP functions like header() and echo to output
+ * the response.
+ *
+ * @param \Throwable|FlattenException $exception A \Throwable or FlattenException instance
+ */
+ public function sendPhpResponse($exception)
+ {
+ if ($exception instanceof \Throwable) {
+ $exception = FlattenException::createFromThrowable($exception);
+ }
+
+ if (!headers_sent()) {
+ header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
+ foreach ($exception->getHeaders() as $name => $value) {
+ header($name.': '.$value, false);
+ }
+ header('Content-Type: text/html; charset='.$this->charset);
+ }
+
+ if (null === $this->htmlErrorRenderer) {
+ $this->htmlErrorRenderer = new HtmlErrorRenderer($this->debug, $this->charset, $this->fileLinkFormat);
+ }
+
+ echo $this->htmlErrorRenderer->render($exception);
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php b/src/Symfony/Component/ErrorHandler/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
new file mode 100644
index 0000000000000..b59b0d4517375
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php
@@ -0,0 +1,193 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
+
+use Composer\Autoload\ClassLoader as ComposerClassLoader;
+use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
+use Symfony\Component\ErrorHandler\DebugClassLoader;
+use Symfony\Component\ErrorHandler\Exception\ClassNotFoundException;
+use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
+
+/**
+ * ErrorHandler for classes that do not exist.
+ *
+ * @author Fabien Potencier
+ */
+class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function handleError(array $error, FatalErrorException $exception)
+ {
+ $messageLen = \strlen($error['message']);
+ $notFoundSuffix = '\' not found';
+ $notFoundSuffixLen = \strlen($notFoundSuffix);
+ if ($notFoundSuffixLen > $messageLen) {
+ return;
+ }
+
+ if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
+ return;
+ }
+
+ foreach (['class', 'interface', 'trait'] as $typeName) {
+ $prefix = ucfirst($typeName).' \'';
+ $prefixLen = \strlen($prefix);
+ if (0 !== strpos($error['message'], $prefix)) {
+ continue;
+ }
+
+ $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
+ if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
+ $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
+ $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
+ $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
+ $tail = ' for another namespace?';
+ } else {
+ $className = $fullyQualifiedClassName;
+ $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
+ $tail = '?';
+ }
+
+ if ($candidates = $this->getClassCandidates($className)) {
+ $tail = array_pop($candidates).'"?';
+ if ($candidates) {
+ $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
+ } else {
+ $tail = ' for "'.$tail;
+ }
+ }
+ $message .= "\nDid you forget a \"use\" statement".$tail;
+
+ return new ClassNotFoundException($message, $exception);
+ }
+ }
+
+ /**
+ * Tries to guess the full namespace for a given class name.
+ *
+ * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
+ * autoloader (that should cover all common cases).
+ *
+ * @param string $class A class name (without its namespace)
+ *
+ * @return array An array of possible fully qualified class names
+ */
+ private function getClassCandidates(string $class): array
+ {
+ if (!\is_array($functions = spl_autoload_functions())) {
+ return [];
+ }
+
+ // find Symfony and Composer autoloaders
+ $classes = [];
+
+ foreach ($functions as $function) {
+ if (!\is_array($function)) {
+ continue;
+ }
+ // get class loaders wrapped by DebugClassLoader
+ if ($function[0] instanceof DebugClassLoader) {
+ $function = $function[0]->getClassLoader();
+
+ if (!\is_array($function)) {
+ continue;
+ }
+ }
+
+ if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
+ foreach ($function[0]->getPrefixes() as $prefix => $paths) {
+ foreach ($paths as $path) {
+ $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
+ }
+ }
+ }
+ if ($function[0] instanceof ComposerClassLoader) {
+ foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
+ foreach ($paths as $path) {
+ $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
+ }
+ }
+ }
+ }
+
+ return array_unique($classes);
+ }
+
+ private function findClassInPath(string $path, string $class, string $prefix): array
+ {
+ if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
+ return [];
+ }
+
+ $classes = [];
+ $filename = $class.'.php';
+ foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
+ if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
+ $classes[] = $class;
+ }
+ }
+
+ return $classes;
+ }
+
+ private function convertFileToClass(string $path, string $file, string $prefix): ?string
+ {
+ $candidates = [
+ // namespaced class
+ $namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file),
+ // namespaced class (with target dir)
+ $prefix.$namespacedClass,
+ // namespaced class (with target dir and separator)
+ $prefix.'\\'.$namespacedClass,
+ // PEAR class
+ str_replace('\\', '_', $namespacedClass),
+ // PEAR class (with target dir)
+ str_replace('\\', '_', $prefix.$namespacedClass),
+ // PEAR class (with target dir and separator)
+ str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
+ ];
+
+ if ($prefix) {
+ $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
+ }
+
+ // We cannot use the autoloader here as most of them use require; but if the class
+ // is not found, the new autoloader call will require the file again leading to a
+ // "cannot redeclare class" error.
+ foreach ($candidates as $candidate) {
+ if ($this->classExists($candidate)) {
+ return $candidate;
+ }
+ }
+
+ try {
+ require_once $file;
+ } catch (\Throwable $e) {
+ return null;
+ }
+
+ foreach ($candidates as $candidate) {
+ if ($this->classExists($candidate)) {
+ return $candidate;
+ }
+ }
+
+ return null;
+ }
+
+ private function classExists(string $class): bool
+ {
+ return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/FatalErrorHandler/FatalErrorHandlerInterface.php b/src/Symfony/Component/ErrorHandler/FatalErrorHandler/FatalErrorHandlerInterface.php
new file mode 100644
index 0000000000000..afa8b7d2367d6
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/FatalErrorHandler/FatalErrorHandlerInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
+
+use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
+
+/**
+ * Attempts to convert fatal errors to exceptions.
+ *
+ * @author Fabien Potencier
+ */
+interface FatalErrorHandlerInterface
+{
+ /**
+ * Attempts to convert an error into an exception.
+ *
+ * @param array $error An array as returned by error_get_last()
+ * @param FatalErrorException $exception A FatalErrorException instance
+ *
+ * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise
+ */
+ public function handleError(array $error, FatalErrorException $exception);
+}
diff --git a/src/Symfony/Component/ErrorHandler/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php b/src/Symfony/Component/ErrorHandler/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
new file mode 100644
index 0000000000000..9e3affb14dbac
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
+
+use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
+use Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException;
+
+/**
+ * ErrorHandler for undefined functions.
+ *
+ * @author Fabien Potencier
+ */
+class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function handleError(array $error, FatalErrorException $exception)
+ {
+ $messageLen = \strlen($error['message']);
+ $notFoundSuffix = '()';
+ $notFoundSuffixLen = \strlen($notFoundSuffix);
+ if ($notFoundSuffixLen > $messageLen) {
+ return;
+ }
+
+ if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
+ return;
+ }
+
+ $prefix = 'Call to undefined function ';
+ $prefixLen = \strlen($prefix);
+ if (0 !== strpos($error['message'], $prefix)) {
+ return;
+ }
+
+ $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
+ if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
+ $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
+ $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
+ $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
+ } else {
+ $functionName = $fullyQualifiedFunctionName;
+ $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
+ }
+
+ $candidates = [];
+ foreach (get_defined_functions() as $type => $definedFunctionNames) {
+ foreach ($definedFunctionNames as $definedFunctionName) {
+ if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) {
+ $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1);
+ } else {
+ $definedFunctionNameBasename = $definedFunctionName;
+ }
+
+ if ($definedFunctionNameBasename === $functionName) {
+ $candidates[] = '\\'.$definedFunctionName;
+ }
+ }
+ }
+
+ if ($candidates) {
+ sort($candidates);
+ $last = array_pop($candidates).'"?';
+ if ($candidates) {
+ $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
+ } else {
+ $candidates = '"'.$last;
+ }
+ $message .= "\nDid you mean to call ".$candidates;
+ }
+
+ return new UndefinedFunctionException($message, $exception);
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php b/src/Symfony/Component/ErrorHandler/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
new file mode 100644
index 0000000000000..49de27446945a
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php
@@ -0,0 +1,66 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
+
+use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
+use Symfony\Component\ErrorHandler\Exception\UndefinedMethodException;
+
+/**
+ * ErrorHandler for undefined methods.
+ *
+ * @author Grégoire Pineau
+ */
+class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function handleError(array $error, FatalErrorException $exception)
+ {
+ preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
+ if (!$matches) {
+ return;
+ }
+
+ $className = $matches[1];
+ $methodName = $matches[2];
+
+ $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);
+
+ if (!class_exists($className) || null === $methods = get_class_methods($className)) {
+ // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class)
+ return new UndefinedMethodException($message, $exception);
+ }
+
+ $candidates = [];
+ foreach ($methods as $definedMethodName) {
+ $lev = levenshtein($methodName, $definedMethodName);
+ if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
+ $candidates[] = $definedMethodName;
+ }
+ }
+
+ if ($candidates) {
+ sort($candidates);
+ $last = array_pop($candidates).'"?';
+ if ($candidates) {
+ $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last;
+ } else {
+ $candidates = '"'.$last;
+ }
+
+ $message .= "\nDid you mean to call ".$candidates;
+ }
+
+ return new UndefinedMethodException($message, $exception);
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/LICENSE b/src/Symfony/Component/ErrorHandler/LICENSE
new file mode 100644
index 0000000000000..1a1869751d250
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/Symfony/Component/ErrorHandler/README.md b/src/Symfony/Component/ErrorHandler/README.md
new file mode 100644
index 0000000000000..17e1cfd751d06
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/README.md
@@ -0,0 +1,12 @@
+ErrorHandler Component
+======================
+
+The ErrorHandler component provides tools to manage errors and ease debugging PHP code.
+
+Resources
+---------
+
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php
new file mode 100644
index 0000000000000..2699daec18a22
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/DebugClassLoaderTest.php
@@ -0,0 +1,448 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ErrorHandler\DebugClassLoader;
+
+class DebugClassLoaderTest extends TestCase
+{
+ /**
+ * @var int Error reporting level before running tests
+ */
+ private $errorReporting;
+
+ private $loader;
+
+ protected function setUp()
+ {
+ $this->errorReporting = error_reporting(E_ALL);
+ $this->loader = new ClassLoader();
+ spl_autoload_register([$this->loader, 'loadClass'], true, true);
+ DebugClassLoader::enable();
+ }
+
+ protected function tearDown()
+ {
+ DebugClassLoader::disable();
+ spl_autoload_unregister([$this->loader, 'loadClass']);
+ error_reporting($this->errorReporting);
+ }
+
+ public function testIdempotence()
+ {
+ DebugClassLoader::enable();
+
+ $functions = spl_autoload_functions();
+ foreach ($functions as $function) {
+ if (\is_array($function) && $function[0] instanceof DebugClassLoader) {
+ $reflClass = new \ReflectionClass($function[0]);
+ $reflProp = $reflClass->getProperty('classLoader');
+ $reflProp->setAccessible(true);
+
+ $this->assertNotInstanceOf('Symfony\Component\ErrorHandler\DebugClassLoader', $reflProp->getValue($function[0]));
+
+ return;
+ }
+ }
+
+ $this->fail('DebugClassLoader did not register');
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage boo
+ */
+ public function testThrowingClass()
+ {
+ try {
+ class_exists(__NAMESPACE__.'\Fixtures\Throwing');
+ $this->fail('Exception expected');
+ } catch (\Exception $e) {
+ $this->assertSame('boo', $e->getMessage());
+ }
+
+ // the second call also should throw
+ class_exists(__NAMESPACE__.'\Fixtures\Throwing');
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testNameCaseMismatch()
+ {
+ class_exists(__NAMESPACE__.'\TestingCaseMismatch', true);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Case mismatch between class and real file names
+ */
+ public function testFileCaseMismatch()
+ {
+ if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) {
+ $this->markTestSkipped('Can only be run on case insensitive filesystems');
+ }
+
+ class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testPsr4CaseMismatch()
+ {
+ class_exists(__NAMESPACE__.'\Fixtures\Psr4CaseMismatch', true);
+ }
+
+ public function testNotPsr0()
+ {
+ $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0', true));
+ }
+
+ public function testNotPsr0Bis()
+ {
+ $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true));
+ }
+
+ public function testClassAlias()
+ {
+ $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true));
+ }
+
+ /**
+ * @dataProvider provideDeprecatedSuper
+ */
+ public function testDeprecatedSuper($class, $super, $type)
+ {
+ set_error_handler(function () { return false; });
+ $e = error_reporting(0);
+ trigger_error('', E_USER_DEPRECATED);
+
+ class_exists('Test\\'.__NAMESPACE__.'\\'.$class, true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $lastError = error_get_last();
+ unset($lastError['file'], $lastError['line']);
+
+ $xError = [
+ 'type' => E_USER_DEPRECATED,
+ 'message' => 'The "Test\Symfony\Component\ErrorHandler\Tests\\'.$class.'" class '.$type.' "Symfony\Component\ErrorHandler\Tests\Fixtures\\'.$super.'" that is deprecated but this is a test deprecation notice.',
+ ];
+
+ $this->assertSame($xError, $lastError);
+ }
+
+ public function provideDeprecatedSuper()
+ {
+ return [
+ ['DeprecatedInterfaceClass', 'DeprecatedInterface', 'implements'],
+ ['DeprecatedParentClass', 'DeprecatedClass', 'extends'],
+ ];
+ }
+
+ public function testInterfaceExtendsDeprecatedInterface()
+ {
+ set_error_handler(function () { return false; });
+ $e = error_reporting(0);
+ trigger_error('', E_USER_NOTICE);
+
+ class_exists('Test\\'.__NAMESPACE__.'\\NonDeprecatedInterfaceClass', true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $lastError = error_get_last();
+ unset($lastError['file'], $lastError['line']);
+
+ $xError = [
+ 'type' => E_USER_NOTICE,
+ 'message' => '',
+ ];
+
+ $this->assertSame($xError, $lastError);
+ }
+
+ public function testDeprecatedSuperInSameNamespace()
+ {
+ set_error_handler(function () { return false; });
+ $e = error_reporting(0);
+ trigger_error('', E_USER_NOTICE);
+
+ class_exists('Symfony\Bridge\ErrorHandler\Tests\Fixtures\ExtendsDeprecatedParent', true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $lastError = error_get_last();
+ unset($lastError['file'], $lastError['line']);
+
+ $xError = [
+ 'type' => E_USER_NOTICE,
+ 'message' => '',
+ ];
+
+ $this->assertSame($xError, $lastError);
+ }
+
+ public function testExtendedFinalClass()
+ {
+ $deprecations = [];
+ set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
+ $e = error_reporting(E_USER_DEPRECATED);
+
+ require __DIR__.'/Fixtures/FinalClasses.php';
+
+ $i = 1;
+ while (class_exists($finalClass = __NAMESPACE__.'\\Fixtures\\FinalClass'.$i++, false)) {
+ spl_autoload_call($finalClass);
+ class_exists('Test\\'.__NAMESPACE__.'\\Extends'.substr($finalClass, strrpos($finalClass, '\\') + 1), true);
+ }
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $this->assertSame([
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalClass1" class is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsFinalClass1".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalClass2" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsFinalClass2".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalClass3" class is considered final comment with @@@ and ***. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsFinalClass3".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalClass4" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsFinalClass4".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalClass5" class is considered final multiline comment. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsFinalClass5".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalClass6" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsFinalClass6".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalClass7" class is considered final another multiline comment... It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsFinalClass7".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalClass8" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsFinalClass8".',
+ ], $deprecations);
+ }
+
+ public function testExtendedFinalMethod()
+ {
+ $deprecations = [];
+ set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
+ $e = error_reporting(E_USER_DEPRECATED);
+
+ class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $xError = [
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\ErrorHandler\Tests\Fixtures\ExtendedFinalMethod".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalMethod::finalMethod2()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\ErrorHandler\Tests\Fixtures\ExtendedFinalMethod".',
+ ];
+
+ $this->assertSame($xError, $deprecations);
+ }
+
+ public function testExtendedDeprecatedMethodDoesntTriggerAnyNotice()
+ {
+ set_error_handler(function () { return false; });
+ $e = error_reporting(0);
+ trigger_error('', E_USER_NOTICE);
+
+ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsAnnotatedClass', true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $lastError = error_get_last();
+ unset($lastError['file'], $lastError['line']);
+
+ $this->assertSame(['type' => E_USER_NOTICE, 'message' => ''], $lastError);
+ }
+
+ public function testInternalsUse()
+ {
+ $deprecations = [];
+ set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
+ $e = error_reporting(E_USER_DEPRECATED);
+
+ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $this->assertSame($deprecations, [
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\InternalInterface" interface is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsInternalsParent".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\InternalClass" class is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsInternalsParent".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\InternalTrait" trait is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsInternals".',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\InternalClass::internalMethod()" method is considered internal. It may change without further notice. You should not extend it from "Test\Symfony\Component\ErrorHandler\Tests\ExtendsInternals".',
+ ]);
+ }
+
+ public function testExtendedMethodDefinesNewParameters()
+ {
+ $deprecations = [];
+ set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
+ $e = error_reporting(E_USER_DEPRECATED);
+
+ class_exists(__NAMESPACE__.'\\Fixtures\SubClassWithAnnotatedParameters', true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $this->assertSame([
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::quzMethod()" method will require a new "Quz $quz" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\ClassWithAnnotatedParameters", not defining it is deprecated.',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::whereAmI()" method will require a new "bool $matrix" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "$noType" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "callable(\Throwable|null $reason, mixed $value) $callback" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "string $param" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "callable ($a, $b) $anotherOne" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::iAmHere()" method will require a new "Type$WithDollarIsStillAType $ccc" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\InterfaceWithAnnotatedParameters", not defining it is deprecated.',
+ 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\SubClassWithAnnotatedParameters::isSymfony()" method will require a new "true $yes" argument in the next major version of its parent class "Symfony\Component\ErrorHandler\Tests\Fixtures\ClassWithAnnotatedParameters", not defining it is deprecated.',
+ ], $deprecations);
+ }
+
+ public function testUseTraitWithInternalMethod()
+ {
+ $deprecations = [];
+ set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
+ $e = error_reporting(E_USER_DEPRECATED);
+
+ class_exists('Test\\'.__NAMESPACE__.'\\UseTraitWithInternalMethod', true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $this->assertSame([], $deprecations);
+ }
+
+ public function testVirtualUse()
+ {
+ $deprecations = [];
+ set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
+ $e = error_reporting(E_USER_DEPRECATED);
+
+ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtual', true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $this->assertSame([
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::sameLineInterfaceMethodNoBraces()".',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::newLineInterfaceMethod()": Some description!',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::newLineInterfaceMethodNoBraces()": Description.',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::invalidInterfaceMethod()".',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::invalidInterfaceMethodNoBraces()".',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::complexInterfaceMethod($arg, ...$args)".',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::complexInterfaceMethodTyped($arg, int ...$args)": Description ...',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::staticMethodNoBraces()".',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::staticMethodTyped(int $arg)": Description.',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualInterface::staticMethodTypedNoBraces()".',
+ 'Class "Test\Symfony\Component\ErrorHandler\Tests\ExtendsVirtual" should implement method "Symfony\Component\ErrorHandler\Tests\Fixtures\VirtualSubInterface::subInterfaceMethod()".',
+ ], $deprecations);
+ }
+
+ public function testVirtualUseWithMagicCall()
+ {
+ $deprecations = [];
+ set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
+ $e = error_reporting(E_USER_DEPRECATED);
+
+ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtualMagicCall', true);
+
+ error_reporting($e);
+ restore_error_handler();
+
+ $this->assertSame([], $deprecations);
+ }
+
+ public function testEvaluatedCode()
+ {
+ $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\DefinitionInEvaluatedCode', true));
+ }
+}
+
+class ClassLoader
+{
+ public function loadClass($class)
+ {
+ }
+
+ public function getClassMap()
+ {
+ return [__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php'];
+ }
+
+ public function findFile($class)
+ {
+ $fixtureDir = __DIR__.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR;
+
+ if (__NAMESPACE__.'\TestingUnsilencing' === $class) {
+ eval('-- parse error --');
+ } elseif (__NAMESPACE__.'\TestingStacking' === $class) {
+ eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }');
+ } elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) {
+ eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}');
+ } elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) {
+ return $fixtureDir.'psr4'.\DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php';
+ } elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) {
+ return $fixtureDir.'reallyNotPsr0.php';
+ } elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) {
+ return $fixtureDir.'notPsr0Bis.php';
+ } elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) {
+ eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
+ } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedParentClass extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}');
+ } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedInterfaceClass' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\DeprecatedInterface {}');
+ } elseif ('Test\\'.__NAMESPACE__.'\NonDeprecatedInterfaceClass' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}');
+ } elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
+ } elseif (0 === strpos($class, 'Test\\'.__NAMESPACE__.'\ExtendsFinalClass')) {
+ $classShortName = substr($class, strrpos($class, '\\') + 1);
+ eval('namespace Test\\'.__NAMESPACE__.'; class '.$classShortName.' extends \\'.__NAMESPACE__.'\Fixtures\\'.substr($classShortName, 7).' {}');
+ } elseif ('Test\\'.__NAMESPACE__.'\ExtendsAnnotatedClass' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsAnnotatedClass extends \\'.__NAMESPACE__.'\Fixtures\AnnotatedClass {
+ public function deprecatedMethod() { }
+ }');
+ } elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternals' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternals extends ExtendsInternalsParent {
+ use \\'.__NAMESPACE__.'\Fixtures\InternalTrait;
+
+ public function internalMethod() { }
+ }');
+ } elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternalsParent' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }');
+ } elseif ('Test\\'.__NAMESPACE__.'\UseTraitWithInternalMethod' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class UseTraitWithInternalMethod { use \\'.__NAMESPACE__.'\Fixtures\TraitWithInternalMethod; }');
+ } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtual' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtual extends ExtendsVirtualParent implements \\'.__NAMESPACE__.'\Fixtures\VirtualSubInterface {
+ public function ownClassMethod() { }
+ public function classMethod() { }
+ public function sameLineInterfaceMethodNoBraces() { }
+ }');
+ } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualParent' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualParent extends ExtendsVirtualAbstract {
+ public function ownParentMethod() { }
+ public function traitMethod() { }
+ public function sameLineInterfaceMethod() { }
+ public function staticMethodNoBraces() { } // should be static
+ }');
+ } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstract' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstract extends ExtendsVirtualAbstractBase {
+ public static function staticMethod() { }
+ public function ownAbstractMethod() { }
+ public function interfaceMethod() { }
+ }');
+ } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstractBase' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstractBase extends \\'.__NAMESPACE__.'\Fixtures\VirtualClass implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface {
+ public function ownAbstractBaseMethod() { }
+ }');
+ } elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualMagicCall' === $class) {
+ eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualMagicCall extends \\'.__NAMESPACE__.'\Fixtures\VirtualClassMagicCall implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface {
+ }');
+ }
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php
new file mode 100644
index 0000000000000..af667e49ec99d
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php
@@ -0,0 +1,563 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LogLevel;
+use Psr\Log\NullLogger;
+use Symfony\Component\ErrorHandler\BufferingLogger;
+use Symfony\Component\ErrorHandler\ErrorHandler;
+use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
+use Symfony\Component\ErrorHandler\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
+use Symfony\Component\ErrorHandler\Tests\Fixtures\LoggerThatSetAnErrorHandler;
+
+/**
+ * ErrorHandlerTest.
+ *
+ * @author Robert Schönthal
+ * @author Nicolas Grekas
+ */
+class ErrorHandlerTest extends TestCase
+{
+ public function testRegister()
+ {
+ $handler = ErrorHandler::register();
+
+ try {
+ $this->assertInstanceOf('Symfony\Component\ErrorHandler\ErrorHandler', $handler);
+ $this->assertSame($handler, ErrorHandler::register());
+
+ $newHandler = new ErrorHandler();
+
+ $this->assertSame($handler, ErrorHandler::register($newHandler, false));
+ $h = set_error_handler('var_dump');
+ restore_error_handler();
+ $this->assertSame([$handler, 'handleError'], $h);
+
+ try {
+ $this->assertSame($newHandler, ErrorHandler::register($newHandler, true));
+ $h = set_error_handler('var_dump');
+ restore_error_handler();
+ $this->assertSame([$newHandler, 'handleError'], $h);
+ } catch (\Exception $e) {
+ }
+
+ restore_error_handler();
+ restore_exception_handler();
+
+ if (isset($e)) {
+ throw $e;
+ }
+ } catch (\Exception $e) {
+ }
+
+ restore_error_handler();
+ restore_exception_handler();
+
+ if (isset($e)) {
+ throw $e;
+ }
+ }
+
+ public function testErrorGetLast()
+ {
+ $handler = ErrorHandler::register();
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+ $handler->setDefaultLogger($logger);
+ $handler->screamAt(E_ALL);
+
+ try {
+ @trigger_error('Hello', E_USER_WARNING);
+ $expected = [
+ 'type' => E_USER_WARNING,
+ 'message' => 'Hello',
+ 'file' => __FILE__,
+ 'line' => __LINE__ - 5,
+ ];
+ $this->assertSame($expected, error_get_last());
+ } catch (\Exception $e) {
+ restore_error_handler();
+ restore_exception_handler();
+
+ throw $e;
+ }
+ }
+
+ public function testNotice()
+ {
+ ErrorHandler::register();
+
+ try {
+ self::triggerNotice($this);
+ $this->fail('ErrorException expected');
+ } catch (\ErrorException $exception) {
+ // if an exception is thrown, the test passed
+ $this->assertEquals(E_NOTICE, $exception->getSeverity());
+ $this->assertEquals(__FILE__, $exception->getFile());
+ $this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage());
+
+ $trace = $exception->getTrace();
+
+ $this->assertEquals(__FILE__, $trace[0]['file']);
+ $this->assertEquals(__CLASS__, $trace[0]['class']);
+ $this->assertEquals('triggerNotice', $trace[0]['function']);
+ $this->assertEquals('::', $trace[0]['type']);
+
+ $this->assertEquals(__FILE__, $trace[0]['file']);
+ $this->assertEquals(__CLASS__, $trace[1]['class']);
+ $this->assertEquals(__FUNCTION__, $trace[1]['function']);
+ $this->assertEquals('->', $trace[1]['type']);
+ } finally {
+ restore_error_handler();
+ restore_exception_handler();
+ }
+ }
+
+ // dummy function to test trace in error handler.
+ private static function triggerNotice($that)
+ {
+ $that->assertSame('', $foo.$foo.$bar);
+ }
+
+ public function testConstruct()
+ {
+ try {
+ $handler = ErrorHandler::register();
+ $handler->throwAt(3, true);
+ $this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0));
+ } finally {
+ restore_error_handler();
+ restore_exception_handler();
+ }
+ }
+
+ public function testDefaultLogger()
+ {
+ try {
+ $handler = ErrorHandler::register();
+
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+ $handler->setDefaultLogger($logger, E_NOTICE);
+ $handler->setDefaultLogger($logger, [E_USER_NOTICE => LogLevel::CRITICAL]);
+
+ $loggers = [
+ E_DEPRECATED => [null, LogLevel::INFO],
+ E_USER_DEPRECATED => [null, LogLevel::INFO],
+ E_NOTICE => [$logger, LogLevel::WARNING],
+ E_USER_NOTICE => [$logger, LogLevel::CRITICAL],
+ E_STRICT => [null, LogLevel::WARNING],
+ E_WARNING => [null, LogLevel::WARNING],
+ E_USER_WARNING => [null, LogLevel::WARNING],
+ E_COMPILE_WARNING => [null, LogLevel::WARNING],
+ E_CORE_WARNING => [null, LogLevel::WARNING],
+ E_USER_ERROR => [null, LogLevel::CRITICAL],
+ E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
+ E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
+ E_PARSE => [null, LogLevel::CRITICAL],
+ E_ERROR => [null, LogLevel::CRITICAL],
+ E_CORE_ERROR => [null, LogLevel::CRITICAL],
+ ];
+ $this->assertSame($loggers, $handler->setLoggers([]));
+ } finally {
+ restore_error_handler();
+ restore_exception_handler();
+ }
+ }
+
+ public function testHandleError()
+ {
+ try {
+ $handler = ErrorHandler::register();
+ $handler->throwAt(0, true);
+ $this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, []));
+
+ restore_error_handler();
+ restore_exception_handler();
+
+ $handler = ErrorHandler::register();
+ $handler->throwAt(3, true);
+ $this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, []));
+
+ restore_error_handler();
+ restore_exception_handler();
+
+ $handler = ErrorHandler::register();
+ $handler->throwAt(3, true);
+ try {
+ $handler->handleError(4, 'foo', 'foo.php', 12, []);
+ } catch (\ErrorException $e) {
+ $this->assertSame('Parse Error: foo', $e->getMessage());
+ $this->assertSame(4, $e->getSeverity());
+ $this->assertSame('foo.php', $e->getFile());
+ $this->assertSame(12, $e->getLine());
+ }
+
+ restore_error_handler();
+ restore_exception_handler();
+
+ $handler = ErrorHandler::register();
+ $handler->throwAt(E_USER_DEPRECATED, true);
+ $this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, []));
+
+ restore_error_handler();
+ restore_exception_handler();
+
+ $handler = ErrorHandler::register();
+ $handler->throwAt(E_DEPRECATED, true);
+ $this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, []));
+
+ restore_error_handler();
+ restore_exception_handler();
+
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+ $warnArgCheck = function ($logLevel, $message, $context) {
+ $this->assertEquals('info', $logLevel);
+ $this->assertEquals('User Deprecated: foo', $message);
+ $this->assertArrayHasKey('exception', $context);
+ $exception = $context['exception'];
+ $this->assertInstanceOf(\ErrorException::class, $exception);
+ $this->assertSame('User Deprecated: foo', $exception->getMessage());
+ $this->assertSame(E_USER_DEPRECATED, $exception->getSeverity());
+ };
+
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->willReturnCallback($warnArgCheck)
+ ;
+
+ $handler = ErrorHandler::register();
+ $handler->setDefaultLogger($logger, E_USER_DEPRECATED);
+ $this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, []));
+
+ restore_error_handler();
+ restore_exception_handler();
+
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+ $line = null;
+ $logArgCheck = function ($level, $message, $context) use (&$line) {
+ $this->assertEquals('Notice: Undefined variable: undefVar', $message);
+ $this->assertArrayHasKey('exception', $context);
+ $exception = $context['exception'];
+ $this->assertInstanceOf(SilencedErrorContext::class, $exception);
+ $this->assertSame(E_NOTICE, $exception->getSeverity());
+ $this->assertSame(__FILE__, $exception->getFile());
+ $this->assertSame($line, $exception->getLine());
+ $this->assertNotEmpty($exception->getTrace());
+ $this->assertSame(1, $exception->count);
+ };
+
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->willReturnCallback($logArgCheck)
+ ;
+
+ $handler = ErrorHandler::register();
+ $handler->setDefaultLogger($logger, E_NOTICE);
+ $handler->screamAt(E_NOTICE);
+ unset($undefVar);
+ $line = __LINE__ + 1;
+ @$undefVar++;
+
+ restore_error_handler();
+ restore_exception_handler();
+ } catch (\Exception $e) {
+ restore_error_handler();
+ restore_exception_handler();
+
+ throw $e;
+ }
+ }
+
+ public function testHandleUserError()
+ {
+ try {
+ $handler = ErrorHandler::register();
+ $handler->throwAt(0, true);
+
+ $e = null;
+ $x = new \Exception('Foo');
+
+ try {
+ $f = new Fixtures\ToStringThrower($x);
+ $f .= ''; // Trigger $f->__toString()
+ } catch (\Exception $e) {
+ }
+
+ $this->assertSame($x, $e);
+ } finally {
+ restore_error_handler();
+ restore_exception_handler();
+ }
+ }
+
+ public function testHandleDeprecation()
+ {
+ $logArgCheck = function ($level, $message, $context) {
+ $this->assertEquals(LogLevel::INFO, $level);
+ $this->assertArrayHasKey('exception', $context);
+ $exception = $context['exception'];
+ $this->assertInstanceOf(\ErrorException::class, $exception);
+ $this->assertSame('User Deprecated: Foo deprecation', $exception->getMessage());
+ };
+
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->willReturnCallback($logArgCheck)
+ ;
+
+ $handler = new ErrorHandler();
+ $handler->setDefaultLogger($logger);
+ @$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, []);
+
+ restore_error_handler();
+ }
+
+ public function testHandleException()
+ {
+ try {
+ $handler = ErrorHandler::register();
+
+ $exception = new \Exception('foo');
+
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+ $logArgCheck = function ($level, $message, $context) {
+ $this->assertSame('Uncaught Exception: foo', $message);
+ $this->assertArrayHasKey('exception', $context);
+ $this->assertInstanceOf(\Exception::class, $context['exception']);
+ };
+
+ $logger
+ ->expects($this->exactly(2))
+ ->method('log')
+ ->willReturnCallback($logArgCheck)
+ ;
+
+ $handler->setDefaultLogger($logger, E_ERROR);
+
+ try {
+ $handler->handleException($exception);
+ $this->fail('Exception expected');
+ } catch (\Exception $e) {
+ $this->assertSame($exception, $e);
+ }
+
+ $handler->setExceptionHandler(function ($e) use ($exception) {
+ $this->assertSame($exception, $e);
+ });
+
+ $handler->handleException($exception);
+ } finally {
+ restore_error_handler();
+ restore_exception_handler();
+ }
+ }
+
+ public function testBootstrappingLogger()
+ {
+ $bootLogger = new BufferingLogger();
+ $handler = new ErrorHandler($bootLogger);
+
+ $loggers = [
+ E_DEPRECATED => [$bootLogger, LogLevel::INFO],
+ E_USER_DEPRECATED => [$bootLogger, LogLevel::INFO],
+ E_NOTICE => [$bootLogger, LogLevel::WARNING],
+ E_USER_NOTICE => [$bootLogger, LogLevel::WARNING],
+ E_STRICT => [$bootLogger, LogLevel::WARNING],
+ E_WARNING => [$bootLogger, LogLevel::WARNING],
+ E_USER_WARNING => [$bootLogger, LogLevel::WARNING],
+ E_COMPILE_WARNING => [$bootLogger, LogLevel::WARNING],
+ E_CORE_WARNING => [$bootLogger, LogLevel::WARNING],
+ E_USER_ERROR => [$bootLogger, LogLevel::CRITICAL],
+ E_RECOVERABLE_ERROR => [$bootLogger, LogLevel::CRITICAL],
+ E_COMPILE_ERROR => [$bootLogger, LogLevel::CRITICAL],
+ E_PARSE => [$bootLogger, LogLevel::CRITICAL],
+ E_ERROR => [$bootLogger, LogLevel::CRITICAL],
+ E_CORE_ERROR => [$bootLogger, LogLevel::CRITICAL],
+ ];
+
+ $this->assertSame($loggers, $handler->setLoggers([]));
+
+ $handler->handleError(E_DEPRECATED, 'Foo message', __FILE__, 123, []);
+
+ $logs = $bootLogger->cleanLogs();
+
+ $this->assertCount(1, $logs);
+ $log = $logs[0];
+ $this->assertSame('info', $log[0]);
+ $this->assertSame('Deprecated: Foo message', $log[1]);
+ $this->assertArrayHasKey('exception', $log[2]);
+ $exception = $log[2]['exception'];
+ $this->assertInstanceOf(\ErrorException::class, $exception);
+ $this->assertSame('Deprecated: Foo message', $exception->getMessage());
+ $this->assertSame(__FILE__, $exception->getFile());
+ $this->assertSame(123, $exception->getLine());
+ $this->assertSame(E_DEPRECATED, $exception->getSeverity());
+
+ $bootLogger->log(LogLevel::WARNING, 'Foo message', ['exception' => $exception]);
+
+ $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+ $mockLogger->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::WARNING, 'Foo message', ['exception' => $exception]);
+
+ $handler->setLoggers([E_DEPRECATED => [$mockLogger, LogLevel::WARNING]]);
+ }
+
+ public function testSettingLoggerWhenExceptionIsBuffered()
+ {
+ $bootLogger = new BufferingLogger();
+ $handler = new ErrorHandler($bootLogger);
+
+ $exception = new \Exception('Foo message');
+
+ $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+ $mockLogger->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::CRITICAL, 'Uncaught Exception: Foo message', ['exception' => $exception]);
+
+ $handler->setExceptionHandler(function () use ($handler, $mockLogger) {
+ $handler->setDefaultLogger($mockLogger);
+ });
+
+ $handler->handleException($exception);
+ }
+
+ public function testHandleFatalError()
+ {
+ try {
+ $handler = ErrorHandler::register();
+
+ $error = [
+ 'type' => E_PARSE,
+ 'message' => 'foo',
+ 'file' => 'bar',
+ 'line' => 123,
+ ];
+
+ $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock();
+
+ $logArgCheck = function ($level, $message, $context) {
+ $this->assertEquals('Fatal Parse Error: foo', $message);
+ $this->assertArrayHasKey('exception', $context);
+ $this->assertInstanceOf(\Exception::class, $context['exception']);
+ };
+
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->willReturnCallback($logArgCheck)
+ ;
+
+ $handler->setDefaultLogger($logger, E_PARSE);
+
+ $handler->handleFatalError($error);
+
+ restore_error_handler();
+ restore_exception_handler();
+ } catch (\Exception $e) {
+ restore_error_handler();
+ restore_exception_handler();
+
+ throw $e;
+ }
+ }
+
+ public function testHandleErrorException()
+ {
+ $exception = new \Error("Class 'IReallyReallyDoNotExistAnywhereInTheRepositoryISwear' not found");
+
+ $handler = new ErrorHandler();
+ $handler->setExceptionHandler(function () use (&$args) {
+ $args = \func_get_args();
+ });
+
+ $handler->handleException($exception);
+
+ $this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $args[0]);
+ $this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
+ }
+
+ /**
+ * @expectedException \Exception
+ */
+ public function testCustomExceptionHandler()
+ {
+ $handler = new ErrorHandler();
+ $handler->setExceptionHandler(function ($e) use ($handler) {
+ $handler->handleException($e);
+ });
+
+ $handler->handleException(new \Exception());
+ }
+
+ /**
+ * @dataProvider errorHandlerWhenLoggingProvider
+ */
+ public function testErrorHandlerWhenLogging($previousHandlerWasDefined, $loggerSetsAnotherHandler, $nextHandlerIsDefined)
+ {
+ try {
+ if ($previousHandlerWasDefined) {
+ set_error_handler('count');
+ }
+
+ $logger = $loggerSetsAnotherHandler ? new LoggerThatSetAnErrorHandler() : new NullLogger();
+
+ $handler = ErrorHandler::register();
+ $handler->setDefaultLogger($logger);
+
+ if ($nextHandlerIsDefined) {
+ $handler = ErrorHandlerThatUsesThePreviousOne::register();
+ }
+
+ @trigger_error('foo', E_USER_DEPRECATED);
+ @trigger_error('bar', E_USER_DEPRECATED);
+
+ $this->assertSame([$handler, 'handleError'], set_error_handler('var_dump'));
+
+ if ($logger instanceof LoggerThatSetAnErrorHandler) {
+ $this->assertCount(2, $logger->cleanLogs());
+ }
+
+ restore_error_handler();
+
+ if ($previousHandlerWasDefined) {
+ restore_error_handler();
+ }
+
+ if ($nextHandlerIsDefined) {
+ restore_error_handler();
+ }
+ } finally {
+ restore_error_handler();
+ restore_exception_handler();
+ }
+ }
+
+ public function errorHandlerWhenLoggingProvider()
+ {
+ foreach ([false, true] as $previousHandlerWasDefined) {
+ foreach ([false, true] as $loggerSetsAnotherHandler) {
+ foreach ([false, true] as $nextHandlerIsDefined) {
+ yield [$previousHandlerWasDefined, $loggerSetsAnotherHandler, $nextHandlerIsDefined];
+ }
+ }
+ }
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/ExceptionHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ExceptionHandlerTest.php
new file mode 100644
index 0000000000000..694177f91e43d
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/ExceptionHandlerTest.php
@@ -0,0 +1,145 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException;
+use Symfony\Component\ErrorHandler\ExceptionHandler;
+use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+require_once __DIR__.'/HeaderMock.php';
+
+class ExceptionHandlerTest extends TestCase
+{
+ protected function setUp()
+ {
+ testHeader();
+ }
+
+ protected function tearDown()
+ {
+ testHeader();
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testDebug()
+ {
+ $handler = new ExceptionHandler(false);
+
+ ob_start();
+ $handler->sendPhpResponse(new \RuntimeException('Foo'));
+ $response = ob_get_clean();
+
+ $this->assertContains('The server returned a "500 Internal Server Error".', $response);
+ $this->assertNotContains('
', $response);
+
+ $handler = new ExceptionHandler(true);
+
+ ob_start();
+ $handler->sendPhpResponse(new \RuntimeException('Foo'));
+ $response = ob_get_clean();
+
+ $this->assertContains('
Foo
', $response);
+ $this->assertContains('
', $response);
+
+ // taken from https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
+ $htmlWithXss = '
click me!
';
+ ob_start();
+ $handler->sendPhpResponse(new \RuntimeException($htmlWithXss));
+ $response = ob_get_clean();
+
+ $this->assertContains(sprintf('
%s
', htmlspecialchars($htmlWithXss, ENT_COMPAT | ENT_SUBSTITUTE, 'UTF-8')), $response);
+ }
+
+ public function testStatusCode()
+ {
+ $handler = new ExceptionHandler(false, 'iso8859-1');
+
+ ob_start();
+ $handler->sendPhpResponse(new NotFoundHttpException('Foo'));
+ $response = ob_get_clean();
+
+ $this->assertContains('The server returned a "404 Not Found".', $response);
+
+ $expectedHeaders = [
+ ['HTTP/1.0 404', true, null],
+ ['Content-Type: text/html; charset=iso8859-1', true, null],
+ ];
+
+ $this->assertSame($expectedHeaders, testHeader());
+ }
+
+ public function testHeaders()
+ {
+ $handler = new ExceptionHandler(false, 'iso8859-1');
+
+ ob_start();
+ $handler->sendPhpResponse(new MethodNotAllowedHttpException(['POST']));
+ $response = ob_get_clean();
+
+ $expectedHeaders = [
+ ['HTTP/1.0 405', true, null],
+ ['Allow: POST', false, null],
+ ['Content-Type: text/html; charset=iso8859-1', true, null],
+ ];
+
+ $this->assertSame($expectedHeaders, testHeader());
+ }
+
+ public function testNestedExceptions()
+ {
+ $handler = new ExceptionHandler(true);
+ ob_start();
+ $handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
+ $response = ob_get_clean();
+
+ $this->assertStringMatchesFormat('%A
Foo
%A
Bar
%A', $response);
+ }
+
+ public function testHandle()
+ {
+ $exception = new \Exception('foo');
+
+ $handler = $this->getMockBuilder('Symfony\Component\ErrorHandler\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
+ $handler
+ ->expects($this->exactly(2))
+ ->method('sendPhpResponse');
+
+ $handler->handle($exception);
+
+ $handler->setHandler(function ($e) use ($exception) {
+ $this->assertSame($exception, $e);
+ });
+
+ $handler->handle($exception);
+ }
+
+ public function testHandleOutOfMemoryException()
+ {
+ $exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
+
+ $handler = $this->getMockBuilder('Symfony\Component\ErrorHandler\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
+ $handler
+ ->expects($this->once())
+ ->method('sendPhpResponse');
+
+ $handler->setHandler(function ($e) {
+ $this->fail('OutOfMemoryException should bypass the handler');
+ });
+
+ $handler->handle($exception);
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
new file mode 100644
index 0000000000000..6057c313f411f
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php
@@ -0,0 +1,180 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Tests\FatalErrorHandler;
+
+use Composer\Autoload\ClassLoader as ComposerClassLoader;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ErrorHandler\DebugClassLoader;
+use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
+use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
+
+class ClassNotFoundFatalErrorHandlerTest extends TestCase
+{
+ public static function setUpBeforeClass()
+ {
+ foreach (spl_autoload_functions() as $function) {
+ if (!\is_array($function)) {
+ continue;
+ }
+
+ // get class loaders wrapped by DebugClassLoader
+ if ($function[0] instanceof DebugClassLoader) {
+ $function = $function[0]->getClassLoader();
+ }
+
+ if ($function[0] instanceof ComposerClassLoader) {
+ $function[0]->add('Symfony_Component_ErrorHandler_Tests_Fixtures', \dirname(\dirname(\dirname(\dirname(\dirname(__DIR__))))));
+ break;
+ }
+ }
+ }
+
+ /**
+ * @dataProvider provideClassNotFoundData
+ */
+ public function testHandleClassNotFound($error, $translatedMessage, $autoloader = null)
+ {
+ if ($autoloader) {
+ // Unregister all autoloaders to ensure the custom provided
+ // autoloader is the only one to be used during the test run.
+ $autoloaders = spl_autoload_functions();
+ array_map('spl_autoload_unregister', $autoloaders);
+ spl_autoload_register($autoloader);
+ }
+
+ $handler = new ClassNotFoundFatalErrorHandler();
+
+ $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
+
+ if ($autoloader) {
+ spl_autoload_unregister($autoloader);
+ array_map('spl_autoload_register', $autoloaders);
+ }
+
+ $this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $exception);
+ $this->assertSame($translatedMessage, $exception->getMessage());
+ $this->assertSame($error['type'], $exception->getSeverity());
+ $this->assertSame($error['file'], $exception->getFile());
+ $this->assertSame($error['line'], $exception->getLine());
+ }
+
+ public function provideClassNotFoundData()
+ {
+ $autoloader = new ComposerClassLoader();
+ $autoloader->add('Symfony\Component\ErrorHandler\Exception\\', realpath(__DIR__.'/../../Exception'));
+ $autoloader->add('Symfony_Component_ErrorHandler_Tests_Fixtures', realpath(__DIR__.'/../../Tests/Fixtures'));
+
+ $debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
+
+ return [
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Class \'WhizBangFactory\' not found',
+ ],
+ "Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?",
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found',
+ ],
+ "Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Class \'UndefinedFunctionException\' not found',
+ ],
+ "Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
+ [$debugClassLoader, 'loadClass'],
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Class \'PEARClass\' not found',
+ ],
+ "Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_ErrorHandler_Tests_Fixtures_PEARClass\"?",
+ [$debugClassLoader, 'loadClass'],
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
+ ],
+ "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
+ [$debugClassLoader, 'loadClass'],
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
+ ],
+ "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
+ [$autoloader, 'loadClass'],
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
+ ],
+ "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
+ [$debugClassLoader, 'loadClass'],
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
+ ],
+ "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
+ function ($className) { /* do nothing here */ },
+ ],
+ ];
+ }
+
+ public function testCannotRedeclareClass()
+ {
+ if (!file_exists(__DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP')) {
+ $this->markTestSkipped('Can only be run on case insensitive filesystems');
+ }
+
+ require_once __DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP';
+
+ $error = [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Class \'Foo\\Bar\\RequiredTwice\' not found',
+ ];
+
+ $handler = new ClassNotFoundFatalErrorHandler();
+ $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
+
+ $this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $exception);
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
new file mode 100644
index 0000000000000..c24109b1b3525
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Tests\FatalErrorHandler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
+use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
+
+class UndefinedFunctionFatalErrorHandlerTest extends TestCase
+{
+ /**
+ * @dataProvider provideUndefinedFunctionData
+ */
+ public function testUndefinedFunction($error, $translatedMessage)
+ {
+ $handler = new UndefinedFunctionFatalErrorHandler();
+ $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
+
+ $this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException', $exception);
+ // class names are case insensitive and PHP do not return the same
+ $this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
+ $this->assertSame($error['type'], $exception->getSeverity());
+ $this->assertSame($error['file'], $exception->getFile());
+ $this->assertSame($error['line'], $exception->getLine());
+ }
+
+ public function provideUndefinedFunctionData()
+ {
+ return [
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Call to undefined function test_namespaced_function()',
+ ],
+ "Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
+ ],
+ "Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Call to undefined function foo()',
+ ],
+ 'Attempted to call function "foo" from the global namespace.',
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Call to undefined function Foo\\Bar\\Baz\\foo()',
+ ],
+ 'Attempted to call function "foo" from namespace "Foo\Bar\Baz".',
+ ],
+ ];
+ }
+}
+
+function test_namespaced_function()
+{
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
new file mode 100644
index 0000000000000..b91792b440329
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Tests\FatalErrorHandler;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
+use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
+
+class UndefinedMethodFatalErrorHandlerTest extends TestCase
+{
+ /**
+ * @dataProvider provideUndefinedMethodData
+ */
+ public function testUndefinedMethod($error, $translatedMessage)
+ {
+ $handler = new UndefinedMethodFatalErrorHandler();
+ $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
+
+ $this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\UndefinedMethodException', $exception);
+ $this->assertSame($translatedMessage, $exception->getMessage());
+ $this->assertSame($error['type'], $exception->getSeverity());
+ $this->assertSame($error['file'], $exception->getFile());
+ $this->assertSame($error['line'], $exception->getLine());
+ }
+
+ public function provideUndefinedMethodData()
+ {
+ return [
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Call to undefined method SplObjectStorage::what()',
+ ],
+ 'Attempted to call an undefined method named "what" of class "SplObjectStorage".',
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Call to undefined method SplObjectStorage::walid()',
+ ],
+ "Attempted to call an undefined method named \"walid\" of class \"SplObjectStorage\".\nDid you mean to call \"valid\"?",
+ ],
+ [
+ [
+ 'type' => 1,
+ 'line' => 12,
+ 'file' => 'foo.php',
+ 'message' => 'Call to undefined method SplObjectStorage::offsetFet()',
+ ],
+ "Attempted to call an undefined method named \"offsetFet\" of class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?",
+ ],
+ [
+ [
+ 'type' => 1,
+ 'message' => 'Call to undefined method class@anonymous::test()',
+ 'file' => '/home/possum/work/symfony/test.php',
+ 'line' => 11,
+ ],
+ 'Attempted to call an undefined method named "test" of class "class@anonymous".',
+ ],
+ ];
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/AnnotatedClass.php b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/AnnotatedClass.php
new file mode 100644
index 0000000000000..bbd19e15a6f47
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/AnnotatedClass.php
@@ -0,0 +1,13 @@
+exception = $e;
+ }
+
+ public function __toString()
+ {
+ try {
+ throw $this->exception;
+ } catch (\Exception $e) {
+ // Using user_error() here is on purpose so we do not forget
+ // that this alias also should work alongside with trigger_error().
+ return trigger_error($e, E_USER_ERROR);
+ }
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/Fixtures/TraitWithAnnotatedParameters.php b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/TraitWithAnnotatedParameters.php
new file mode 100644
index 0000000000000..c9abd4096f3e3
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/Fixtures/TraitWithAnnotatedParameters.php
@@ -0,0 +1,13 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler;
+
+function headers_sent()
+{
+ return false;
+}
+
+function header($str, $replace = true, $status = null)
+{
+ Tests\testHeader($str, $replace, $status);
+}
+
+namespace Symfony\Component\ErrorHandler\Tests;
+
+function testHeader()
+{
+ static $headers = [];
+
+ if (!$h = \func_get_args()) {
+ $h = $headers;
+ $headers = [];
+
+ return $h;
+ }
+
+ $headers[] = \func_get_args();
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/MockExceptionHandler.php b/src/Symfony/Component/ErrorHandler/Tests/MockExceptionHandler.php
new file mode 100644
index 0000000000000..700990de58ec6
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/MockExceptionHandler.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\Tests;
+
+use Symfony\Component\ErrorHandler\ExceptionHandler;
+
+class MockExceptionHandler extends ExceptionHandler
+{
+ public $e;
+
+ public function handle(\Exception $e)
+ {
+ $this->e = $e;
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/phpt/debug_class_loader.phpt b/src/Symfony/Component/ErrorHandler/Tests/phpt/debug_class_loader.phpt
new file mode 100644
index 0000000000000..865dbd7e03e12
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/phpt/debug_class_loader.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Test DebugClassLoader with previously loaded parents
+--FILE--
+
+--EXPECTF--
+The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\ErrorHandler\Tests\Fixtures\ExtendedFinalMethod".
+The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalMethod::finalMethod2()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\ErrorHandler\Tests\Fixtures\ExtendedFinalMethod".
diff --git a/src/Symfony/Component/ErrorHandler/Tests/phpt/decorate_exception_hander.phpt b/src/Symfony/Component/ErrorHandler/Tests/phpt/decorate_exception_hander.phpt
new file mode 100644
index 0000000000000..034d5a5292a44
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/phpt/decorate_exception_hander.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Test catching fatal errors when handlers are nested
+--INI--
+display_errors=0
+--FILE--
+
+--EXPECTF--
+object(Symfony\Component\ErrorHandler\Exception\ClassNotFoundException)#%d (8) {
+ ["message":protected]=>
+ string(138) "Attempted to load class "missing" from namespace "Symfony\Component\ErrorHandler".
+Did you forget a "use" statement for another namespace?"
+ ["string":"Exception":private]=>
+ string(0) ""
+ ["code":protected]=>
+ int(0)
+ ["file":protected]=>
+ string(%d) "%s"
+ ["line":protected]=>
+ int(%d)
+ ["trace":"Exception":private]=>
+ array(%d) {%A}
+ ["previous":"Exception":private]=>
+ NULL
+ ["severity":protected]=>
+ int(1)
+}
diff --git a/src/Symfony/Component/ErrorHandler/Tests/phpt/exception_rethrown.phpt b/src/Symfony/Component/ErrorHandler/Tests/phpt/exception_rethrown.phpt
new file mode 100644
index 0000000000000..82a9006d840f9
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/phpt/exception_rethrown.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test rethrowing in custom exception handler
+--FILE--
+setDefaultLogger(new TestLogger());
+ini_set('display_errors', 1);
+
+throw new \Exception('foo');
+?>
+--EXPECTF--
+Uncaught Exception: foo
+123
+Fatal error: Uncaught %s:25
+Stack trace:
+%a
diff --git a/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt
new file mode 100644
index 0000000000000..532fe922410cb
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/Tests/phpt/fatal_with_nested_handlers.phpt
@@ -0,0 +1,42 @@
+--TEST--
+Test catching fatal errors when handlers are nested
+--FILE--
+setExceptionHandler('print_r');
+
+if (true) {
+ class Broken implements \JsonSerializable
+ {
+ }
+}
+
+?>
+--EXPECTF--
+array(1) {
+ [0]=>
+ string(37) "Error and exception handlers do match"
+}
+object(Symfony\Component\ErrorHandler\Exception\FatalErrorException)#%d (%d) {
+ ["message":protected]=>
+ string(186) "Error: Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
+%a
+}
diff --git a/src/Symfony/Component/ErrorHandler/composer.json b/src/Symfony/Component/ErrorHandler/composer.json
new file mode 100644
index 0000000000000..3794930f35b9b
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/composer.json
@@ -0,0 +1,41 @@
+{
+ "name": "symfony/error-handler",
+ "type": "library",
+ "description": "Symfony ErrorHandler Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.1.3",
+ "psr/log": "~1.0",
+ "symfony/error-renderer": "^4.4|^5.0"
+ },
+ "conflict": {
+ "symfony/http-kernel": "<3.4"
+ },
+ "require-dev": {
+ "symfony/http-kernel": "^3.4|^4.0|^5.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\ErrorHandler\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.4-dev"
+ }
+ }
+}
diff --git a/src/Symfony/Component/ErrorHandler/phpunit.xml.dist b/src/Symfony/Component/ErrorHandler/phpunit.xml.dist
new file mode 100644
index 0000000000000..6c42fd1815b2c
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/phpunit.xml.dist
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+ ./Resources/ext/tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php b/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php
index 29d1685583b34..8bbd6e695c8af 100644
--- a/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php
+++ b/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php
@@ -11,7 +11,7 @@
namespace Symfony\Component\ErrorRenderer\Exception;
-use Symfony\Component\Debug\Exception\FatalThrowableError;
+use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
diff --git a/src/Symfony/Component/ErrorRenderer/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/ErrorRenderer/Tests/Exception/FlattenExceptionTest.php
index 5994be23973e1..0220d75b9e568 100644
--- a/src/Symfony/Component/ErrorRenderer/Tests/Exception/FlattenExceptionTest.php
+++ b/src/Symfony/Component/ErrorRenderer/Tests/Exception/FlattenExceptionTest.php
@@ -12,7 +12,7 @@
namespace Symfony\Component\ErrorRenderer\Tests\Exception;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Debug\Exception\FatalThrowableError;
+use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
diff --git a/src/Symfony/Component/ErrorRenderer/composer.json b/src/Symfony/Component/ErrorRenderer/composer.json
index bf46db539d566..4ce8a991f4280 100644
--- a/src/Symfony/Component/ErrorRenderer/composer.json
+++ b/src/Symfony/Component/ErrorRenderer/composer.json
@@ -20,12 +20,10 @@
"psr/log": "~1.0"
},
"require-dev": {
- "symfony/debug": "^4.4",
"symfony/dependency-injection": "^4.4",
"symfony/http-kernel": "^4.4"
},
"conflict": {
- "symfony/debug": "<4.4",
"symfony/http-kernel": "<4.4"
},
"autoload": {
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
index 405a951526bde..b71f33c5686ea 100644
--- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
+++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php
@@ -11,7 +11,7 @@
namespace Symfony\Component\HttpKernel\DataCollector;
-use Symfony\Component\Debug\Exception\SilencedErrorContext;
+use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php
index 2659d34de649d..728ebe35ed565 100644
--- a/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php
+++ b/src/Symfony/Component/HttpKernel/DependencyInjection/AddAnnotatedClassesToCachePass.php
@@ -12,9 +12,9 @@
namespace Symfony\Component\HttpKernel\DependencyInjection;
use Composer\Autoload\ClassLoader;
-use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\HttpKernel\Kernel;
/**
diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php
index 9d45d8378650c..4f7a54291e525 100644
--- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php
+++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php
@@ -15,8 +15,8 @@
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
-use Symfony\Component\Debug\ErrorHandler;
-use Symfony\Component\Debug\ExceptionHandler;
+use Symfony\Component\ErrorHandler\ErrorHandler;
+use Symfony\Component\ErrorHandler\ExceptionHandler;
use Symfony\Component\ErrorRenderer\ErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException;
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index e6532861215ab..4f33015653b66 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -16,7 +16,6 @@
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
-use Symfony\Component\Debug\DebugClassLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -29,6 +28,7 @@
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
+use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php
index 261f098a72c19..3010c5e02eb8e 100644
--- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php
@@ -12,7 +12,7 @@
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Debug\Exception\SilencedErrorContext;
+use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
@@ -106,7 +106,7 @@ public function testCollect($nb, $logs, $expectedLogs, $expectedDeprecationCount
$logs = array_map(function ($v) {
if (isset($v['context']['exception'])) {
$e = &$v['context']['exception'];
- $e = isset($e["\0*\0message"]) ? [$e["\0*\0message"], $e["\0*\0severity"]] : [$e["\0Symfony\Component\Debug\Exception\SilencedErrorContext\0severity"]];
+ $e = isset($e["\0*\0message"]) ? [$e["\0*\0message"], $e["\0*\0severity"]] : [$e["\0Symfony\Component\ErrorHandler\Exception\SilencedErrorContext\0severity"]];
}
return $v;
diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php
index 1e52bce7a359b..746756cf01c69 100644
--- a/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php
@@ -19,8 +19,8 @@
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
-use Symfony\Component\Debug\ErrorHandler;
-use Symfony\Component\Debug\ExceptionHandler;
+use Symfony\Component\ErrorHandler\ErrorHandler;
+use Symfony\Component\ErrorHandler\ExceptionHandler;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\KernelEvent;
diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json
index 52d026be20c36..fcea6c32c4c70 100644
--- a/src/Symfony/Component/HttpKernel/composer.json
+++ b/src/Symfony/Component/HttpKernel/composer.json
@@ -17,7 +17,7 @@
],
"require": {
"php": "^7.1.3",
- "symfony/debug": "^4.4|^5.0",
+ "symfony/error-handler": "^4.4|^5.0",
"symfony/error-renderer": "^4.4|^5.0",
"symfony/event-dispatcher": "^4.3",
"symfony/http-foundation": "^4.4|^5.0",
diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json
index 02bcaec43012d..7f2ee3ca5fe5b 100644
--- a/src/Symfony/Component/Messenger/composer.json
+++ b/src/Symfony/Component/Messenger/composer.json
@@ -38,8 +38,7 @@
},
"conflict": {
"symfony/event-dispatcher": "<4.3",
- "symfony/http-kernel": "<4.4",
- "symfony/debug": "<4.4"
+ "symfony/http-kernel": "<4.4"
},
"suggest": {
"enqueue/messenger-adapter": "For using the php-enqueue library as a transport."
diff --git a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php
index ec168c8da9c92..60cffcb6b73a9 100644
--- a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php
+++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php
@@ -11,7 +11,7 @@
namespace Symfony\Component\VarDumper\Caster;
-use Symfony\Component\Debug\Exception\SilencedErrorContext;
+use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
index ea2b45ffa1a06..ed16de76bfaf3 100644
--- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
+++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
@@ -85,7 +85,7 @@ abstract class AbstractCloner implements ClonerInterface
'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'],
'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'],
'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
- 'Symfony\Component\Debug\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'],
+ 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'],
'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'],
'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],