Added ErrorHandler component
This commit is contained in:
parent
dca9325e61
commit
7057244890
@ -19,8 +19,8 @@ use Symfony\Component\Console\Input\InputOption;
|
|||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
|
||||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
use Symfony\Component\HttpKernel\Kernel;
|
use Symfony\Component\HttpKernel\Kernel;
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
@ -152,6 +152,7 @@ class FrameworkExtension extends Extension
|
|||||||
$loader->load('web.xml');
|
$loader->load('web.xml');
|
||||||
$loader->load('services.xml');
|
$loader->load('services.xml');
|
||||||
$loader->load('fragment_renderer.xml');
|
$loader->load('fragment_renderer.xml');
|
||||||
|
$loader->load('error_renderer.xml');
|
||||||
|
|
||||||
$container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class);
|
$container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class);
|
||||||
|
|
||||||
|
@ -29,10 +29,11 @@ use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
|
|||||||
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
|
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
|
||||||
use Symfony\Component\Config\Resource\ClassExistenceResource;
|
use Symfony\Component\Config\Resource\ClassExistenceResource;
|
||||||
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
||||||
use Symfony\Component\Debug\ErrorHandler;
|
|
||||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||||
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
|
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\ErrorHandler\DependencyInjection\ErrorHandlerPass;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||||
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
||||||
use Symfony\Component\Form\DependencyInjection\FormPass;
|
use Symfony\Component\Form\DependencyInjection\FormPass;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@ -90,6 +91,7 @@ class FrameworkBundle extends Bundle
|
|||||||
KernelEvents::FINISH_REQUEST,
|
KernelEvents::FINISH_REQUEST,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$this->addCompilerPassIfExists($container, ErrorHandlerPass::class);
|
||||||
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
||||||
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
|
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
|
||||||
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
|
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
|
||||||
|
@ -19,9 +19,10 @@
|
|||||||
<argument>null</argument><!-- Log levels map for enabled error levels -->
|
<argument>null</argument><!-- Log levels map for enabled error levels -->
|
||||||
<argument>%debug.error_handler.throw_at%</argument>
|
<argument>%debug.error_handler.throw_at%</argument>
|
||||||
<argument>%kernel.debug%</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
<argument type="service" id="debug.file_link_formatter"></argument>
|
<argument type="service" id="debug.file_link_formatter" />
|
||||||
<argument>%kernel.debug%</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
<argument>%kernel.charset%</argument>
|
<argument>%kernel.charset%</argument>
|
||||||
|
<argument type="service" id="error_handler.error_renderer" on-invalid="null" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="debug.file_link_formatter" class="Symfony\Component\HttpKernel\Debug\FileLinkFormatter">
|
<service id="debug.file_link_formatter" class="Symfony\Component\HttpKernel\Debug\FileLinkFormatter">
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<container xmlns="http://symfony.com/schema/dic/services"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||||
|
|
||||||
|
<services>
|
||||||
|
<defaults public="false" />
|
||||||
|
|
||||||
|
<service id="error_handler.error_renderer" class="Symfony\Component\ErrorHandler\DependencyInjection\ErrorRenderer">
|
||||||
|
<argument /> <!-- error renderer locator -->
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="error_handler.renderer.html" class="Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer">
|
||||||
|
<tag name="error_handler.renderer" />
|
||||||
|
<argument>%kernel.debug%</argument>
|
||||||
|
<argument>%kernel.charset%</argument>
|
||||||
|
<argument>%debug.file_link_format%</argument>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="error_handler.renderer.json" class="Symfony\Component\ErrorHandler\ErrorRenderer\JsonErrorRenderer">
|
||||||
|
<tag name="error_handler.renderer" />
|
||||||
|
<argument>%kernel.debug%</argument>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="error_handler.renderer.xml" class="Symfony\Component\ErrorHandler\ErrorRenderer\XmlErrorRenderer">
|
||||||
|
<tag name="error_handler.renderer" format="atom" />
|
||||||
|
<tag name="error_handler.renderer" />
|
||||||
|
<argument>%kernel.debug%</argument>
|
||||||
|
<argument>%kernel.charset%</argument>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="error_handler.renderer.txt" class="Symfony\Component\ErrorHandler\ErrorRenderer\TxtErrorRenderer">
|
||||||
|
<tag name="error_handler.renderer" />
|
||||||
|
<argument>%kernel.debug%</argument>
|
||||||
|
<argument>%kernel.charset%</argument>
|
||||||
|
</service>
|
||||||
|
</services>
|
||||||
|
</container>
|
@ -22,7 +22,7 @@
|
|||||||
"symfony/config": "^4.2|^5.0",
|
"symfony/config": "^4.2|^5.0",
|
||||||
"symfony/dependency-injection": "^4.4|^5.0",
|
"symfony/dependency-injection": "^4.4|^5.0",
|
||||||
"symfony/http-foundation": "^4.3|^5.0",
|
"symfony/http-foundation": "^4.3|^5.0",
|
||||||
"symfony/http-kernel": "^4.3|^5.0",
|
"symfony/http-kernel": "^4.4|^5.0",
|
||||||
"symfony/polyfill-mbstring": "~1.0",
|
"symfony/polyfill-mbstring": "~1.0",
|
||||||
"symfony/filesystem": "^3.4|^4.0|^5.0",
|
"symfony/filesystem": "^3.4|^4.0|^5.0",
|
||||||
"symfony/finder": "^3.4|^4.0|^5.0",
|
"symfony/finder": "^3.4|^4.0|^5.0",
|
||||||
@ -69,6 +69,7 @@
|
|||||||
"symfony/asset": "<3.4",
|
"symfony/asset": "<3.4",
|
||||||
"symfony/browser-kit": "<4.3",
|
"symfony/browser-kit": "<4.3",
|
||||||
"symfony/console": "<4.3",
|
"symfony/console": "<4.3",
|
||||||
|
"symfony/debug": "<4.4",
|
||||||
"symfony/dotenv": "<4.2",
|
"symfony/dotenv": "<4.2",
|
||||||
"symfony/dom-crawler": "<4.3",
|
"symfony/dom-crawler": "<4.3",
|
||||||
"symfony/form": "<4.3",
|
"symfony/form": "<4.3",
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\TwigBundle\Controller;
|
namespace Symfony\Bundle\TwigBundle\Controller;
|
||||||
|
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
|
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\TwigBundle\Controller;
|
namespace Symfony\Bundle\TwigBundle\Controller;
|
||||||
|
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class ExceptionListenerPass implements CompilerPassInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// register the exception controller only if Twig is enabled and required dependencies do exist
|
// register the exception controller only if Twig is enabled and required dependencies do exist
|
||||||
if (!class_exists('Symfony\Component\Debug\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) {
|
if (!class_exists('Symfony\Component\ErrorHandler\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) {
|
||||||
$container->removeDefinition('twig.exception_listener');
|
$container->removeDefinition('twig.exception_listener');
|
||||||
} elseif ($container->hasParameter('templating.engines')) {
|
} elseif ($container->hasParameter('templating.engines')) {
|
||||||
$engines = $container->getParameter('templating.engines');
|
$engines = $container->getParameter('templating.engines');
|
||||||
|
@ -13,7 +13,7 @@ namespace Symfony\Bundle\TwigBundle\Tests\Controller;
|
|||||||
|
|
||||||
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
|
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
|
||||||
use Symfony\Bundle\TwigBundle\Tests\TestCase;
|
use Symfony\Bundle\TwigBundle\Tests\TestCase;
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
use Twig\Loader\ArrayLoader;
|
use Twig\Loader\ArrayLoader;
|
||||||
|
@ -13,7 +13,7 @@ namespace Symfony\Bundle\TwigBundle\Tests\Controller;
|
|||||||
|
|
||||||
use Symfony\Bundle\TwigBundle\Controller\PreviewErrorController;
|
use Symfony\Bundle\TwigBundle\Controller\PreviewErrorController;
|
||||||
use Symfony\Bundle\TwigBundle\Tests\TestCase;
|
use Symfony\Bundle\TwigBundle\Tests\TestCase;
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"symfony/config": "^4.2|^5.0",
|
"symfony/config": "^4.2|^5.0",
|
||||||
"symfony/twig-bridge": "^4.4|^5.0",
|
"symfony/twig-bridge": "^4.4|^5.0",
|
||||||
"symfony/http-foundation": "^4.3|^5.0",
|
"symfony/http-foundation": "^4.3|^5.0",
|
||||||
"symfony/http-kernel": "^4.1|^5.0",
|
"symfony/http-kernel": "^4.4|^5.0",
|
||||||
"symfony/polyfill-ctype": "~1.8",
|
"symfony/polyfill-ctype": "~1.8",
|
||||||
"twig/twig": "~1.41|~2.10"
|
"twig/twig": "~1.41|~2.10"
|
||||||
},
|
},
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\WebProfilerBundle\Controller;
|
namespace Symfony\Bundle\WebProfilerBundle\Controller;
|
||||||
|
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
@ -30,14 +30,18 @@ class ExceptionController
|
|||||||
protected $twig;
|
protected $twig;
|
||||||
protected $debug;
|
protected $debug;
|
||||||
protected $profiler;
|
protected $profiler;
|
||||||
private $fileLinkFormat;
|
private $errorRenderer;
|
||||||
|
|
||||||
public function __construct(Profiler $profiler = null, Environment $twig, bool $debug, FileLinkFormatter $fileLinkFormat = null)
|
public function __construct(Profiler $profiler = null, Environment $twig, bool $debug, FileLinkFormatter $fileLinkFormat = null, HtmlErrorRenderer $errorRenderer = null)
|
||||||
{
|
{
|
||||||
$this->profiler = $profiler;
|
$this->profiler = $profiler;
|
||||||
$this->twig = $twig;
|
$this->twig = $twig;
|
||||||
$this->debug = $debug;
|
$this->debug = $debug;
|
||||||
$this->fileLinkFormat = $fileLinkFormat;
|
$this->errorRenderer = $errorRenderer;
|
||||||
|
|
||||||
|
if (null === $errorRenderer) {
|
||||||
|
$this->errorRenderer = new HtmlErrorRenderer($debug, $this->twig->getCharset(), $fileLinkFormat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,9 +65,7 @@ class ExceptionController
|
|||||||
$template = $this->getTemplate();
|
$template = $this->getTemplate();
|
||||||
|
|
||||||
if (!$this->twig->getLoader()->exists($template)) {
|
if (!$this->twig->getLoader()->exists($template)) {
|
||||||
$handler = new ExceptionHandler($this->debug, $this->twig->getCharset(), $this->fileLinkFormat);
|
return new Response($this->errorRenderer->getBody($exception), 200, ['Content-Type' => 'text/html']);
|
||||||
|
|
||||||
return new Response($handler->getContent($exception), 200, ['Content-Type' => 'text/html']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$code = $exception->getStatusCode();
|
$code = $exception->getStatusCode();
|
||||||
@ -97,13 +99,10 @@ class ExceptionController
|
|||||||
|
|
||||||
$this->profiler->disable();
|
$this->profiler->disable();
|
||||||
|
|
||||||
$exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException();
|
|
||||||
$template = $this->getTemplate();
|
$template = $this->getTemplate();
|
||||||
|
|
||||||
if (!$this->templateExists($template)) {
|
if (!$this->templateExists($template)) {
|
||||||
$handler = new ExceptionHandler($this->debug, $this->twig->getCharset(), $this->fileLinkFormat);
|
return new Response($this->errorRenderer->getStylesheet(), 200, ['Content-Type' => 'text/css']);
|
||||||
|
|
||||||
return new Response($handler->getStylesheet($exception), 200, ['Content-Type' => 'text/css']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response($this->twig->render('@WebProfiler/Collector/exception.css.twig'), 200, ['Content-Type' => 'text/css']);
|
return new Response($this->twig->render('@WebProfiler/Collector/exception.css.twig'), 200, ['Content-Type' => 'text/css']);
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
<argument type="service" id="twig" />
|
<argument type="service" id="twig" />
|
||||||
<argument>%kernel.debug%</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
<argument type="service" id="debug.file_link_formatter" />
|
<argument type="service" id="debug.file_link_formatter" />
|
||||||
|
<argument type="service" id="error_handler.renderer.html" on-invalid="null" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="web_profiler.csp.handler" class="Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler">
|
<service id="web_profiler.csp.handler" class="Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler">
|
||||||
|
@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Container;
|
|||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Definition;
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ class WebProfilerExtensionTest extends TestCase
|
|||||||
$this->kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\KernelInterface')->getMock();
|
$this->kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\KernelInterface')->getMock();
|
||||||
|
|
||||||
$this->container = new ContainerBuilder();
|
$this->container = new ContainerBuilder();
|
||||||
|
$this->container->register('error_handler.renderer.html', HtmlErrorRenderer::class);
|
||||||
$this->container->register('event_dispatcher', EventDispatcher::class)->setPublic(true);
|
$this->container->register('event_dispatcher', EventDispatcher::class)->setPublic(true);
|
||||||
$this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true);
|
$this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true);
|
||||||
$this->container->register('twig', 'Twig\Environment')->setPublic(true);
|
$this->container->register('twig', 'Twig\Environment')->setPublic(true);
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"symfony/config": "^4.2|^5.0",
|
"symfony/config": "^4.2|^5.0",
|
||||||
"symfony/http-kernel": "^4.3",
|
"symfony/http-kernel": "^4.4",
|
||||||
"symfony/routing": "^3.4|^4.0|^5.0",
|
"symfony/routing": "^3.4|^4.0|^5.0",
|
||||||
"symfony/twig-bundle": "^4.2|^5.0",
|
"symfony/twig-bundle": "^4.2|^5.0",
|
||||||
"symfony/var-dumper": "^3.4|^4.0|^5.0",
|
"symfony/var-dumper": "^3.4|^4.0|^5.0",
|
||||||
|
@ -41,8 +41,8 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
|||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Debug\ErrorHandler;
|
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||||
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
|
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
|
||||||
|
|
||||||
|
@ -11,27 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
use Psr\Log\AbstractLogger;
|
use Symfony\Component\ErrorHandler\BufferingLogger as BaseBufferingLogger;
|
||||||
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', BufferingLogger::class, BaseBufferingLogger::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A buffering logger that stacks logs for later.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\BufferingLogger instead.
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*/
|
*/
|
||||||
class BufferingLogger extends AbstractLogger
|
class BufferingLogger extends BaseBufferingLogger
|
||||||
{
|
{
|
||||||
private $logs = [];
|
|
||||||
|
|
||||||
public function log($level, $message, array $context = [])
|
|
||||||
{
|
|
||||||
$this->logs[] = [$level, $message, $context];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function cleanLogs()
|
|
||||||
{
|
|
||||||
$logs = $this->logs;
|
|
||||||
$this->logs = [];
|
|
||||||
|
|
||||||
return $logs;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
4.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* deprecated the `BufferingLogger`, `ErrorHandler` and `ExceptionHandler` classes,
|
||||||
|
they have been moved to the `ErrorHandler` component
|
||||||
|
* deprecated the `FatalErrorHandlerInterface`, `ClassNotFoundFatalErrorHandler`,
|
||||||
|
`UndefinedFunctionFatalErrorHandler` and `UndefinedMethodFatalErrorHandler` classes,
|
||||||
|
they have been moved to the `ErrorHandler` component
|
||||||
|
* deprecated the `ClassNotFoundException`, `FatalErrorException`, `FatalThrowableError`,
|
||||||
|
`FlattenException`, `OutOfMemoryException`, `SilencedErrorContext`, `UndefinedFunctionException`,
|
||||||
|
and `UndefinedMethodException`, they have been moved to the `ErrorHandler` component
|
||||||
|
|
||||||
4.3.0
|
4.3.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -11,6 +11,10 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\BufferingLogger;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||||
|
use Symfony\Component\ErrorHandler\ExceptionHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers all the debug tools.
|
* Registers all the debug tools.
|
||||||
*
|
*
|
||||||
|
@ -86,7 +86,7 @@ class DebugClassLoader
|
|||||||
public static function enable()
|
public static function enable()
|
||||||
{
|
{
|
||||||
// Ensures we don't hit https://bugs.php.net/42098
|
// Ensures we don't hit https://bugs.php.net/42098
|
||||||
class_exists('Symfony\Component\Debug\ErrorHandler');
|
class_exists('Symfony\Component\ErrorHandler\ErrorHandler');
|
||||||
class_exists('Psr\Log\LogLevel');
|
class_exists('Psr\Log\LogLevel');
|
||||||
|
|
||||||
if (!\is_array($functions = spl_autoload_functions())) {
|
if (!\is_array($functions = spl_autoload_functions())) {
|
||||||
|
@ -11,705 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\Debug;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Symfony\Component\ErrorHandler\ErrorHandler as BaseErrorHandler;
|
||||||
use Psr\Log\LogLevel;
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ErrorHandler::class, BaseErrorHandler::class), E_USER_DEPRECATED);
|
||||||
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
|
||||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
|
||||||
use Symfony\Component\Debug\Exception\SilencedErrorContext;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic ErrorHandler for the PHP engine.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\ErrorHandler instead.
|
||||||
*
|
|
||||||
* 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 <p@tchwork.com>
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*
|
|
||||||
* @final since Symfony 4.3
|
|
||||||
*/
|
*/
|
||||||
class ErrorHandler
|
class ErrorHandler extends BaseErrorHandler
|
||||||
{
|
{
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,26 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\ClassNotFoundException as BaseClassNotFoundException;
|
||||||
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundException::class, BaseClassNotFoundException::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class (or Trait or Interface) Not Found Exception.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\ClassNotFoundException instead.
|
||||||
*
|
|
||||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
|
||||||
*/
|
*/
|
||||||
class ClassNotFoundException extends FatalErrorException
|
class ClassNotFoundException extends BaseClassNotFoundException
|
||||||
{
|
{
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,67 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FatalErrorException as BaseFatalErrorException;
|
||||||
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorException::class, BaseFatalErrorException::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fatal Error Exception.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FatalErrorException instead.
|
||||||
*
|
|
||||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
|
||||||
*/
|
*/
|
||||||
class FatalErrorException extends \ErrorException
|
class FatalErrorException extends BaseFatalErrorException
|
||||||
{
|
{
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,41 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError as BaseFatalThrowableError;
|
||||||
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FatalThrowableError::class, BaseFatalThrowableError::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fatal Throwable Error.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FatalThrowableError instead.
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*/
|
*/
|
||||||
class FatalThrowableError extends FatalErrorException
|
class FatalThrowableError extends BaseFatalThrowableError
|
||||||
{
|
{
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,349 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException as BaseFlattenException;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', FlattenException::class, BaseFlattenException::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlattenException wraps a PHP Error or Exception to be able to serialize it.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FlattenException instead.
|
||||||
*
|
|
||||||
* Basically, this class removes all objects from the trace.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
*/
|
||||||
class FlattenException
|
class FlattenException extends BaseFlattenException
|
||||||
{
|
{
|
||||||
private $message;
|
|
||||||
private $code;
|
|
||||||
private $previous;
|
|
||||||
private $trace;
|
|
||||||
private $traceAsString;
|
|
||||||
private $class;
|
|
||||||
private $statusCode;
|
|
||||||
private $headers;
|
|
||||||
private $file;
|
|
||||||
private $line;
|
|
||||||
|
|
||||||
public static function create(\Exception $exception, $statusCode = null, array $headers = [])
|
|
||||||
{
|
|
||||||
return static::createFromThrowable($exception, $statusCode, $headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function createFromThrowable(\Throwable $exception, ?int $statusCode = null, array $headers = []): self
|
|
||||||
{
|
|
||||||
$e = new static();
|
|
||||||
$e->setMessage($exception->getMessage());
|
|
||||||
$e->setCode($exception->getCode());
|
|
||||||
|
|
||||||
if ($exception instanceof HttpExceptionInterface) {
|
|
||||||
$statusCode = $exception->getStatusCode();
|
|
||||||
$headers = array_merge($headers, $exception->getHeaders());
|
|
||||||
} elseif ($exception instanceof RequestExceptionInterface) {
|
|
||||||
$statusCode = 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null === $statusCode) {
|
|
||||||
$statusCode = 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
$e->setStatusCode($statusCode);
|
|
||||||
$e->setHeaders($headers);
|
|
||||||
$e->setTraceFromThrowable($exception);
|
|
||||||
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
|
|
||||||
$e->setFile($exception->getFile());
|
|
||||||
$e->setLine($exception->getLine());
|
|
||||||
|
|
||||||
$previous = $exception->getPrevious();
|
|
||||||
|
|
||||||
if ($previous instanceof \Throwable) {
|
|
||||||
$e->setPrevious(static::createFromThrowable($previous));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toArray()
|
|
||||||
{
|
|
||||||
$exceptions = [];
|
|
||||||
foreach (array_merge([$this], $this->getAllPrevious()) as $exception) {
|
|
||||||
$exceptions[] = [
|
|
||||||
'message' => $exception->getMessage(),
|
|
||||||
'class' => $exception->getClass(),
|
|
||||||
'trace' => $exception->getTrace(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $exceptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStatusCode()
|
|
||||||
{
|
|
||||||
return $this->statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setStatusCode($code)
|
|
||||||
{
|
|
||||||
$this->statusCode = $code;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHeaders()
|
|
||||||
{
|
|
||||||
return $this->headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setHeaders(array $headers)
|
|
||||||
{
|
|
||||||
$this->headers = $headers;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getClass()
|
|
||||||
{
|
|
||||||
return $this->class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setClass($class)
|
|
||||||
{
|
|
||||||
$this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFile()
|
|
||||||
{
|
|
||||||
return $this->file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setFile($file)
|
|
||||||
{
|
|
||||||
$this->file = $file;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLine()
|
|
||||||
{
|
|
||||||
return $this->line;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setLine($line)
|
|
||||||
{
|
|
||||||
$this->line = $line;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMessage()
|
|
||||||
{
|
|
||||||
return $this->message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setMessage($message)
|
|
||||||
{
|
|
||||||
if (false !== strpos($message, "class@anonymous\0")) {
|
|
||||||
$message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
|
|
||||||
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
|
|
||||||
}, $message);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->message = $message;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCode()
|
|
||||||
{
|
|
||||||
return $this->code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setCode($code)
|
|
||||||
{
|
|
||||||
$this->code = $code;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPrevious()
|
|
||||||
{
|
|
||||||
return $this->previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setPrevious(self $previous)
|
|
||||||
{
|
|
||||||
$this->previous = $previous;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllPrevious()
|
|
||||||
{
|
|
||||||
$exceptions = [];
|
|
||||||
$e = $this;
|
|
||||||
while ($e = $e->getPrevious()) {
|
|
||||||
$exceptions[] = $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $exceptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTrace()
|
|
||||||
{
|
|
||||||
return $this->trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated since 4.1, use {@see setTraceFromThrowable()} instead.
|
|
||||||
*/
|
|
||||||
public function setTraceFromException(\Exception $exception)
|
|
||||||
{
|
|
||||||
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use "setTraceFromThrowable()" instead.', __METHOD__), E_USER_DEPRECATED);
|
|
||||||
|
|
||||||
$this->setTraceFromThrowable($exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTraceFromThrowable(\Throwable $throwable)
|
|
||||||
{
|
|
||||||
$this->traceAsString = $throwable->getTraceAsString();
|
|
||||||
|
|
||||||
return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function setTrace($trace, $file, $line)
|
|
||||||
{
|
|
||||||
$this->trace = [];
|
|
||||||
$this->trace[] = [
|
|
||||||
'namespace' => '',
|
|
||||||
'short_class' => '',
|
|
||||||
'class' => '',
|
|
||||||
'type' => '',
|
|
||||||
'function' => '',
|
|
||||||
'file' => $file,
|
|
||||||
'line' => $line,
|
|
||||||
'args' => [],
|
|
||||||
];
|
|
||||||
foreach ($trace as $entry) {
|
|
||||||
$class = '';
|
|
||||||
$namespace = '';
|
|
||||||
if (isset($entry['class'])) {
|
|
||||||
$parts = explode('\\', $entry['class']);
|
|
||||||
$class = array_pop($parts);
|
|
||||||
$namespace = implode('\\', $parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->trace[] = [
|
|
||||||
'namespace' => $namespace,
|
|
||||||
'short_class' => $class,
|
|
||||||
'class' => isset($entry['class']) ? $entry['class'] : '',
|
|
||||||
'type' => isset($entry['type']) ? $entry['type'] : '',
|
|
||||||
'function' => isset($entry['function']) ? $entry['function'] : null,
|
|
||||||
'file' => isset($entry['file']) ? $entry['file'] : null,
|
|
||||||
'line' => isset($entry['line']) ? $entry['line'] : null,
|
|
||||||
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function flattenArgs($args, $level = 0, &$count = 0)
|
|
||||||
{
|
|
||||||
$result = [];
|
|
||||||
foreach ($args as $key => $value) {
|
|
||||||
if (++$count > 1e4) {
|
|
||||||
return ['array', '*SKIPPED over 10000 entries*'];
|
|
||||||
}
|
|
||||||
if ($value instanceof \__PHP_Incomplete_Class) {
|
|
||||||
// is_object() returns false on PHP<=7.1
|
|
||||||
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
|
|
||||||
} elseif (\is_object($value)) {
|
|
||||||
$result[$key] = ['object', \get_class($value)];
|
|
||||||
} elseif (\is_array($value)) {
|
|
||||||
if ($level > 10) {
|
|
||||||
$result[$key] = ['array', '*DEEP NESTED ARRAY*'];
|
|
||||||
} else {
|
|
||||||
$result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
|
|
||||||
}
|
|
||||||
} elseif (null === $value) {
|
|
||||||
$result[$key] = ['null', null];
|
|
||||||
} elseif (\is_bool($value)) {
|
|
||||||
$result[$key] = ['boolean', $value];
|
|
||||||
} elseif (\is_int($value)) {
|
|
||||||
$result[$key] = ['integer', $value];
|
|
||||||
} elseif (\is_float($value)) {
|
|
||||||
$result[$key] = ['float', $value];
|
|
||||||
} elseif (\is_resource($value)) {
|
|
||||||
$result[$key] = ['resource', get_resource_type($value)];
|
|
||||||
} else {
|
|
||||||
$result[$key] = ['string', (string) $value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
|
|
||||||
{
|
|
||||||
$array = new \ArrayObject($value);
|
|
||||||
|
|
||||||
return $array['__PHP_Incomplete_Class_Name'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTraceAsString()
|
|
||||||
{
|
|
||||||
return $this->traceAsString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAsString()
|
|
||||||
{
|
|
||||||
$message = '';
|
|
||||||
$next = false;
|
|
||||||
|
|
||||||
foreach (array_reverse(array_merge([$this], $this->getAllPrevious())) as $exception) {
|
|
||||||
if ($next) {
|
|
||||||
$message .= 'Next ';
|
|
||||||
} else {
|
|
||||||
$next = true;
|
|
||||||
}
|
|
||||||
$message .= $exception->getClass();
|
|
||||||
|
|
||||||
if ('' != $exception->getMessage()) {
|
|
||||||
$message .= ': '.$exception->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
$message .= ' in '.$exception->getFile().':'.$exception->getLine().
|
|
||||||
"\nStack trace:\n".$exception->getTraceAsString()."\n\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return rtrim($message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException as BaseOutOfMemoryException;
|
||||||
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', OutOfMemoryException::class, BaseOutOfMemoryException::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Out of memory exception.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException instead.
|
||||||
*
|
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
|
||||||
*/
|
*/
|
||||||
class OutOfMemoryException extends FatalErrorException
|
class OutOfMemoryException extends BaseOutOfMemoryException
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -11,57 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext as BaseSilencedErrorContext;
|
||||||
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', SilencedErrorContext::class, BaseSilencedErrorContext::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data Object that represents a Silenced Error.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext instead.
|
||||||
*
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*/
|
*/
|
||||||
class SilencedErrorContext implements \JsonSerializable
|
class SilencedErrorContext extends BaseSilencedErrorContext
|
||||||
{
|
{
|
||||||
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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,26 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException as BaseUndefinedFunctionException;
|
||||||
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionException::class, BaseUndefinedFunctionException::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Undefined Function Exception.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException instead.
|
||||||
*
|
|
||||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
|
||||||
*/
|
*/
|
||||||
class UndefinedFunctionException extends FatalErrorException
|
class UndefinedFunctionException extends BaseUndefinedFunctionException
|
||||||
{
|
{
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,26 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\Exception;
|
namespace Symfony\Component\Debug\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\UndefinedMethodException as BaseUndefinedMethodException;
|
||||||
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodException::class, BaseUndefinedMethodException::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Undefined Method Exception.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\UndefinedMethodException instead.
|
||||||
*
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*/
|
*/
|
||||||
class UndefinedMethodException extends FatalErrorException
|
class UndefinedMethodException extends BaseUndefinedMethodException
|
||||||
{
|
{
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -11,183 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||||
|
|
||||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler as BaseClassNotFoundFatalErrorHandler;
|
||||||
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
|
||||||
use Symfony\Component\Debug\DebugClassLoader;
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ClassNotFoundFatalErrorHandler::class, BaseClassNotFoundFatalErrorHandler::class), E_USER_DEPRECATED);
|
||||||
use Symfony\Component\Debug\Exception\ClassNotFoundException;
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ErrorHandler for classes that do not exist.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler instead.
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
*/
|
||||||
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
class ClassNotFoundFatalErrorHandler extends BaseClassNotFoundFatalErrorHandler
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* {@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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,22 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||||
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
use Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface as BaseFatalErrorHandlerInterface;
|
||||||
|
|
||||||
|
@trigger_error(sprintf('The "%s" interface is deprecated since Symfony 4.4, use "%s" instead.', FatalErrorHandlerInterface::class, BaseFatalErrorHandlerInterface::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to convert fatal errors to exceptions.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface instead.
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
*/
|
||||||
interface FatalErrorHandlerInterface
|
interface FatalErrorHandlerInterface extends BaseFatalErrorHandlerInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
@ -11,74 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||||
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler as BaseUndefinedFunctionFatalErrorHandler;
|
||||||
use Symfony\Component\Debug\Exception\UndefinedFunctionException;
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedFunctionFatalErrorHandler::class, BaseUndefinedFunctionFatalErrorHandler::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ErrorHandler for undefined functions.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler instead.
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
*/
|
||||||
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
|
class UndefinedFunctionFatalErrorHandler extends BaseUndefinedFunctionFatalErrorHandler
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* {@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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,56 +11,13 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Debug\FatalErrorHandler;
|
namespace Symfony\Component\Debug\FatalErrorHandler;
|
||||||
|
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler as BaseUndefinedMethodFatalErrorHandler;
|
||||||
use Symfony\Component\Debug\Exception\UndefinedMethodException;
|
|
||||||
|
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', UndefinedMethodFatalErrorHandler::class, BaseUndefinedMethodFatalErrorHandler::class), E_USER_DEPRECATED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ErrorHandler for undefined methods.
|
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler instead.
|
||||||
*
|
|
||||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
|
||||||
*/
|
*/
|
||||||
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
|
class UndefinedMethodFatalErrorHandler extends BaseUndefinedMethodFatalErrorHandler
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* {@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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Symfony_Component_Debug_Tests_Fixtures_PEARClass
|
|
||||||
{
|
|
||||||
}
|
|
@ -17,7 +17,8 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"psr/log": "~1.0"
|
"psr/log": "~1.0",
|
||||||
|
"symfony/error-handler": "^4.4"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/http-kernel": "<3.4"
|
"symfony/http-kernel": "<3.4"
|
||||||
|
3
src/Symfony/Component/ErrorHandler/.gitignore
vendored
Normal file
3
src/Symfony/Component/ErrorHandler/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
vendor/
|
||||||
|
composer.lock
|
||||||
|
phpunit.xml
|
37
src/Symfony/Component/ErrorHandler/BufferingLogger.php
Normal file
37
src/Symfony/Component/ErrorHandler/BufferingLogger.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
7
src/Symfony/Component/ErrorHandler/CHANGELOG.md
Normal file
7
src/Symfony/Component/ErrorHandler/CHANGELOG.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
4.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added the component
|
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\DependencyInjection;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||||
|
*/
|
||||||
|
class ErrorHandlerPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
private $rendererService;
|
||||||
|
private $rendererTag;
|
||||||
|
|
||||||
|
public function __construct(string $rendererService = 'error_handler.error_renderer', string $rendererTag = 'error_handler.renderer')
|
||||||
|
{
|
||||||
|
$this->rendererService = $rendererService;
|
||||||
|
$this->rendererTag = $rendererTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
if (!$container->hasDefinition($this->rendererService)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$renderers = $registered = [];
|
||||||
|
foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $serviceId => $tags) {
|
||||||
|
/** @var ErrorRendererInterface $class */
|
||||||
|
$class = $container->getDefinition($serviceId)->getClass();
|
||||||
|
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$format = $tag['format'] ?? $class::getFormat();
|
||||||
|
if (!isset($registered[$format])) {
|
||||||
|
$priority = $tag['priority'] ?? 0;
|
||||||
|
$renderers[$priority][$format] = new Reference($serviceId);
|
||||||
|
$registered[$format] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($renderers) {
|
||||||
|
krsort($renderers);
|
||||||
|
$renderers = array_merge(...$renderers);
|
||||||
|
}
|
||||||
|
|
||||||
|
$definition = $container->getDefinition($this->rendererService);
|
||||||
|
$definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\DependencyInjection;
|
||||||
|
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer as BaseErrorRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily loads error renderers from the dependency injection container.
|
||||||
|
*
|
||||||
|
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||||
|
*/
|
||||||
|
class ErrorRenderer extends BaseErrorRenderer
|
||||||
|
{
|
||||||
|
private $container;
|
||||||
|
private $initialized = [];
|
||||||
|
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function render($exception, string $format = 'html'): string
|
||||||
|
{
|
||||||
|
if (!isset($this->initialized[$format]) && $this->container->has($format)) {
|
||||||
|
$this->addRenderer($this->container->get($format), $format);
|
||||||
|
$this->initialized[$format] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::render($exception, $format);
|
||||||
|
}
|
||||||
|
}
|
716
src/Symfony/Component/ErrorHandler/ErrorHandler.php
Normal file
716
src/Symfony/Component/ErrorHandler/ErrorHandler.php
Normal file
@ -0,0 +1,716 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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\Debug\DebugClassLoader;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 <p@tchwork.com>
|
||||||
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||||
|
*
|
||||||
|
* @final
|
||||||
|
*/
|
||||||
|
class ErrorHandler
|
||||||
|
{
|
||||||
|
private $levels = [
|
||||||
|
E_COMPILE_ERROR => 'Compile Error',
|
||||||
|
E_COMPILE_WARNING => 'Compile Warning',
|
||||||
|
E_CORE_ERROR => 'Core Error',
|
||||||
|
E_CORE_WARNING => 'Core Warning',
|
||||||
|
E_DEPRECATED => 'Deprecated',
|
||||||
|
E_ERROR => 'Error',
|
||||||
|
E_NOTICE => 'Notice',
|
||||||
|
E_PARSE => 'Parse Error',
|
||||||
|
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
|
||||||
|
E_STRICT => 'Runtime Notice',
|
||||||
|
E_USER_DEPRECATED => 'User Deprecated',
|
||||||
|
E_USER_ERROR => 'User Error',
|
||||||
|
E_USER_NOTICE => 'User Notice',
|
||||||
|
E_USER_WARNING => 'User Warning',
|
||||||
|
E_WARNING => 'Warning',
|
||||||
|
];
|
||||||
|
|
||||||
|
private $loggers = [
|
||||||
|
E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
|
||||||
|
E_COMPILE_WARNING => [null, LogLevel::WARNING],
|
||||||
|
E_CORE_ERROR => [null, LogLevel::CRITICAL],
|
||||||
|
E_CORE_WARNING => [null, LogLevel::WARNING],
|
||||||
|
E_DEPRECATED => [null, LogLevel::INFO],
|
||||||
|
E_ERROR => [null, LogLevel::CRITICAL],
|
||||||
|
E_NOTICE => [null, LogLevel::WARNING],
|
||||||
|
E_PARSE => [null, LogLevel::CRITICAL],
|
||||||
|
E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
|
||||||
|
E_STRICT => [null, LogLevel::WARNING],
|
||||||
|
E_USER_DEPRECATED => [null, LogLevel::INFO],
|
||||||
|
E_USER_ERROR => [null, LogLevel::CRITICAL],
|
||||||
|
E_USER_NOTICE => [null, LogLevel::WARNING],
|
||||||
|
E_USER_WARNING => [null, LogLevel::WARNING],
|
||||||
|
E_WARNING => [null, LogLevel::WARNING],
|
||||||
|
];
|
||||||
|
|
||||||
|
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 = $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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\ErrorRendererNotFoundException;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an Exception that represents a Response content.
|
||||||
|
*
|
||||||
|
* @see ErrorRendererInterface
|
||||||
|
*
|
||||||
|
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||||
|
*/
|
||||||
|
class ErrorRenderer
|
||||||
|
{
|
||||||
|
private $renderers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ErrorRendererInterface[] $renderers
|
||||||
|
*/
|
||||||
|
public function __construct(array $renderers)
|
||||||
|
{
|
||||||
|
foreach ($renderers as $renderer) {
|
||||||
|
if (!$renderer instanceof ErrorRendererInterface) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('Error renderer "%s" must implement "%s".', \get_class($renderer), ErrorRendererInterface::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addRenderer($renderer, $renderer::getFormat());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRenderer(ErrorRendererInterface $renderer, string $format): self
|
||||||
|
{
|
||||||
|
$this->renderers[$format] = $renderer;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an Exception and returns the Response content.
|
||||||
|
*
|
||||||
|
* @param \Exception|FlattenException $exception An \Exception or FlattenException instance
|
||||||
|
* @param string $format The request format (html, json, xml, etc.)
|
||||||
|
*
|
||||||
|
* @return string The Response content as a string
|
||||||
|
*
|
||||||
|
* @throws ErrorRendererNotFoundException if no renderer is found
|
||||||
|
*/
|
||||||
|
public function render($exception, string $format = 'html'): string
|
||||||
|
{
|
||||||
|
if (!isset($this->renderers[$format])) {
|
||||||
|
throw new ErrorRendererNotFoundException(sprintf('No error renderer found for format "%s".', $format));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$exception instanceof FlattenException) {
|
||||||
|
$exception = FlattenException::create($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->renderers[$format]->render($exception);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface implemented by all error renderers.
|
||||||
|
*
|
||||||
|
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||||
|
*/
|
||||||
|
interface ErrorRendererInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets the format of the content.
|
||||||
|
*
|
||||||
|
* @return string The content format
|
||||||
|
*/
|
||||||
|
public static function getFormat(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an Exception and returns the Response content.
|
||||||
|
*
|
||||||
|
* @return string The Response content as a string
|
||||||
|
*/
|
||||||
|
public function render(FlattenException $exception): string;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||||
|
*/
|
||||||
|
class JsonErrorRenderer implements ErrorRendererInterface
|
||||||
|
{
|
||||||
|
private $debug;
|
||||||
|
|
||||||
|
public function __construct(bool $debug = true)
|
||||||
|
{
|
||||||
|
$this->debug = $debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function getFormat(): string
|
||||||
|
{
|
||||||
|
return 'json';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function render(FlattenException $exception): string
|
||||||
|
{
|
||||||
|
$content = [
|
||||||
|
'title' => $exception->getTitle(),
|
||||||
|
'status' => $exception->getStatusCode(),
|
||||||
|
'detail' => $exception->getMessage(),
|
||||||
|
];
|
||||||
|
if ($this->debug) {
|
||||||
|
$content['exceptions'] = $exception->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) json_encode($content);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||||
|
*/
|
||||||
|
class TxtErrorRenderer implements ErrorRendererInterface
|
||||||
|
{
|
||||||
|
private $debug;
|
||||||
|
private $charset;
|
||||||
|
|
||||||
|
public function __construct(bool $debug = true, string $charset = null)
|
||||||
|
{
|
||||||
|
$this->debug = $debug;
|
||||||
|
$this->charset = $charset ?: (ini_get('default_charset') ?: 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function getFormat(): string
|
||||||
|
{
|
||||||
|
return 'txt';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function render(FlattenException $exception): string
|
||||||
|
{
|
||||||
|
$content = sprintf("[title] %s\n", $exception->getTitle());
|
||||||
|
$content .= sprintf("[status] %s\n", $exception->getStatusCode());
|
||||||
|
$content .= sprintf("[detail] %s\n", $exception->getMessage());
|
||||||
|
|
||||||
|
if ($this->debug) {
|
||||||
|
foreach ($exception->toArray() as $i => $e) {
|
||||||
|
$content .= sprintf("[%d] %s: %s\n", $i + 1, $e['class'], $e['message']);
|
||||||
|
foreach ($e['trace'] as $trace) {
|
||||||
|
if ($trace['function']) {
|
||||||
|
$content .= sprintf('at %s%s%s(%s) ', $trace['class'], $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
|
||||||
|
}
|
||||||
|
if (isset($trace['file'], $trace['line'])) {
|
||||||
|
$content .= $this->formatPath($trace['file'], $trace['line']);
|
||||||
|
}
|
||||||
|
$content .= "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatPath(string $path, int $line): string
|
||||||
|
{
|
||||||
|
$file = preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path;
|
||||||
|
|
||||||
|
return sprintf('in %s %s', $path, 0 < $line ? ' line '.$line : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an array as a string.
|
||||||
|
*/
|
||||||
|
private function formatArgs(array $args): string
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($args as $key => $item) {
|
||||||
|
if ('object' === $item[0]) {
|
||||||
|
$formattedValue = sprintf('object(%s)', $item[1]);
|
||||||
|
} elseif ('array' === $item[0]) {
|
||||||
|
$formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
|
||||||
|
} elseif ('null' === $item[0]) {
|
||||||
|
$formattedValue = 'null';
|
||||||
|
} elseif ('boolean' === $item[0]) {
|
||||||
|
$formattedValue = strtolower(var_export($item[1], true));
|
||||||
|
} elseif ('resource' === $item[0]) {
|
||||||
|
$formattedValue = 'resource';
|
||||||
|
} else {
|
||||||
|
$formattedValue = str_replace("\n", '', var_export($item[1], true));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(', ', $result);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\ErrorRenderer;
|
||||||
|
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||||
|
*/
|
||||||
|
class XmlErrorRenderer implements ErrorRendererInterface
|
||||||
|
{
|
||||||
|
private $debug;
|
||||||
|
private $charset;
|
||||||
|
|
||||||
|
public function __construct(bool $debug = true, string $charset = null)
|
||||||
|
{
|
||||||
|
$this->debug = $debug;
|
||||||
|
$this->charset = $charset ?: (ini_get('default_charset') ?: 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function getFormat(): string
|
||||||
|
{
|
||||||
|
return 'xml';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function render(FlattenException $exception): string
|
||||||
|
{
|
||||||
|
$message = $this->escapeXml($exception->getMessage());
|
||||||
|
|
||||||
|
$exceptions = '';
|
||||||
|
if ($this->debug) {
|
||||||
|
$exceptions .= '<exceptions>';
|
||||||
|
foreach ($exception->toArray() as $e) {
|
||||||
|
$exceptions .= sprintf('<exception class="%s" message="%s"><traces>', $e['class'], $this->escapeXml($e['message']));
|
||||||
|
foreach ($e['trace'] as $trace) {
|
||||||
|
$exceptions .= '<trace>';
|
||||||
|
if ($trace['function']) {
|
||||||
|
$exceptions .= sprintf('at %s%s%s(%s) ', $trace['class'], $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
|
||||||
|
}
|
||||||
|
if (isset($trace['file'], $trace['line'])) {
|
||||||
|
$exceptions .= $this->formatPath($trace['file'], $trace['line']);
|
||||||
|
}
|
||||||
|
$exceptions .= '</trace>';
|
||||||
|
}
|
||||||
|
$exceptions .= '</traces></exception>';
|
||||||
|
}
|
||||||
|
$exceptions .= '</exceptions>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return <<<EOF
|
||||||
|
<?xml version="1.0" encoding="{$this->charset}" ?>
|
||||||
|
<problem xmlns="urn:ietf:rfc:7807">
|
||||||
|
<title>{$exception->getTitle()}</title>
|
||||||
|
<status>{$exception->getStatusCode()}</status>
|
||||||
|
<detail>{$message}</detail>
|
||||||
|
{$exceptions}
|
||||||
|
</problem>
|
||||||
|
EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML-encodes a string.
|
||||||
|
*/
|
||||||
|
private function escapeXml(string $str): string
|
||||||
|
{
|
||||||
|
return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatPath(string $path, int $line): string
|
||||||
|
{
|
||||||
|
$file = $this->escapeXml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
|
||||||
|
|
||||||
|
return sprintf('in %s %s', $this->escapeXml($path), 0 < $line ? ' line '.$line : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an array as a string.
|
||||||
|
*/
|
||||||
|
private function formatArgs(array $args): string
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($args as $key => $item) {
|
||||||
|
if ('object' === $item[0]) {
|
||||||
|
$formattedValue = sprintf('object(%s)', $item[1]);
|
||||||
|
} elseif ('array' === $item[0]) {
|
||||||
|
$formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
|
||||||
|
} elseif ('null' === $item[0]) {
|
||||||
|
$formattedValue = 'null';
|
||||||
|
} elseif ('boolean' === $item[0]) {
|
||||||
|
$formattedValue = strtolower(var_export($item[1], true));
|
||||||
|
} elseif ('resource' === $item[0]) {
|
||||||
|
$formattedValue = 'resource';
|
||||||
|
} else {
|
||||||
|
$formattedValue = str_replace("\n", '', $this->escapeXml(var_export($item[1], true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeXml($key), $formattedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(', ', $result);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <koc-dp@yandex.ru>
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 ErrorRendererNotFoundException extends \RuntimeException
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <koc-dp@yandex.ru>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,380 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\Exception;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FlattenException wraps a PHP Error or Exception to be able to serialize it.
|
||||||
|
*
|
||||||
|
* Basically, this class removes all objects from the trace.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class FlattenException
|
||||||
|
{
|
||||||
|
private $title;
|
||||||
|
private $message;
|
||||||
|
private $code;
|
||||||
|
private $previous;
|
||||||
|
private $trace;
|
||||||
|
private $traceAsString;
|
||||||
|
private $class;
|
||||||
|
private $statusCode;
|
||||||
|
private $headers;
|
||||||
|
private $file;
|
||||||
|
private $line;
|
||||||
|
|
||||||
|
public static function create(\Exception $exception, $statusCode = null, array $headers = [])
|
||||||
|
{
|
||||||
|
return static::createFromThrowable($exception, $statusCode, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createFromThrowable(\Throwable $exception, ?int $statusCode = null, array $headers = []): self
|
||||||
|
{
|
||||||
|
$e = new static();
|
||||||
|
$e->setMessage($exception->getMessage());
|
||||||
|
$e->setCode($exception->getCode());
|
||||||
|
|
||||||
|
if ($exception instanceof HttpExceptionInterface) {
|
||||||
|
$statusCode = $exception->getStatusCode();
|
||||||
|
$headers = array_merge($headers, $exception->getHeaders());
|
||||||
|
} elseif ($exception instanceof RequestExceptionInterface) {
|
||||||
|
$statusCode = 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $statusCode) {
|
||||||
|
$statusCode = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) {
|
||||||
|
$title = Response::$statusTexts[$statusCode];
|
||||||
|
} else {
|
||||||
|
$title = 'Whoops, looks like something went wrong.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$e->setTitle($title);
|
||||||
|
$e->setStatusCode($statusCode);
|
||||||
|
$e->setHeaders($headers);
|
||||||
|
$e->setTraceFromThrowable($exception);
|
||||||
|
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
|
||||||
|
$e->setFile($exception->getFile());
|
||||||
|
$e->setLine($exception->getLine());
|
||||||
|
|
||||||
|
$previous = $exception->getPrevious();
|
||||||
|
|
||||||
|
if ($previous instanceof \Throwable) {
|
||||||
|
$e->setPrevious(static::createFromThrowable($previous));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray()
|
||||||
|
{
|
||||||
|
$exceptions = [];
|
||||||
|
foreach (array_merge([$this], $this->getAllPrevious()) as $exception) {
|
||||||
|
$exceptions[] = [
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'class' => $exception->getClass(),
|
||||||
|
'trace' => $exception->getTrace(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $exceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatusCode()
|
||||||
|
{
|
||||||
|
return $this->statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setStatusCode($code)
|
||||||
|
{
|
||||||
|
$this->statusCode = $code;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeaders()
|
||||||
|
{
|
||||||
|
return $this->headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setHeaders(array $headers)
|
||||||
|
{
|
||||||
|
$this->headers = $headers;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClass()
|
||||||
|
{
|
||||||
|
return $this->class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setClass($class)
|
||||||
|
{
|
||||||
|
$this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile()
|
||||||
|
{
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setFile($file)
|
||||||
|
{
|
||||||
|
$this->file = $file;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLine()
|
||||||
|
{
|
||||||
|
return $this->line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setLine($line)
|
||||||
|
{
|
||||||
|
$this->line = $line;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTitle(string $title): self
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessage()
|
||||||
|
{
|
||||||
|
return $this->message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMessage($message)
|
||||||
|
{
|
||||||
|
if (false !== strpos($message, "class@anonymous\0")) {
|
||||||
|
$message = preg_replace_callback('/class@anonymous\x00.*?\.php0x?[0-9a-fA-F]++/', function ($m) {
|
||||||
|
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
|
||||||
|
}, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->message = $message;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode()
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setCode($code)
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrevious()
|
||||||
|
{
|
||||||
|
return $this->previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setPrevious(self $previous)
|
||||||
|
{
|
||||||
|
$this->previous = $previous;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllPrevious()
|
||||||
|
{
|
||||||
|
$exceptions = [];
|
||||||
|
$e = $this;
|
||||||
|
while ($e = $e->getPrevious()) {
|
||||||
|
$exceptions[] = $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $exceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTrace()
|
||||||
|
{
|
||||||
|
return $this->trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.1, use {@see setTraceFromThrowable()} instead.
|
||||||
|
*/
|
||||||
|
public function setTraceFromException(\Exception $exception)
|
||||||
|
{
|
||||||
|
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use "setTraceFromThrowable()" instead.', __METHOD__), E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
$this->setTraceFromThrowable($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTraceFromThrowable(\Throwable $throwable)
|
||||||
|
{
|
||||||
|
$this->traceAsString = $throwable->getTraceAsString();
|
||||||
|
|
||||||
|
return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTrace($trace, $file, $line)
|
||||||
|
{
|
||||||
|
$this->trace = [];
|
||||||
|
$this->trace[] = [
|
||||||
|
'namespace' => '',
|
||||||
|
'short_class' => '',
|
||||||
|
'class' => '',
|
||||||
|
'type' => '',
|
||||||
|
'function' => '',
|
||||||
|
'file' => $file,
|
||||||
|
'line' => $line,
|
||||||
|
'args' => [],
|
||||||
|
];
|
||||||
|
foreach ($trace as $entry) {
|
||||||
|
$class = '';
|
||||||
|
$namespace = '';
|
||||||
|
if (isset($entry['class'])) {
|
||||||
|
$parts = explode('\\', $entry['class']);
|
||||||
|
$class = array_pop($parts);
|
||||||
|
$namespace = implode('\\', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->trace[] = [
|
||||||
|
'namespace' => $namespace,
|
||||||
|
'short_class' => $class,
|
||||||
|
'class' => isset($entry['class']) ? $entry['class'] : '',
|
||||||
|
'type' => isset($entry['type']) ? $entry['type'] : '',
|
||||||
|
'function' => isset($entry['function']) ? $entry['function'] : null,
|
||||||
|
'file' => isset($entry['file']) ? $entry['file'] : null,
|
||||||
|
'line' => isset($entry['line']) ? $entry['line'] : null,
|
||||||
|
'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function flattenArgs($args, $level = 0, &$count = 0)
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($args as $key => $value) {
|
||||||
|
if (++$count > 1e4) {
|
||||||
|
return ['array', '*SKIPPED over 10000 entries*'];
|
||||||
|
}
|
||||||
|
if ($value instanceof \__PHP_Incomplete_Class) {
|
||||||
|
// is_object() returns false on PHP<=7.1
|
||||||
|
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
|
||||||
|
} elseif (\is_object($value)) {
|
||||||
|
$result[$key] = ['object', \get_class($value)];
|
||||||
|
} elseif (\is_array($value)) {
|
||||||
|
if ($level > 10) {
|
||||||
|
$result[$key] = ['array', '*DEEP NESTED ARRAY*'];
|
||||||
|
} else {
|
||||||
|
$result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
|
||||||
|
}
|
||||||
|
} elseif (null === $value) {
|
||||||
|
$result[$key] = ['null', null];
|
||||||
|
} elseif (\is_bool($value)) {
|
||||||
|
$result[$key] = ['boolean', $value];
|
||||||
|
} elseif (\is_int($value)) {
|
||||||
|
$result[$key] = ['integer', $value];
|
||||||
|
} elseif (\is_float($value)) {
|
||||||
|
$result[$key] = ['float', $value];
|
||||||
|
} elseif (\is_resource($value)) {
|
||||||
|
$result[$key] = ['resource', get_resource_type($value)];
|
||||||
|
} else {
|
||||||
|
$result[$key] = ['string', (string) $value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value)
|
||||||
|
{
|
||||||
|
$array = new \ArrayObject($value);
|
||||||
|
|
||||||
|
return $array['__PHP_Incomplete_Class_Name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTraceAsString()
|
||||||
|
{
|
||||||
|
return $this->traceAsString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAsString()
|
||||||
|
{
|
||||||
|
$message = '';
|
||||||
|
$next = false;
|
||||||
|
|
||||||
|
foreach (array_reverse(array_merge([$this], $this->getAllPrevious())) as $exception) {
|
||||||
|
if ($next) {
|
||||||
|
$message .= 'Next ';
|
||||||
|
} else {
|
||||||
|
$next = true;
|
||||||
|
}
|
||||||
|
$message .= $exception->getClass();
|
||||||
|
|
||||||
|
if ('' != $exception->getMessage()) {
|
||||||
|
$message .= ': '.$exception->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$message .= ' in '.$exception->getFile().':'.$exception->getLine().
|
||||||
|
"\nStack trace:\n".$exception->getTraceAsString()."\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtrim($message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class OutOfMemoryException extends FatalErrorException
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <lyrixx@lyrixx.info>
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <koc-dp@yandex.ru>
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <lyrixx@lyrixx.info>
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
177
src/Symfony/Component/ErrorHandler/ExceptionHandler.php
Normal file
177
src/Symfony/Component/ErrorHandler/ExceptionHandler.php
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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\ErrorRenderer\HtmlErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException;
|
||||||
|
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 <fabien@symfony.com>
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*
|
||||||
|
* @final
|
||||||
|
*/
|
||||||
|
class ExceptionHandler
|
||||||
|
{
|
||||||
|
private $charset;
|
||||||
|
private $errorRenderer;
|
||||||
|
private $handler;
|
||||||
|
private $caughtBuffer;
|
||||||
|
private $caughtLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(bool $debug = true, string $charset = null, $fileLinkFormat = null, HtmlErrorRenderer $errorRenderer = null)
|
||||||
|
{
|
||||||
|
$this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
|
||||||
|
$this->errorRenderer = $errorRenderer ?? new HtmlErrorRenderer($debug, $this->charset, $fileLinkFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
return $this->errorRenderer->setFileLinkFormat($fileLinkFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 \Exception|FlattenException $exception An \Exception or FlattenException instance
|
||||||
|
*/
|
||||||
|
public function sendPhpResponse($exception)
|
||||||
|
{
|
||||||
|
if (!$exception instanceof FlattenException) {
|
||||||
|
$exception = FlattenException::create($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);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $this->errorRenderer->render($exception);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,193 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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\Debug\DebugClassLoader;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\ClassNotFoundException;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ErrorHandler for classes that do not exist.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <lyrixx@lyrixx.info>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
19
src/Symfony/Component/ErrorHandler/LICENSE
Normal file
19
src/Symfony/Component/ErrorHandler/LICENSE
Normal file
@ -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.
|
12
src/Symfony/Component/ErrorHandler/README.md
Normal file
12
src/Symfony/Component/ErrorHandler/README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
ErrorHandler Component
|
||||||
|
======================
|
||||||
|
|
||||||
|
The ErrorHandler component provides tools to manage and display errors and exceptions.
|
||||||
|
|
||||||
|
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)
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\Tests\DependencyInjection;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||||
|
use Symfony\Component\ErrorHandler\DependencyInjection\ErrorHandlerPass;
|
||||||
|
use Symfony\Component\ErrorHandler\DependencyInjection\ErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\JsonErrorRenderer;
|
||||||
|
|
||||||
|
class ErrorHandlerPassTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testProcess()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->setParameter('kernel.debug', true);
|
||||||
|
$definition = $container->register('error_handler.error_renderer', ErrorRenderer::class)
|
||||||
|
->addArgument([])
|
||||||
|
;
|
||||||
|
$container->register('error_handler.renderer.html', HtmlErrorRenderer::class)
|
||||||
|
->addTag('error_handler.renderer')
|
||||||
|
;
|
||||||
|
$container->register('error_handler.renderer.json', JsonErrorRenderer::class)
|
||||||
|
->addTag('error_handler.renderer')
|
||||||
|
;
|
||||||
|
|
||||||
|
(new ErrorHandlerPass())->process($container);
|
||||||
|
|
||||||
|
$serviceLocatorDefinition = $container->getDefinition((string) $definition->getArgument(0));
|
||||||
|
$this->assertSame(ServiceLocator::class, $serviceLocatorDefinition->getClass());
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'html' => new ServiceClosureArgument(new Reference('error_handler.renderer.html')),
|
||||||
|
'json' => new ServiceClosureArgument(new Reference('error_handler.renderer.json')),
|
||||||
|
];
|
||||||
|
$this->assertEquals($expected, $serviceLocatorDefinition->getArgument(0));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\Tests\DependencyInjection;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\ErrorHandler\DependencyInjection\ErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
class ErrorRendererTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\ErrorHandler\Exception\ErrorRendererNotFoundException
|
||||||
|
* @expectedExceptionMessage No error renderer found for format "foo".
|
||||||
|
*/
|
||||||
|
public function testInvalidErrorRenderer()
|
||||||
|
{
|
||||||
|
$container = $this->getMockBuilder('Psr\Container\ContainerInterface')->getMock();
|
||||||
|
$container->expects($this->once())->method('has')->with('foo')->willReturn(false);
|
||||||
|
|
||||||
|
$exception = FlattenException::create(new \Exception('Foo'));
|
||||||
|
(new ErrorRenderer($container))->render($exception, 'foo');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustomErrorRenderer()
|
||||||
|
{
|
||||||
|
$container = $this->getMockBuilder('Psr\Container\ContainerInterface')->getMock();
|
||||||
|
$container
|
||||||
|
->expects($this->once())
|
||||||
|
->method('has')
|
||||||
|
->with('foo')
|
||||||
|
->willReturn(true)
|
||||||
|
;
|
||||||
|
$container
|
||||||
|
->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->willReturn(new FooErrorRenderer())
|
||||||
|
;
|
||||||
|
|
||||||
|
$errorRenderer = new ErrorRenderer($container);
|
||||||
|
|
||||||
|
$exception = FlattenException::create(new \RuntimeException('Foo'));
|
||||||
|
$this->assertSame('Foo', $errorRenderer->render($exception, 'foo'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FooErrorRenderer implements ErrorRendererInterface
|
||||||
|
{
|
||||||
|
public static function getFormat(): string
|
||||||
|
{
|
||||||
|
return 'foo';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(FlattenException $exception): string
|
||||||
|
{
|
||||||
|
return $exception->getMessage();
|
||||||
|
}
|
||||||
|
}
|
@ -9,16 +9,16 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests;
|
namespace Symfony\Component\ErrorHandler\Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
use Symfony\Component\Debug\BufferingLogger;
|
use Symfony\Component\ErrorHandler\BufferingLogger;
|
||||||
use Symfony\Component\Debug\ErrorHandler;
|
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||||
use Symfony\Component\Debug\Exception\SilencedErrorContext;
|
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
|
||||||
use Symfony\Component\Debug\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
|
use Symfony\Component\ErrorHandler\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
|
||||||
use Symfony\Component\Debug\Tests\Fixtures\LoggerThatSetAnErrorHandler;
|
use Symfony\Component\ErrorHandler\Tests\Fixtures\LoggerThatSetAnErrorHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ErrorHandlerTest.
|
* ErrorHandlerTest.
|
||||||
@ -33,7 +33,7 @@ class ErrorHandlerTest extends TestCase
|
|||||||
$handler = ErrorHandler::register();
|
$handler = ErrorHandler::register();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
|
$this->assertInstanceOf('Symfony\Component\ErrorHandler\ErrorHandler', $handler);
|
||||||
$this->assertSame($handler, ErrorHandler::register());
|
$this->assertSame($handler, ErrorHandler::register());
|
||||||
|
|
||||||
$newHandler = new ErrorHandler();
|
$newHandler = new ErrorHandler();
|
||||||
@ -151,21 +151,21 @@ class ErrorHandlerTest extends TestCase
|
|||||||
$handler->setDefaultLogger($logger, [E_USER_NOTICE => LogLevel::CRITICAL]);
|
$handler->setDefaultLogger($logger, [E_USER_NOTICE => LogLevel::CRITICAL]);
|
||||||
|
|
||||||
$loggers = [
|
$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_COMPILE_ERROR => [null, LogLevel::CRITICAL],
|
||||||
E_PARSE => [null, LogLevel::CRITICAL],
|
E_COMPILE_WARNING => [null, LogLevel::WARNING],
|
||||||
E_ERROR => [null, LogLevel::CRITICAL],
|
|
||||||
E_CORE_ERROR => [null, LogLevel::CRITICAL],
|
E_CORE_ERROR => [null, LogLevel::CRITICAL],
|
||||||
|
E_CORE_WARNING => [null, LogLevel::WARNING],
|
||||||
|
E_DEPRECATED => [null, LogLevel::INFO],
|
||||||
|
E_ERROR => [null, LogLevel::CRITICAL],
|
||||||
|
E_NOTICE => [$logger, LogLevel::WARNING],
|
||||||
|
E_PARSE => [null, LogLevel::CRITICAL],
|
||||||
|
E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
|
||||||
|
E_STRICT => [null, LogLevel::WARNING],
|
||||||
|
E_USER_DEPRECATED => [null, LogLevel::INFO],
|
||||||
|
E_USER_ERROR => [null, LogLevel::CRITICAL],
|
||||||
|
E_USER_NOTICE => [$logger, LogLevel::CRITICAL],
|
||||||
|
E_USER_WARNING => [null, LogLevel::WARNING],
|
||||||
|
E_WARNING => [null, LogLevel::WARNING],
|
||||||
];
|
];
|
||||||
$this->assertSame($loggers, $handler->setLoggers([]));
|
$this->assertSame($loggers, $handler->setLoggers([]));
|
||||||
} finally {
|
} finally {
|
||||||
@ -375,21 +375,21 @@ class ErrorHandlerTest extends TestCase
|
|||||||
$handler = new ErrorHandler($bootLogger);
|
$handler = new ErrorHandler($bootLogger);
|
||||||
|
|
||||||
$loggers = [
|
$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_COMPILE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
E_PARSE => [$bootLogger, LogLevel::CRITICAL],
|
E_COMPILE_WARNING => [$bootLogger, LogLevel::WARNING],
|
||||||
E_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
|
||||||
E_CORE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
E_CORE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_CORE_WARNING => [$bootLogger, LogLevel::WARNING],
|
||||||
|
E_DEPRECATED => [$bootLogger, LogLevel::INFO],
|
||||||
|
E_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_NOTICE => [$bootLogger, LogLevel::WARNING],
|
||||||
|
E_PARSE => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_RECOVERABLE_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_STRICT => [$bootLogger, LogLevel::WARNING],
|
||||||
|
E_USER_DEPRECATED => [$bootLogger, LogLevel::INFO],
|
||||||
|
E_USER_ERROR => [$bootLogger, LogLevel::CRITICAL],
|
||||||
|
E_USER_NOTICE => [$bootLogger, LogLevel::WARNING],
|
||||||
|
E_USER_WARNING => [$bootLogger, LogLevel::WARNING],
|
||||||
|
E_WARNING => [$bootLogger, LogLevel::WARNING],
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->assertSame($loggers, $handler->setLoggers([]));
|
$this->assertSame($loggers, $handler->setLoggers([]));
|
||||||
@ -490,7 +490,7 @@ class ErrorHandlerTest extends TestCase
|
|||||||
|
|
||||||
$handler->handleException($exception);
|
$handler->handleException($exception);
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]);
|
$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());
|
$this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\Tests\ErrorRenderer;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
class ErrorRendererTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\ErrorHandler\Exception\ErrorRendererNotFoundException
|
||||||
|
* @expectedExceptionMessage No error renderer found for format "foo".
|
||||||
|
*/
|
||||||
|
public function testErrorRendererNotFound()
|
||||||
|
{
|
||||||
|
$exception = FlattenException::create(new \Exception('foo'));
|
||||||
|
(new ErrorRenderer([]))->render($exception, 'foo');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Error renderer "stdClass" must implement "Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface".
|
||||||
|
*/
|
||||||
|
public function testInvalidErrorRenderer()
|
||||||
|
{
|
||||||
|
$exception = FlattenException::create(new \Exception('foo'));
|
||||||
|
(new ErrorRenderer([new \stdClass()]))->render($exception, 'foo');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustomErrorRenderer()
|
||||||
|
{
|
||||||
|
$renderers = [new FooErrorRenderer()];
|
||||||
|
$errorRenderer = new ErrorRenderer($renderers);
|
||||||
|
|
||||||
|
$exception = FlattenException::create(new \RuntimeException('Foo'));
|
||||||
|
$this->assertSame('Foo', $errorRenderer->render($exception, 'foo'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FooErrorRenderer implements ErrorRendererInterface
|
||||||
|
{
|
||||||
|
public static function getFormat(): string
|
||||||
|
{
|
||||||
|
return 'foo';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(FlattenException $exception): string
|
||||||
|
{
|
||||||
|
return $exception->getMessage();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\Tests\ErrorRenderer;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
class HtmlErrorRendererTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testRender()
|
||||||
|
{
|
||||||
|
$exception = FlattenException::create(new \RuntimeException('Foo'));
|
||||||
|
$expected = '<!DOCTYPE html>%A<html>%A<head>%A<title>Internal Server Error</title>%A<h1 class="break-long-words exception-message">Foo</h1>%A<abbr title="RuntimeException">RuntimeException</abbr>%A';
|
||||||
|
|
||||||
|
$this->assertStringMatchesFormat($expected, (new HtmlErrorRenderer())->render($exception));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\Tests\ErrorRenderer;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\JsonErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
class JsonErrorRendererTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testRender()
|
||||||
|
{
|
||||||
|
$exception = FlattenException::create(new \RuntimeException('Foo'));
|
||||||
|
$expected = '{"title":"Internal Server Error","status":500,"detail":"Foo","exceptions":[{"message":"Foo","class":"RuntimeException","trace":';
|
||||||
|
|
||||||
|
$this->assertStringStartsWith($expected, (new JsonErrorRenderer())->render($exception));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\Tests\ErrorRenderer;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\TxtErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
class TxtErrorRendererTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testRender()
|
||||||
|
{
|
||||||
|
$exception = FlattenException::create(new \RuntimeException('Foo'));
|
||||||
|
$expected = '[title] Internal Server Error%A[status] 500%A[detail] Foo%A[1] RuntimeException: Foo%A';
|
||||||
|
|
||||||
|
$this->assertStringMatchesFormat($expected, (new TxtErrorRenderer())->render($exception));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\Tests\ErrorRenderer;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\XmlErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
|
class XmlErrorRendererTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testRender()
|
||||||
|
{
|
||||||
|
$exception = FlattenException::create(new \RuntimeException('Foo'));
|
||||||
|
$expected = '<?xml version="1.0" encoding="UTF-8" ?>%A<problem xmlns="urn:ietf:rfc:7807">%A<title>Internal Server Error</title>%A<status>500</status>%A<detail>Foo</detail>%A';
|
||||||
|
|
||||||
|
$this->assertStringMatchesFormat($expected, (new XmlErrorRenderer())->render($exception));
|
||||||
|
}
|
||||||
|
}
|
@ -9,11 +9,11 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Exception;
|
namespace Symfony\Component\ErrorHandler\Tests\Exception;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
@ -9,11 +9,11 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests;
|
namespace Symfony\Component\ErrorHandler\Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Debug\Exception\OutOfMemoryException;
|
use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException;
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
use Symfony\Component\ErrorHandler\ExceptionHandler;
|
||||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ class ExceptionHandlerTest extends TestCase
|
|||||||
$handler->sendPhpResponse(new \RuntimeException('Foo'));
|
$handler->sendPhpResponse(new \RuntimeException('Foo'));
|
||||||
$response = ob_get_clean();
|
$response = ob_get_clean();
|
||||||
|
|
||||||
$this->assertContains('Whoops, looks like something went wrong.', $response);
|
$this->assertContains('The server returned a "500 Internal Server Error".', $response);
|
||||||
$this->assertNotContains('<div class="trace trace-as-html">', $response);
|
$this->assertNotContains('<div class="trace trace-as-html">', $response);
|
||||||
|
|
||||||
$handler = new ExceptionHandler(true);
|
$handler = new ExceptionHandler(true);
|
||||||
@ -69,7 +69,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
|
|||||||
$handler->sendPhpResponse(new NotFoundHttpException('Foo'));
|
$handler->sendPhpResponse(new NotFoundHttpException('Foo'));
|
||||||
$response = ob_get_clean();
|
$response = ob_get_clean();
|
||||||
|
|
||||||
$this->assertContains('Sorry, the page you are looking for could not be found.', $response);
|
$this->assertContains('The server returned a "404 Not Found".', $response);
|
||||||
|
|
||||||
$expectedHeaders = [
|
$expectedHeaders = [
|
||||||
['HTTP/1.0 404', true, null],
|
['HTTP/1.0 404', true, null],
|
||||||
@ -110,7 +110,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
|
|||||||
{
|
{
|
||||||
$exception = new \Exception('foo');
|
$exception = new \Exception('foo');
|
||||||
|
|
||||||
$handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
$handler = $this->getMockBuilder('Symfony\Component\ErrorHandler\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
||||||
$handler
|
$handler
|
||||||
->expects($this->exactly(2))
|
->expects($this->exactly(2))
|
||||||
->method('sendPhpResponse');
|
->method('sendPhpResponse');
|
||||||
@ -128,7 +128,7 @@ content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg"
|
|||||||
{
|
{
|
||||||
$exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
|
$exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__);
|
||||||
|
|
||||||
$handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
$handler = $this->getMockBuilder('Symfony\Component\ErrorHandler\ExceptionHandler')->setMethods(['sendPhpResponse'])->getMock();
|
||||||
$handler
|
$handler
|
||||||
->expects($this->once())
|
->expects($this->once())
|
||||||
->method('sendPhpResponse');
|
->method('sendPhpResponse');
|
@ -9,13 +9,13 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
namespace Symfony\Component\ErrorHandler\Tests\FatalErrorHandler;
|
||||||
|
|
||||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Debug\DebugClassLoader;
|
use Symfony\Component\Debug\DebugClassLoader;
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
||||||
|
|
||||||
class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -32,7 +32,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($function[0] instanceof ComposerClassLoader) {
|
if ($function[0] instanceof ComposerClassLoader) {
|
||||||
$function[0]->add('Symfony_Component_Debug_Tests_Fixtures', \dirname(\dirname(\dirname(\dirname(\dirname(__DIR__))))));
|
$function[0]->add('Symfony_Component_ErrorHandler_Tests_Fixtures', \dirname(\dirname(\dirname(\dirname(\dirname(__DIR__))))));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
array_map('spl_autoload_register', $autoloaders);
|
array_map('spl_autoload_register', $autoloaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
|
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $exception);
|
||||||
$this->assertSame($translatedMessage, $exception->getMessage());
|
$this->assertSame($translatedMessage, $exception->getMessage());
|
||||||
$this->assertSame($error['type'], $exception->getSeverity());
|
$this->assertSame($error['type'], $exception->getSeverity());
|
||||||
$this->assertSame($error['file'], $exception->getFile());
|
$this->assertSame($error['file'], $exception->getFile());
|
||||||
@ -70,7 +70,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
public function provideClassNotFoundData()
|
public function provideClassNotFoundData()
|
||||||
{
|
{
|
||||||
$autoloader = new ComposerClassLoader();
|
$autoloader = new ComposerClassLoader();
|
||||||
$autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception'));
|
$autoloader->add('Symfony\Component\ErrorHandler\Exception\\', realpath(__DIR__.'/../../Exception'));
|
||||||
|
|
||||||
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
|
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
|
||||||
|
|
||||||
@ -98,9 +98,9 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
'type' => 1,
|
'type' => 1,
|
||||||
'line' => 12,
|
'line' => 12,
|
||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'UndefinedFunctionException\' not found',
|
'message' => 'Class \'UndefinedFuncException\' not found',
|
||||||
],
|
],
|
||||||
"Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?",
|
"Attempted to load class \"UndefinedFuncException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Tests\Fixtures\UndefinedFuncException\"?",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
@ -109,7 +109,16 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'PEARClass\' not found',
|
'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\"?",
|
"Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_ErrorHandler_Tests_Fixtures_PEARClass\"?",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'type' => 1,
|
||||||
|
'line' => 12,
|
||||||
|
'file' => 'foo.php',
|
||||||
|
'message' => 'Class \'Foo\\Bar\\UndefinedFuncException\' not found',
|
||||||
|
],
|
||||||
|
"Attempted to load class \"UndefinedFuncException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Tests\Fixtures\UndefinedFuncException\"?",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
@ -118,16 +127,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
'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\"?",
|
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
|
||||||
],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
'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\Debug\Exception\UndefinedFunctionException\"?",
|
|
||||||
[$autoloader, 'loadClass'],
|
[$autoloader, 'loadClass'],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -137,7 +137,7 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
'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\"?",
|
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
|
||||||
[$debugClassLoader, 'loadClass'],
|
[$debugClassLoader, 'loadClass'],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -171,6 +171,6 @@ class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
|||||||
$handler = new ClassNotFoundFatalErrorHandler();
|
$handler = new ClassNotFoundFatalErrorHandler();
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception);
|
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,11 +9,11 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
namespace Symfony\Component\ErrorHandler\Tests\FatalErrorHandler;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
||||||
|
|
||||||
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -25,7 +25,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
|||||||
$handler = new UndefinedFunctionFatalErrorHandler();
|
$handler = new UndefinedFunctionFatalErrorHandler();
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedFunctionException', $exception);
|
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException', $exception);
|
||||||
// class names are case insensitive and PHP do not return the same
|
// class names are case insensitive and PHP do not return the same
|
||||||
$this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
|
$this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
|
||||||
$this->assertSame($error['type'], $exception->getSeverity());
|
$this->assertSame($error['type'], $exception->getSeverity());
|
||||||
@ -43,7 +43,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Call to undefined function test_namespaced_function()',
|
'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\\debug\\tests\\fatalerrorhandler\\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\"?",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
@ -52,7 +52,7 @@ class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
|||||||
'file' => 'foo.php',
|
'file' => 'foo.php',
|
||||||
'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
|
'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\\debug\\tests\\fatalerrorhandler\\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\"?",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[
|
[
|
@ -9,11 +9,11 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\FatalErrorHandler;
|
namespace Symfony\Component\ErrorHandler\Tests\FatalErrorHandler;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Debug\Exception\FatalErrorException;
|
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||||
use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
||||||
|
|
||||||
class UndefinedMethodFatalErrorHandlerTest extends TestCase
|
class UndefinedMethodFatalErrorHandlerTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -25,7 +25,7 @@ class UndefinedMethodFatalErrorHandlerTest extends TestCase
|
|||||||
$handler = new UndefinedMethodFatalErrorHandler();
|
$handler = new UndefinedMethodFatalErrorHandler();
|
||||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||||
|
|
||||||
$this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedMethodException', $exception);
|
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\UndefinedMethodException', $exception);
|
||||||
$this->assertSame($translatedMessage, $exception->getMessage());
|
$this->assertSame($translatedMessage, $exception->getMessage());
|
||||||
$this->assertSame($error['type'], $exception->getSeverity());
|
$this->assertSame($error['type'], $exception->getSeverity());
|
||||||
$this->assertSame($error['file'], $exception->getFile());
|
$this->assertSame($error['file'], $exception->getFile());
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
namespace Symfony\Component\ErrorHandler\Tests\Fixtures;
|
||||||
|
|
||||||
class ErrorHandlerThatUsesThePreviousOne
|
class ErrorHandlerThatUsesThePreviousOne
|
||||||
{
|
{
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
namespace Symfony\Component\ErrorHandler\Tests\Fixtures;
|
||||||
|
|
||||||
use Symfony\Component\Debug\BufferingLogger;
|
use Symfony\Component\ErrorHandler\BufferingLogger;
|
||||||
|
|
||||||
class LoggerThatSetAnErrorHandler extends BufferingLogger
|
class LoggerThatSetAnErrorHandler extends BufferingLogger
|
||||||
{
|
{
|
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class Symfony_Component_ErrorHandler_Tests_Fixtures_PEARClass
|
||||||
|
{
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests\Fixtures;
|
namespace Symfony\Component\ErrorHandler\Tests\Fixtures;
|
||||||
|
|
||||||
class ToStringThrower
|
class ToStringThrower
|
||||||
{
|
{
|
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\ErrorHandler\Tests\Fixtures;
|
||||||
|
|
||||||
|
class UndefinedFuncException
|
||||||
|
{
|
||||||
|
}
|
@ -9,7 +9,7 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\ErrorHandler;
|
||||||
|
|
||||||
function headers_sent()
|
function headers_sent()
|
||||||
{
|
{
|
||||||
@ -21,7 +21,7 @@ function header($str, $replace = true, $status = null)
|
|||||||
Tests\testHeader($str, $replace, $status);
|
Tests\testHeader($str, $replace, $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests;
|
namespace Symfony\Component\ErrorHandler\Tests;
|
||||||
|
|
||||||
function testHeader()
|
function testHeader()
|
||||||
{
|
{
|
@ -9,9 +9,9 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Symfony\Component\Debug\Tests;
|
namespace Symfony\Component\ErrorHandler\Tests;
|
||||||
|
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
use Symfony\Component\ErrorHandler\ExceptionHandler;
|
||||||
|
|
||||||
class MockExceptionHandler extends ExceptionHandler
|
class MockExceptionHandler extends ExceptionHandler
|
||||||
{
|
{
|
@ -5,7 +5,7 @@ display_errors=0
|
|||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\ErrorHandler;
|
||||||
|
|
||||||
$vendor = __DIR__;
|
$vendor = __DIR__;
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
while (!file_exists($vendor.'/vendor')) {
|
||||||
@ -26,9 +26,9 @@ if (true) {
|
|||||||
|
|
||||||
?>
|
?>
|
||||||
--EXPECTF--
|
--EXPECTF--
|
||||||
object(Symfony\Component\Debug\Exception\ClassNotFoundException)#%d (8) {
|
object(Symfony\Component\ErrorHandler\Exception\ClassNotFoundException)#%d (8) {
|
||||||
["message":protected]=>
|
["message":protected]=>
|
||||||
string(131) "Attempted to load class "missing" from namespace "Symfony\Component\Debug".
|
string(138) "Attempted to load class "missing" from namespace "Symfony\Component\ErrorHandler".
|
||||||
Did you forget a "use" statement for another namespace?"
|
Did you forget a "use" statement for another namespace?"
|
||||||
["string":"Exception":private]=>
|
["string":"Exception":private]=>
|
||||||
string(0) ""
|
string(0) ""
|
@ -3,7 +3,7 @@ Test rethrowing in custom exception handler
|
|||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\ErrorHandler;
|
||||||
|
|
||||||
$vendor = __DIR__;
|
$vendor = __DIR__;
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
while (!file_exists($vendor.'/vendor')) {
|
@ -3,7 +3,9 @@ Test catching fatal errors when handlers are nested
|
|||||||
--FILE--
|
--FILE--
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Symfony\Component\Debug;
|
namespace Symfony\Component\ErrorHandler;
|
||||||
|
|
||||||
|
use Symfony\Component\Debug\Debug;
|
||||||
|
|
||||||
$vendor = __DIR__;
|
$vendor = __DIR__;
|
||||||
while (!file_exists($vendor.'/vendor')) {
|
while (!file_exists($vendor.'/vendor')) {
|
||||||
@ -35,8 +37,8 @@ array(1) {
|
|||||||
[0]=>
|
[0]=>
|
||||||
string(37) "Error and exception handlers do match"
|
string(37) "Error and exception handlers do match"
|
||||||
}
|
}
|
||||||
object(Symfony\Component\Debug\Exception\FatalErrorException)#%d (%d) {
|
object(Symfony\Component\ErrorHandler\Exception\FatalErrorException)#%d (%d) {
|
||||||
["message":protected]=>
|
["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)"
|
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
|
%a
|
||||||
}
|
}
|
42
src/Symfony/Component/ErrorHandler/composer.json
Normal file
42
src/Symfony/Component/ErrorHandler/composer.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/debug": "^4.4",
|
||||||
|
"symfony/dependency-injection": "^4.4",
|
||||||
|
"symfony/http-kernel": "^4.4"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/http-kernel": "<4.4"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": { "Symfony\\Component\\ErrorHandler\\": "" },
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "4.4-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
src/Symfony/Component/ErrorHandler/phpunit.xml.dist
Normal file
30
src/Symfony/Component/ErrorHandler/phpunit.xml.dist
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
|
||||||
|
backupGlobals="false"
|
||||||
|
colors="true"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
failOnRisky="true"
|
||||||
|
failOnWarning="true"
|
||||||
|
>
|
||||||
|
<php>
|
||||||
|
<ini name="error_reporting" value="-1" />
|
||||||
|
</php>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Symfony ErrorHandler Component Test Suite">
|
||||||
|
<directory>./Tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory>./</directory>
|
||||||
|
<exclude>
|
||||||
|
<directory>./Tests</directory>
|
||||||
|
<directory>./vendor</directory>
|
||||||
|
</exclude>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
namespace Symfony\Component\HttpKernel\DataCollector;
|
||||||
|
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
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\Request;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
@ -15,8 +15,11 @@ use Psr\Log\LoggerInterface;
|
|||||||
use Symfony\Component\Console\ConsoleEvents;
|
use Symfony\Component\Console\ConsoleEvents;
|
||||||
use Symfony\Component\Console\Event\ConsoleEvent;
|
use Symfony\Component\Console\Event\ConsoleEvent;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\Debug\ErrorHandler;
|
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\ErrorRendererNotFoundException;
|
||||||
|
use Symfony\Component\ErrorHandler\ExceptionHandler;
|
||||||
use Symfony\Component\EventDispatcher\Event;
|
use Symfony\Component\EventDispatcher\Event;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@ -43,6 +46,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
private $fileLinkFormat;
|
private $fileLinkFormat;
|
||||||
private $scope;
|
private $scope;
|
||||||
private $charset;
|
private $charset;
|
||||||
|
private $errorRenderer;
|
||||||
private $firstCall = true;
|
private $firstCall = true;
|
||||||
private $hasTerminatedWithException;
|
private $hasTerminatedWithException;
|
||||||
|
|
||||||
@ -55,7 +59,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
* @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files
|
* @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files
|
||||||
* @param bool $scope Enables/disables scoping mode
|
* @param bool $scope Enables/disables scoping mode
|
||||||
*/
|
*/
|
||||||
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, ?int $throwAt = E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true, string $charset = null)
|
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, ?int $throwAt = E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true, string $charset = null, ErrorRenderer $errorRenderer = null)
|
||||||
{
|
{
|
||||||
$this->exceptionHandler = $exceptionHandler;
|
$this->exceptionHandler = $exceptionHandler;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
@ -65,6 +69,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
$this->fileLinkFormat = $fileLinkFormat;
|
$this->fileLinkFormat = $fileLinkFormat;
|
||||||
$this->scope = $scope;
|
$this->scope = $scope;
|
||||||
$this->charset = $charset;
|
$this->charset = $charset;
|
||||||
|
$this->errorRenderer = $errorRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,10 +167,17 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
|
|
||||||
$debug = $this->scream && $this->scope;
|
$debug = $this->scream && $this->scope;
|
||||||
$controller = function (Request $request) use ($debug) {
|
$controller = function (Request $request) use ($debug) {
|
||||||
$e = $request->attributes->get('exception');
|
if (null === $this->errorRenderer) {
|
||||||
$handler = new ExceptionHandler($debug, $this->charset, $this->fileLinkFormat);
|
$this->errorRenderer = new ErrorRenderer([new HtmlErrorRenderer($debug, $this->charset, $this->fileLinkFormat)]);
|
||||||
|
}
|
||||||
|
|
||||||
return new Response($handler->getHtml($e), $e->getStatusCode(), $e->getHeaders());
|
$e = $request->attributes->get('exception');
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new Response($this->errorRenderer->render($e, $request->getRequestFormat()), $e->getStatusCode(), $e->getHeaders());
|
||||||
|
} catch (ErrorRendererNotFoundException $_) {
|
||||||
|
return new Response($this->errorRenderer->render($e), $e->getStatusCode(), $e->getHeaders());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(new ExceptionListener($controller, $this->logger, $debug))->onKernelException($event);
|
(new ExceptionListener($controller, $this->logger, $debug))->onKernelException($event);
|
||||||
|
@ -12,11 +12,10 @@
|
|||||||
namespace Symfony\Component\HttpKernel\EventListener;
|
namespace Symfony\Component\HttpKernel\EventListener;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
|
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;
|
use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
|
namespace Symfony\Component\HttpKernel\Tests\DataCollector;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
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\Request;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@ -106,7 +106,7 @@ class LoggerDataCollectorTest extends TestCase
|
|||||||
$logs = array_map(function ($v) {
|
$logs = array_map(function ($v) {
|
||||||
if (isset($v['context']['exception'])) {
|
if (isset($v['context']['exception'])) {
|
||||||
$e = &$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;
|
return $v;
|
||||||
|
@ -19,8 +19,8 @@ use Symfony\Component\Console\Event\ConsoleEvent;
|
|||||||
use Symfony\Component\Console\Helper\HelperSet;
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
use Symfony\Component\Debug\ErrorHandler;
|
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||||
use Symfony\Component\Debug\ExceptionHandler;
|
use Symfony\Component\ErrorHandler\ExceptionHandler;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
||||||
|
@ -17,9 +17,10 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
|
"symfony/error-handler": "^4.4",
|
||||||
"symfony/event-dispatcher": "^4.3",
|
"symfony/event-dispatcher": "^4.3",
|
||||||
"symfony/http-foundation": "^4.4|^5.0",
|
"symfony/http-foundation": "^4.4|^5.0",
|
||||||
"symfony/debug": "^3.4|^4.0|^5.0",
|
"symfony/debug": "^4.4|^5.0",
|
||||||
"symfony/polyfill-ctype": "^1.8",
|
"symfony/polyfill-ctype": "^1.8",
|
||||||
"symfony/polyfill-php73": "^1.9",
|
"symfony/polyfill-php73": "^1.9",
|
||||||
"psr/log": "~1.0"
|
"psr/log": "~1.0"
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
namespace Symfony\Component\Messenger\EventListener;
|
namespace Symfony\Component\Messenger\EventListener;
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\Debug\Exception\FlattenException;
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||||
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
use Symfony\Component\Messenger\Exception\HandlerFailedException;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user