Merge branch '4.4'
This commit is contained in:
commit
a306d99297
@ -155,6 +155,33 @@ HttpKernel
|
||||
current directory or with a glob pattern. The fallback directories have never been advocated
|
||||
so you likely do not use those in any app based on the SF Standard or Flex edition.
|
||||
* Getting the container from a non-booted kernel is deprecated
|
||||
* Deprecated passing the `exception` attribute (instance of `Symfony\Component\Debug\Exception\FlattenException`)
|
||||
to the configured controller of the `ExceptionListener`, use the `e` attribute
|
||||
(instance of `Symfony\Component\ErrorRenderer\Exception\FlattenException`) instead
|
||||
|
||||
before:
|
||||
```php
|
||||
use Symfony\Component\Debug\Exception\FlattenException;
|
||||
|
||||
class ExceptionController
|
||||
{
|
||||
public function __invoke(FlattenException $exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
after:
|
||||
```php
|
||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
||||
|
||||
class ExceptionController
|
||||
{
|
||||
public function __invoke(FlattenException $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Lock
|
||||
----
|
||||
|
@ -341,6 +341,33 @@ HttpKernel
|
||||
* Removed the second and third argument of `FileLocator::__construct`
|
||||
* Removed loading resources from `%kernel.root_dir%/Resources` and `%kernel.root_dir%` as
|
||||
fallback directories.
|
||||
* Removed passing the `exception` attribute (instance of `Symfony\Component\Debug\Exception\FlattenException`)
|
||||
to the configured controller of the `ExceptionListener`, use the `e` attribute
|
||||
(instance of `Symfony\Component\ErrorRenderer\Exception\FlattenException`) instead
|
||||
|
||||
before:
|
||||
```php
|
||||
use Symfony\Component\Debug\Exception\FlattenException;
|
||||
|
||||
class ExceptionController
|
||||
{
|
||||
public function __invoke(FlattenException $exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
after:
|
||||
```php
|
||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
||||
|
||||
class ExceptionController
|
||||
{
|
||||
public function __invoke(FlattenException $e)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Intl
|
||||
----
|
||||
|
@ -20,7 +20,7 @@ use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
|
||||
use Symfony\Component\ErrorHandler\Exception\ErrorException;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Symfony\Component\HttpKernel\Kernel;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
@ -208,7 +208,7 @@ class Application extends BaseApplication
|
||||
|
||||
foreach ($this->registrationErrors as $error) {
|
||||
if (!$error instanceof \Exception) {
|
||||
$error = new FatalThrowableError($error);
|
||||
$error = new ErrorException($error);
|
||||
}
|
||||
|
||||
$this->doRenderException($error, $output);
|
||||
|
@ -149,6 +149,16 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA
|
||||
*/
|
||||
abstract protected function doInvalidate(array $tagIds): bool;
|
||||
|
||||
/**
|
||||
* Returns the tags bound to the provided ids.
|
||||
*/
|
||||
protected function doFetchTags(array $ids): iterable
|
||||
{
|
||||
foreach ($this->doFetch($ids) as $id => $value) {
|
||||
yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
@ -232,11 +242,15 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA
|
||||
unset($this->deferred[$key]);
|
||||
}
|
||||
|
||||
foreach ($this->doFetch($ids) as $id => $value) {
|
||||
foreach ($value['tags'] ?? [] as $tag) {
|
||||
try {
|
||||
foreach ($this->doFetchTags($ids) as $id => $tags) {
|
||||
foreach ($tags as $tag) {
|
||||
$tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// ignore unserialization failures
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->doDelete(array_values($ids), $tagData)) {
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
||||
|
||||
@ -37,7 +37,7 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
|
||||
|
||||
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
$this->marshaller = new TagAwareMarshaller($marshaller);
|
||||
parent::__construct('', $defaultLifetime);
|
||||
$this->init($namespace, $directory);
|
||||
}
|
||||
@ -130,6 +130,40 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
|
||||
return $failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetchTags(array $ids): iterable
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
$file = $this->getFile($id);
|
||||
if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$meta = explode("\n", fread($h, 4096), 3)[2] ?? '';
|
||||
|
||||
// detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
|
||||
if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) {
|
||||
$meta[9] = "\0";
|
||||
$tagLen = unpack('Nlen', $meta, 9)['len'];
|
||||
$meta = substr($meta, 13, $tagLen);
|
||||
|
||||
if (0 < $tagLen -= \strlen($meta)) {
|
||||
$meta .= fread($h, $tagLen);
|
||||
}
|
||||
|
||||
try {
|
||||
yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta);
|
||||
} catch (\Exception $e) {
|
||||
yield $id => [];
|
||||
}
|
||||
}
|
||||
|
||||
fclose($h);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -16,6 +16,7 @@ use Predis\Connection\Aggregate\PredisCluster;
|
||||
use Predis\Response\Status;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
|
||||
use Symfony\Component\Cache\Traits\RedisTrait;
|
||||
|
||||
/**
|
||||
@ -67,7 +68,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
|
||||
throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, \get_class($redisClient->getConnection())));
|
||||
}
|
||||
|
||||
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
|
||||
$this->init($redisClient, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,7 +82,7 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
|
||||
}
|
||||
|
||||
// While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op
|
||||
$results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData) {
|
||||
$results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) {
|
||||
// Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one
|
||||
foreach ($serialized as $id => $value) {
|
||||
yield 'setEx' => [
|
||||
@ -93,12 +94,16 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
|
||||
|
||||
// Add and Remove Tags
|
||||
foreach ($addTagData as $tagId => $ids) {
|
||||
if (!$failed || $ids = array_diff($ids, $failed)) {
|
||||
yield 'sAdd' => array_merge([$tagId], $ids);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($delTagData as $tagId => $ids) {
|
||||
if (!$failed || $ids = array_diff($ids, $failed)) {
|
||||
yield 'sRem' => array_merge([$tagId], $ids);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($results as $id => $result) {
|
||||
@ -115,6 +120,42 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
|
||||
return $failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetchTags(array $ids): iterable
|
||||
{
|
||||
$lua = <<<'EOLUA'
|
||||
local v = redis.call('GET', KEYS[1])
|
||||
|
||||
if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then
|
||||
return ''
|
||||
end
|
||||
|
||||
return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536)
|
||||
EOLUA;
|
||||
|
||||
if ($this->redis instanceof \Predis\ClientInterface) {
|
||||
$evalArgs = [$lua, 1, &$id];
|
||||
} else {
|
||||
$evalArgs = [$lua, [&$id], 1];
|
||||
}
|
||||
|
||||
$results = $this->pipeline(function () use ($ids, &$id, $evalArgs) {
|
||||
foreach ($ids as $id) {
|
||||
yield 'eval' => $evalArgs;
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($results as $id => $result) {
|
||||
try {
|
||||
yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result);
|
||||
} catch (\Exception $e) {
|
||||
yield $id => [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -15,6 +15,7 @@ CHANGELOG
|
||||
* added support for connecting to Redis Sentinel clusters
|
||||
* added argument `$prefix` to `AdapterInterface::clear()`
|
||||
* improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag
|
||||
* added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter`
|
||||
* [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead
|
||||
|
||||
4.3.0
|
||||
|
@ -0,0 +1,89 @@
|
||||
<?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\Cache\Marshaller;
|
||||
|
||||
/**
|
||||
* A marshaller optimized for data structures generated by AbstractTagAwareAdapter.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class TagAwareMarshaller implements MarshallerInterface
|
||||
{
|
||||
private $marshaller;
|
||||
|
||||
public function __construct(MarshallerInterface $marshaller = null)
|
||||
{
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function marshall(array $values, ?array &$failed): array
|
||||
{
|
||||
$failed = $notSerialized = $serialized = [];
|
||||
|
||||
foreach ($values as $id => $value) {
|
||||
if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) {
|
||||
// if the value is an array with keys "tags", "value" and "meta", use a compact serialization format
|
||||
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall()
|
||||
|
||||
$v = $this->marshaller->marshall($value, $f);
|
||||
|
||||
if ($f) {
|
||||
$f = [];
|
||||
$failed[] = $id;
|
||||
} else {
|
||||
if ([] === $value['tags']) {
|
||||
$v['tags'] = '';
|
||||
}
|
||||
|
||||
$serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($v['tags'])).$v['tags'].$v['value'];
|
||||
$serialized[$id][9] = "\x5F";
|
||||
}
|
||||
} else {
|
||||
// other arbitratry values are serialized using the decorated marshaller below
|
||||
$notSerialized[$id] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($notSerialized) {
|
||||
$serialized += $this->marshaller->marshall($notSerialized, $f);
|
||||
$failed = array_merge($failed, $f);
|
||||
}
|
||||
|
||||
return $serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unmarshall(string $value)
|
||||
{
|
||||
// detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
|
||||
if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) {
|
||||
return $this->marshaller->unmarshall($value);
|
||||
}
|
||||
|
||||
// data consists of value, tags and metadata which we need to unpack
|
||||
$meta = substr($value, 1, 12);
|
||||
$meta[8] = "\0";
|
||||
$tagLen = unpack('Nlen', $meta, 8)['len'];
|
||||
$meta = substr($meta, 0, 8);
|
||||
|
||||
return [
|
||||
'value' => $this->marshaller->unmarshall(substr($value, 13 + $tagLen)),
|
||||
'tags' => $tagLen ? $this->marshaller->unmarshall(substr($value, 13, $tagLen)) : [],
|
||||
'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta,
|
||||
];
|
||||
}
|
||||
}
|
@ -211,11 +211,13 @@ trait AbstractAdapterTrait
|
||||
foreach ($this->doFetch([$id]) as $value) {
|
||||
$isHit = true;
|
||||
}
|
||||
|
||||
return $f($key, $value, $isHit);
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
|
||||
}
|
||||
|
||||
return $f($key, $value, $isHit);
|
||||
return $f($key, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -445,25 +445,26 @@ trait RedisTrait
|
||||
$results = [];
|
||||
foreach ($generator() as $command => $args) {
|
||||
$results[] = $redis->{$command}(...$args);
|
||||
$ids[] = $args[0];
|
||||
$ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface ? $args[2] : $args[1][0]) : $args[0];
|
||||
}
|
||||
} elseif ($redis instanceof \Predis\ClientInterface) {
|
||||
$results = $redis->pipeline(static function ($redis) use ($generator, &$ids) {
|
||||
foreach ($generator() as $command => $args) {
|
||||
$redis->{$command}(...$args);
|
||||
$ids[] = $args[0];
|
||||
$ids[] = 'eval' === $command ? $args[2] : $args[0];
|
||||
}
|
||||
});
|
||||
} elseif ($redis instanceof \RedisArray) {
|
||||
$connections = $results = $ids = [];
|
||||
foreach ($generator() as $command => $args) {
|
||||
if (!isset($connections[$h = $redis->_target($args[0])])) {
|
||||
$id = 'eval' === $command ? $args[1][0] : $args[0];
|
||||
if (!isset($connections[$h = $redis->_target($id)])) {
|
||||
$connections[$h] = [$redis->_instance($h), -1];
|
||||
$connections[$h][0]->multi(\Redis::PIPELINE);
|
||||
}
|
||||
$connections[$h][0]->{$command}(...$args);
|
||||
$results[] = [$h, ++$connections[$h][1]];
|
||||
$ids[] = $args[0];
|
||||
$ids[] = $id;
|
||||
}
|
||||
foreach ($connections as $h => $c) {
|
||||
$connections[$h] = $c[0]->exec();
|
||||
@ -475,7 +476,7 @@ trait RedisTrait
|
||||
$redis->multi(\Redis::PIPELINE);
|
||||
foreach ($generator() as $command => $args) {
|
||||
$redis->{$command}(...$args);
|
||||
$ids[] = $args[0];
|
||||
$ids[] = 'eval' === $command ? $args[1][0] : $args[0];
|
||||
}
|
||||
$results = $redis->exec();
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
|
||||
use Symfony\Component\ErrorHandler\Exception\ErrorException;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
@ -122,7 +122,7 @@ class Application implements ResetInterface
|
||||
|
||||
$renderException = function (\Throwable $e) use ($output) {
|
||||
if (!$e instanceof \Exception) {
|
||||
$e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||
$e = class_exists(ErrorException::class) ? new ErrorException($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||
}
|
||||
if ($output instanceof ConsoleOutputInterface) {
|
||||
$this->renderException($e, $output->getErrorOutput());
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?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\Error;
|
||||
|
||||
class ClassNotFoundError extends \Error
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(string $message, \Throwable $previous)
|
||||
{
|
||||
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
|
||||
|
||||
foreach ([
|
||||
'file' => $previous->getFile(),
|
||||
'line' => $previous->getLine(),
|
||||
'trace' => $previous->getTrace(),
|
||||
] as $property => $value) {
|
||||
$refl = new \ReflectionProperty(\Error::class, $property);
|
||||
$refl->setAccessible(true);
|
||||
$refl->setValue($this, $value);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,18 +9,22 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\ErrorHandler\Exception;
|
||||
namespace Symfony\Component\ErrorHandler\Error;
|
||||
|
||||
/**
|
||||
* Fatal Error Exception.
|
||||
*
|
||||
* @author Konstanton Myakshin <koc-dp@yandex.ru>
|
||||
*/
|
||||
class FatalErrorException extends \ErrorException
|
||||
class FatalError extends \Error
|
||||
{
|
||||
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)
|
||||
private $error;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $error An array as returned by error_get_last()
|
||||
*/
|
||||
public function __construct(string $message, int $code, array $error, int $traceOffset = null, bool $traceArgs = true, array $trace = null)
|
||||
{
|
||||
parent::__construct($message, $code, $severity, $filename, $lineno, $previous);
|
||||
parent::__construct($message, $code);
|
||||
|
||||
$this->error = $error;
|
||||
|
||||
if (null !== $trace) {
|
||||
if (!$traceArgs) {
|
||||
@ -28,8 +32,6 @@ class FatalErrorException extends \ErrorException
|
||||
unset($frame['args'], $frame['this'], $frame);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setTrace($trace);
|
||||
} elseif (null !== $traceOffset) {
|
||||
if (\function_exists('xdebug_get_function_stack')) {
|
||||
$trace = xdebug_get_function_stack();
|
||||
@ -63,15 +65,24 @@ class FatalErrorException extends \ErrorException
|
||||
} else {
|
||||
$trace = [];
|
||||
}
|
||||
}
|
||||
|
||||
$this->setTrace($trace);
|
||||
foreach ([
|
||||
'file' => $error['file'],
|
||||
'line' => $error['line'],
|
||||
'trace' => $trace,
|
||||
] as $property => $value) {
|
||||
$refl = new \ReflectionProperty(\Error::class, $property);
|
||||
$refl->setAccessible(true);
|
||||
$refl->setValue($this, $value);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setTrace(array $trace): void
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getError(): array
|
||||
{
|
||||
$traceReflector = new \ReflectionProperty('Exception', 'trace');
|
||||
$traceReflector->setAccessible(true);
|
||||
$traceReflector->setValue($this, $trace);
|
||||
return $this->error;
|
||||
}
|
||||
}
|
@ -9,13 +9,8 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\ErrorHandler\Exception;
|
||||
namespace Symfony\Component\ErrorHandler\Error;
|
||||
|
||||
/**
|
||||
* Out of memory exception.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class OutOfMemoryException extends FatalErrorException
|
||||
class OutOfMemoryError extends FatalError
|
||||
{
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?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\Error;
|
||||
|
||||
class UndefinedFunctionError extends \Error
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(string $message, \Throwable $previous)
|
||||
{
|
||||
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
|
||||
|
||||
foreach ([
|
||||
'file' => $previous->getFile(),
|
||||
'line' => $previous->getLine(),
|
||||
'trace' => $previous->getTrace(),
|
||||
] as $property => $value) {
|
||||
$refl = new \ReflectionProperty(\Error::class, $property);
|
||||
$refl->setAccessible(true);
|
||||
$refl->setValue($this, $value);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?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\Error;
|
||||
|
||||
class UndefinedMethodError extends \Error
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(string $message, \Throwable $previous)
|
||||
{
|
||||
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
|
||||
|
||||
foreach ([
|
||||
'file' => $previous->getFile(),
|
||||
'line' => $previous->getLine(),
|
||||
'trace' => $previous->getTrace(),
|
||||
] as $property => $value) {
|
||||
$refl = new \ReflectionProperty(\Error::class, $property);
|
||||
$refl->setAccessible(true);
|
||||
$refl->setValue($this, $value);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,45 +9,45 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
|
||||
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
|
||||
|
||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
|
||||
use Symfony\Component\ErrorHandler\DebugClassLoader;
|
||||
use Symfony\Component\ErrorHandler\Exception\ClassNotFoundException;
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||
use Symfony\Component\ErrorHandler\Error\ClassNotFoundError;
|
||||
use Symfony\Component\ErrorHandler\Error\FatalError;
|
||||
|
||||
/**
|
||||
* ErrorHandler for classes that do not exist.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception)
|
||||
public function enhance(\Throwable $error): ?\Throwable
|
||||
{
|
||||
$messageLen = \strlen($error['message']);
|
||||
// Some specific versions of PHP produce a fatal error when extending a not found class.
|
||||
$message = !$error instanceof FatalError ? $error->getMessage() : $error->getError()['message'];
|
||||
$messageLen = \strlen($message);
|
||||
$notFoundSuffix = '\' not found';
|
||||
$notFoundSuffixLen = \strlen($notFoundSuffix);
|
||||
if ($notFoundSuffixLen > $messageLen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||
if (0 !== substr_compare($message, $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (['class', 'interface', 'trait'] as $typeName) {
|
||||
$prefix = ucfirst($typeName).' \'';
|
||||
$prefixLen = \strlen($prefix);
|
||||
if (0 !== strpos($error['message'], $prefix)) {
|
||||
if (0 !== strpos($message, $prefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
||||
$fullyQualifiedClassName = substr($message, $prefixLen, -$notFoundSuffixLen);
|
||||
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
|
||||
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
|
||||
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
|
||||
@ -69,8 +69,10 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
}
|
||||
$message .= "\nDid you forget a \"use\" statement".$tail;
|
||||
|
||||
return new ClassNotFoundException($message, $exception);
|
||||
return new ClassNotFoundError($message, $error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,7 +83,7 @@ class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
*
|
||||
* @param string $class A class name (without its namespace)
|
||||
*
|
||||
* @return array An array of possible fully qualified class names
|
||||
* Returns an array of possible fully qualified class names
|
||||
*/
|
||||
private function getClassCandidates(string $class): array
|
||||
{
|
@ -0,0 +1,20 @@
|
||||
<?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\ErrorEnhancer;
|
||||
|
||||
interface ErrorEnhancerInterface
|
||||
{
|
||||
/**
|
||||
* Returns an \Throwable instance if the class is able to improve the error, null otherwise.
|
||||
*/
|
||||
public function enhance(\Throwable $error): ?\Throwable;
|
||||
}
|
@ -9,41 +9,44 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
|
||||
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
|
||||
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||
use Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException;
|
||||
use Symfony\Component\ErrorHandler\Error\FatalError;
|
||||
use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError;
|
||||
|
||||
/**
|
||||
* ErrorHandler for undefined functions.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception)
|
||||
public function enhance(\Throwable $error): ?\Throwable
|
||||
{
|
||||
$messageLen = \strlen($error['message']);
|
||||
if ($error instanceof FatalError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$message = $error->getMessage();
|
||||
$messageLen = \strlen($message);
|
||||
$notFoundSuffix = '()';
|
||||
$notFoundSuffixLen = \strlen($notFoundSuffix);
|
||||
if ($notFoundSuffixLen > $messageLen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||
if (0 !== substr_compare($message, $notFoundSuffix, -$notFoundSuffixLen)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$prefix = 'Call to undefined function ';
|
||||
$prefixLen = \strlen($prefix);
|
||||
if (0 !== strpos($error['message'], $prefix)) {
|
||||
if (0 !== strpos($message, $prefix)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
|
||||
$fullyQualifiedFunctionName = substr($message, $prefixLen, -$notFoundSuffixLen);
|
||||
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
|
||||
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
|
||||
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
|
||||
@ -79,6 +82,6 @@ class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
$message .= "\nDid you mean to call ".$candidates;
|
||||
}
|
||||
|
||||
return new UndefinedFunctionException($message, $exception);
|
||||
return new UndefinedFunctionError($message, $error);
|
||||
}
|
||||
}
|
@ -9,24 +9,27 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\ErrorHandler\FatalErrorHandler;
|
||||
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
|
||||
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||
use Symfony\Component\ErrorHandler\Exception\UndefinedMethodException;
|
||||
use Symfony\Component\ErrorHandler\Error\FatalError;
|
||||
use Symfony\Component\ErrorHandler\Error\UndefinedMethodError;
|
||||
|
||||
/**
|
||||
* ErrorHandler for undefined methods.
|
||||
*
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*/
|
||||
class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleError(array $error, FatalErrorException $exception)
|
||||
public function enhance(\Throwable $error): ?\Throwable
|
||||
{
|
||||
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches);
|
||||
if ($error instanceof FatalError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$message = $error->getMessage();
|
||||
preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $message, $matches);
|
||||
if (!$matches) {
|
||||
return null;
|
||||
}
|
||||
@ -38,7 +41,7 @@ class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
|
||||
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);
|
||||
return new UndefinedMethodError($message, $error);
|
||||
}
|
||||
|
||||
$candidates = [];
|
||||
@ -61,6 +64,6 @@ class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface
|
||||
$message .= "\nDid you mean to call ".$candidates;
|
||||
}
|
||||
|
||||
return new UndefinedMethodException($message, $exception);
|
||||
return new UndefinedMethodError($message, $error);
|
||||
}
|
||||
}
|
@ -13,15 +13,13 @@ namespace Symfony\Component\ErrorHandler;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||
use Symfony\Component\ErrorHandler\Exception\OutOfMemoryException;
|
||||
use Symfony\Component\ErrorHandler\Error\FatalError;
|
||||
use Symfony\Component\ErrorHandler\Error\OutOfMemoryError;
|
||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer;
|
||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface;
|
||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer;
|
||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer;
|
||||
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
|
||||
use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
||||
use Symfony\Component\ErrorHandler\FatalErrorHandler\FatalErrorHandlerInterface;
|
||||
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
||||
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
||||
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
|
||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
||||
|
||||
/**
|
||||
* A generic ErrorHandler for the PHP engine.
|
||||
@ -538,64 +536,50 @@ class ErrorHandler
|
||||
/**
|
||||
* Handles an exception by logging then forwarding it to another handler.
|
||||
*
|
||||
* @param array $error An array as returned by error_get_last()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function handleException(\Throwable $exception, array $error = null)
|
||||
public function handleException(\Throwable $exception)
|
||||
{
|
||||
if (null === $error) {
|
||||
self::$exitCode = 255;
|
||||
}
|
||||
|
||||
$type = ThrowableUtils::getSeverity($exception);
|
||||
$handlerException = null;
|
||||
|
||||
if (($this->loggedErrors & $type) || $exception instanceof \Error) {
|
||||
if (!$exception instanceof FatalError) {
|
||||
self::$exitCode = 255;
|
||||
|
||||
$type = ThrowableUtils::getSeverity($exception);
|
||||
} else {
|
||||
$type = $exception->getError()['type'];
|
||||
}
|
||||
|
||||
if ($this->loggedErrors & $type) {
|
||||
if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) {
|
||||
$message = $this->parseAnonymousClass($message);
|
||||
}
|
||||
|
||||
if ($exception instanceof FatalErrorException) {
|
||||
if ($exception instanceof FatalError) {
|
||||
$message = 'Fatal '.$message;
|
||||
} elseif ($exception instanceof \Error) {
|
||||
$message = 'Uncaught Error: '.$message;
|
||||
} elseif ($exception instanceof \ErrorException) {
|
||||
$message = 'Uncaught '.$message;
|
||||
} elseif ($exception instanceof \Error) {
|
||||
$error = [
|
||||
'type' => $type,
|
||||
'message' => $message,
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
];
|
||||
$message = 'Uncaught Error: '.$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) {
|
||||
}
|
||||
}
|
||||
|
||||
// temporary until fatal error handlers rework
|
||||
$originalException = $exception;
|
||||
if (!$exception instanceof \Exception) {
|
||||
$exception = new FatalErrorException($exception->getMessage(), $exception->getCode(), $type, $exception->getFile(), $exception->getLine(), null, true, $exception->getTrace());
|
||||
}
|
||||
|
||||
if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
|
||||
foreach ($this->getFatalErrorHandlers() as $handler) {
|
||||
if ($e = $handler->handleError($error, $exception)) {
|
||||
$convertedException = $e;
|
||||
if (!$exception instanceof OutOfMemoryError) {
|
||||
foreach ($this->getErrorEnhancers() as $errorEnhancer) {
|
||||
if ($e = $errorEnhancer->enhance($exception)) {
|
||||
$exception = $e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$exception = $convertedException ?? $originalException;
|
||||
$exceptionHandler = $this->exceptionHandler;
|
||||
if ((!\is_array($exceptionHandler) || !$exceptionHandler[0] instanceof self || 'sendPhpResponse' !== $exceptionHandler[1]) && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
|
||||
$this->exceptionHandler = [$this, 'sendPhpResponse'];
|
||||
@ -613,6 +597,7 @@ class ErrorHandler
|
||||
self::$reservedMemory = null; // Disable the fatal error handler
|
||||
throw $exception; // Give back $exception to the native handler
|
||||
}
|
||||
|
||||
$this->handleException($handlerException);
|
||||
}
|
||||
|
||||
@ -673,20 +658,20 @@ class ErrorHandler
|
||||
$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);
|
||||
$fatalError = new OutOfMemoryError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, false, $trace);
|
||||
} else {
|
||||
$exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
|
||||
$fatalError = new FatalError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, true, $trace);
|
||||
}
|
||||
} else {
|
||||
$exception = null;
|
||||
$fatalError = null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (null !== $exception) {
|
||||
if (null !== $fatalError) {
|
||||
self::$exitCode = 255;
|
||||
$handler->handleException($exception, $error);
|
||||
$handler->handleException($fatalError);
|
||||
}
|
||||
} catch (FatalErrorException $e) {
|
||||
} catch (FatalError $e) {
|
||||
// Ignore this re-throw
|
||||
}
|
||||
|
||||
@ -730,18 +715,16 @@ class ErrorHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fatal error handlers.
|
||||
* Override this method if you want to define more error enhancers.
|
||||
*
|
||||
* Override this method if you want to define more fatal error handlers.
|
||||
*
|
||||
* @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
|
||||
* @return ErrorEnhancerInterface[]
|
||||
*/
|
||||
protected function getFatalErrorHandlers(): array
|
||||
protected function getErrorEnhancers(): iterable
|
||||
{
|
||||
return [
|
||||
new UndefinedFunctionFatalErrorHandler(),
|
||||
new UndefinedMethodFatalErrorHandler(),
|
||||
new ClassNotFoundFatalErrorHandler(),
|
||||
new UndefinedFunctionErrorEnhancer(),
|
||||
new UndefinedMethodErrorEnhancer(),
|
||||
new ClassNotFoundErrorEnhancer(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1,36 +0,0 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
@ -13,12 +13,7 @@ namespace Symfony\Component\ErrorHandler\Exception;
|
||||
|
||||
use Symfony\Component\ErrorHandler\ThrowableUtils;
|
||||
|
||||
/**
|
||||
* Fatal Throwable Error.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class FatalThrowableError extends FatalErrorException
|
||||
class ErrorException extends \ErrorException
|
||||
{
|
||||
private $originalClassName;
|
||||
|
||||
@ -26,7 +21,7 @@ class FatalThrowableError extends FatalErrorException
|
||||
{
|
||||
$this->originalClassName = \get_class($e);
|
||||
|
||||
\ErrorException::__construct(
|
||||
parent::__construct(
|
||||
$e->getMessage(),
|
||||
$e->getCode(),
|
||||
ThrowableUtils::getSeverity($e),
|
||||
@ -35,7 +30,9 @@ class FatalThrowableError extends FatalErrorException
|
||||
$e->getPrevious()
|
||||
);
|
||||
|
||||
$this->setTrace($e->getTrace());
|
||||
$refl = new \ReflectionProperty(\Exception::class, 'trace');
|
||||
$refl->setAccessible(true);
|
||||
$refl->setValue($this, $e->getTrace());
|
||||
}
|
||||
|
||||
public function getOriginalClassName(): string
|
@ -1,36 +0,0 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?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()
|
||||
*
|
||||
* @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,149 @@
|
||||
<?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\ErrorEnhancer;
|
||||
|
||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ErrorHandler\DebugClassLoader;
|
||||
use Symfony\Component\ErrorHandler\Error\ClassNotFoundError;
|
||||
use Symfony\Component\ErrorHandler\Error\FatalError;
|
||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer;
|
||||
|
||||
class ClassNotFoundErrorEnhancerTest extends TestCase
|
||||
{
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
foreach (spl_autoload_functions() as $function) {
|
||||
if (!\is_array($function)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get class loaders wrapped by DebugClassLoader
|
||||
if ($function[0] instanceof DebugClassLoader) {
|
||||
$function = $function[0]->getClassLoader();
|
||||
}
|
||||
|
||||
if ($function[0] instanceof ComposerClassLoader) {
|
||||
$function[0]->add('Symfony_Component_ErrorHandler_Tests_Fixtures', \dirname(__DIR__, 5));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideClassNotFoundData
|
||||
*/
|
||||
public function testEnhance(string $originalMessage, string $enhancedMessage, $autoloader = null)
|
||||
{
|
||||
try {
|
||||
if ($autoloader) {
|
||||
// Unregister all autoloaders to ensure the custom provided
|
||||
// autoloader is the only one to be used during the test run.
|
||||
$autoloaders = spl_autoload_functions();
|
||||
array_map('spl_autoload_unregister', $autoloaders);
|
||||
spl_autoload_register($autoloader);
|
||||
}
|
||||
|
||||
$expectedLine = __LINE__ + 1;
|
||||
$error = (new ClassNotFoundErrorEnhancer())->enhance(new \Error($originalMessage));
|
||||
} finally {
|
||||
if ($autoloader) {
|
||||
spl_autoload_unregister($autoloader);
|
||||
array_map('spl_autoload_register', $autoloaders);
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertInstanceOf(ClassNotFoundError::class, $error);
|
||||
$this->assertSame($enhancedMessage, $error->getMessage());
|
||||
$this->assertSame(realpath(__FILE__), $error->getFile());
|
||||
$this->assertSame($expectedLine, $error->getLine());
|
||||
}
|
||||
|
||||
public function provideClassNotFoundData()
|
||||
{
|
||||
$autoloader = new ComposerClassLoader();
|
||||
$autoloader->add('Symfony\Component\ErrorHandler\Error\\', realpath(__DIR__.'/../../Error'));
|
||||
$autoloader->add('Symfony_Component_ErrorHandler_Tests_Fixtures', realpath(__DIR__.'/../../Tests/Fixtures'));
|
||||
|
||||
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
|
||||
|
||||
return [
|
||||
[
|
||||
'Class \'WhizBangFactory\' not found',
|
||||
"Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?",
|
||||
],
|
||||
[
|
||||
'Class \'Foo\\Bar\\WhizBangFactory\' not found',
|
||||
"Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
|
||||
],
|
||||
[
|
||||
'Class \'UndefinedFunctionError\' not found',
|
||||
"Attempted to load class \"UndefinedFunctionError\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Error\UndefinedFunctionError\"?",
|
||||
[$debugClassLoader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
'Class \'PEARClass\' not found',
|
||||
"Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_ErrorHandler_Tests_Fixtures_PEARClass\"?",
|
||||
[$debugClassLoader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
'Class \'Foo\\Bar\\UndefinedFunctionError\' not found',
|
||||
"Attempted to load class \"UndefinedFunctionError\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Error\UndefinedFunctionError\"?",
|
||||
[$debugClassLoader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
'Class \'Foo\\Bar\\UndefinedFunctionError\' not found',
|
||||
"Attempted to load class \"UndefinedFunctionError\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Error\UndefinedFunctionError\"?",
|
||||
[$autoloader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
'Class \'Foo\\Bar\\UndefinedFunctionError\' not found',
|
||||
"Attempted to load class \"UndefinedFunctionError\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Error\UndefinedFunctionError\"?",
|
||||
[$debugClassLoader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
'Class \'Foo\\Bar\\UndefinedFunctionError\' not found',
|
||||
"Attempted to load class \"UndefinedFunctionError\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
|
||||
function ($className) { /* do nothing here */ },
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testEnhanceWithFatalError()
|
||||
{
|
||||
$error = (new ClassNotFoundErrorEnhancer())->enhance(new FatalError('foo', 0, [
|
||||
'type' => E_ERROR,
|
||||
'message' => "Class 'FooBarCcc' not found",
|
||||
'file' => $expectedFile = realpath(__FILE__),
|
||||
'line' => $expectedLine = __LINE__,
|
||||
]));
|
||||
|
||||
$this->assertInstanceOf(ClassNotFoundError::class, $error);
|
||||
$this->assertSame("Attempted to load class \"FooBarCcc\" from the global namespace.\nDid you forget a \"use\" statement?", $error->getMessage());
|
||||
$this->assertSame($expectedFile, $error->getFile());
|
||||
$this->assertSame($expectedLine, $error->getLine());
|
||||
}
|
||||
|
||||
public function testCannotRedeclareClass()
|
||||
{
|
||||
if (!file_exists(__DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP')) {
|
||||
$this->markTestSkipped('Can only be run on case insensitive filesystems');
|
||||
}
|
||||
|
||||
require_once __DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP';
|
||||
|
||||
$enhancer = new ClassNotFoundErrorEnhancer();
|
||||
$error = $enhancer->enhance(new \Error("Class 'Foo\\Bar\\RequiredTwice' not found"));
|
||||
|
||||
$this->assertInstanceOf(ClassNotFoundError::class, $error);
|
||||
}
|
||||
}
|
@ -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\ErrorEnhancer;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError;
|
||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer;
|
||||
|
||||
class UndefinedFunctionErrorEnhancerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideUndefinedFunctionData
|
||||
*/
|
||||
public function testEnhance(string $originalMessage, string $enhancedMessage)
|
||||
{
|
||||
$enhancer = new UndefinedFunctionErrorEnhancer();
|
||||
|
||||
$expectedLine = __LINE__ + 1;
|
||||
$error = $enhancer->enhance(new \Error($originalMessage));
|
||||
|
||||
$this->assertInstanceOf(UndefinedFunctionError::class, $error);
|
||||
// class names are case insensitive and PHP do not return the same
|
||||
$this->assertSame(strtolower($enhancedMessage), strtolower($error->getMessage()));
|
||||
$this->assertSame(realpath(__FILE__), $error->getFile());
|
||||
$this->assertSame($expectedLine, $error->getLine());
|
||||
}
|
||||
|
||||
public function provideUndefinedFunctionData()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'Call to undefined function test_namespaced_function()',
|
||||
"Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\errorenhancer\\test_namespaced_function\"?",
|
||||
],
|
||||
[
|
||||
'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
|
||||
"Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\errorenhancer\\test_namespaced_function\"?",
|
||||
],
|
||||
[
|
||||
'Call to undefined function foo()',
|
||||
'Attempted to call function "foo" from the global namespace.',
|
||||
],
|
||||
[
|
||||
'Call to undefined function Foo\\Bar\\Baz\\foo()',
|
||||
'Attempted to call function "foo" from namespace "Foo\Bar\Baz".',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function test_namespaced_function()
|
||||
{
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<?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\ErrorEnhancer;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ErrorHandler\Error\UndefinedMethodError;
|
||||
use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer;
|
||||
|
||||
class UndefinedMethodErrorEnhancerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideUndefinedMethodData
|
||||
*/
|
||||
public function testEnhance(string $originalMessage, string $enhancedMessage)
|
||||
{
|
||||
$enhancer = new UndefinedMethodErrorEnhancer();
|
||||
|
||||
$expectedLine = __LINE__ + 1;
|
||||
$error = $enhancer->enhance(new \Error($originalMessage));
|
||||
|
||||
$this->assertInstanceOf(UndefinedMethodError::class, $error);
|
||||
$this->assertSame($enhancedMessage, $error->getMessage());
|
||||
$this->assertSame(realpath(__FILE__), $error->getFile());
|
||||
$this->assertSame($expectedLine, $error->getLine());
|
||||
}
|
||||
|
||||
public function provideUndefinedMethodData()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'Call to undefined method SplObjectStorage::what()',
|
||||
'Attempted to call an undefined method named "what" of class "SplObjectStorage".',
|
||||
],
|
||||
[
|
||||
'Call to undefined method SplObjectStorage::walid()',
|
||||
"Attempted to call an undefined method named \"walid\" of class \"SplObjectStorage\".\nDid you mean to call \"valid\"?",
|
||||
],
|
||||
[
|
||||
'Call to undefined method SplObjectStorage::offsetFet()',
|
||||
"Attempted to call an undefined method named \"offsetFet\" of class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?",
|
||||
],
|
||||
[
|
||||
'Call to undefined method class@anonymous::test()',
|
||||
'Attempted to call an undefined method named "test" of class "class@anonymous".',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -15,6 +15,8 @@ use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LogLevel;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\ErrorHandler\BufferingLogger;
|
||||
use Symfony\Component\ErrorHandler\Error\ClassNotFoundError;
|
||||
use Symfony\Component\ErrorHandler\Error\FatalError;
|
||||
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
|
||||
use Symfony\Component\ErrorHandler\Tests\Fixtures\ErrorHandlerThatUsesThePreviousOne;
|
||||
@ -518,7 +520,7 @@ class ErrorHandlerTest extends TestCase
|
||||
$logArgCheck = function ($level, $message, $context) {
|
||||
$this->assertEquals('Fatal Parse Error: foo', $message);
|
||||
$this->assertArrayHasKey('exception', $context);
|
||||
$this->assertInstanceOf(\Exception::class, $context['exception']);
|
||||
$this->assertInstanceOf(FatalError::class, $context['exception']);
|
||||
};
|
||||
|
||||
$logger
|
||||
@ -552,7 +554,7 @@ class ErrorHandlerTest extends TestCase
|
||||
|
||||
$handler->handleException($exception);
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $args[0]);
|
||||
$this->assertInstanceOf(ClassNotFoundError::class, $args[0]);
|
||||
$this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
|
||||
}
|
||||
|
||||
|
@ -1,180 +0,0 @@
|
||||
<?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\FatalErrorHandler;
|
||||
|
||||
use Composer\Autoload\ClassLoader as ComposerClassLoader;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ErrorHandler\DebugClassLoader;
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||
use Symfony\Component\ErrorHandler\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
|
||||
|
||||
class ClassNotFoundFatalErrorHandlerTest extends TestCase
|
||||
{
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
foreach (spl_autoload_functions() as $function) {
|
||||
if (!\is_array($function)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get class loaders wrapped by DebugClassLoader
|
||||
if ($function[0] instanceof DebugClassLoader) {
|
||||
$function = $function[0]->getClassLoader();
|
||||
}
|
||||
|
||||
if ($function[0] instanceof ComposerClassLoader) {
|
||||
$function[0]->add('Symfony_Component_ErrorHandler_Tests_Fixtures', \dirname(__DIR__, 5));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideClassNotFoundData
|
||||
*/
|
||||
public function testHandleClassNotFound(array $error, string $translatedMessage, callable $autoloader = null)
|
||||
{
|
||||
if ($autoloader) {
|
||||
// Unregister all autoloaders to ensure the custom provided
|
||||
// autoloader is the only one to be used during the test run.
|
||||
$autoloaders = spl_autoload_functions();
|
||||
array_map('spl_autoload_unregister', $autoloaders);
|
||||
spl_autoload_register($autoloader);
|
||||
}
|
||||
|
||||
$handler = new ClassNotFoundFatalErrorHandler();
|
||||
|
||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||
|
||||
if ($autoloader) {
|
||||
spl_autoload_unregister($autoloader);
|
||||
array_map('spl_autoload_register', $autoloaders);
|
||||
}
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $exception);
|
||||
$this->assertSame($translatedMessage, $exception->getMessage());
|
||||
$this->assertSame($error['type'], $exception->getSeverity());
|
||||
$this->assertSame($error['file'], $exception->getFile());
|
||||
$this->assertSame($error['line'], $exception->getLine());
|
||||
}
|
||||
|
||||
public function provideClassNotFoundData(): array
|
||||
{
|
||||
$autoloader = new ComposerClassLoader();
|
||||
$autoloader->add('Symfony\Component\ErrorHandler\Exception\\', realpath(__DIR__.'/../../Exception'));
|
||||
$autoloader->add('Symfony_Component_ErrorHandler_Tests_Fixtures', realpath(__DIR__.'/../../Tests/Fixtures'));
|
||||
|
||||
$debugClassLoader = new DebugClassLoader([$autoloader, 'loadClass']);
|
||||
|
||||
return [
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'WhizBangFactory\' not found',
|
||||
],
|
||||
"Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?",
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found',
|
||||
],
|
||||
"Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'UndefinedFunctionException\' not found',
|
||||
],
|
||||
"Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
|
||||
[$debugClassLoader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'PEARClass\' not found',
|
||||
],
|
||||
"Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_ErrorHandler_Tests_Fixtures_PEARClass\"?",
|
||||
[$debugClassLoader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||
],
|
||||
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
|
||||
[$debugClassLoader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||
],
|
||||
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
|
||||
[$autoloader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||
],
|
||||
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException\"?",
|
||||
[$debugClassLoader, 'loadClass'],
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found',
|
||||
],
|
||||
"Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?",
|
||||
function ($className) { /* do nothing here */ },
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testCannotRedeclareClass()
|
||||
{
|
||||
if (!file_exists(__DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP')) {
|
||||
$this->markTestSkipped('Can only be run on case insensitive filesystems');
|
||||
}
|
||||
|
||||
require_once __DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP';
|
||||
|
||||
$error = [
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Class \'Foo\\Bar\\RequiredTwice\' not found',
|
||||
];
|
||||
|
||||
$handler = new ClassNotFoundFatalErrorHandler();
|
||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\ClassNotFoundException', $exception);
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
<?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\FatalErrorHandler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
|
||||
|
||||
class UndefinedFunctionFatalErrorHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideUndefinedFunctionData
|
||||
*/
|
||||
public function testUndefinedFunction(array $error, string $translatedMessage)
|
||||
{
|
||||
$handler = new UndefinedFunctionFatalErrorHandler();
|
||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\UndefinedFunctionException', $exception);
|
||||
// class names are case insensitive and PHP do not return the same
|
||||
$this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage()));
|
||||
$this->assertSame($error['type'], $exception->getSeverity());
|
||||
$this->assertSame($error['file'], $exception->getFile());
|
||||
$this->assertSame($error['line'], $exception->getLine());
|
||||
}
|
||||
|
||||
public function provideUndefinedFunctionData(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined function test_namespaced_function()',
|
||||
],
|
||||
"Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()',
|
||||
],
|
||||
"Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\fatalerrorhandler\\test_namespaced_function\"?",
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined function foo()',
|
||||
],
|
||||
'Attempted to call function "foo" from the global namespace.',
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined function Foo\\Bar\\Baz\\foo()',
|
||||
],
|
||||
'Attempted to call function "foo" from namespace "Foo\Bar\Baz".',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function test_namespaced_function()
|
||||
{
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
<?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\FatalErrorHandler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalErrorException;
|
||||
use Symfony\Component\ErrorHandler\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
|
||||
|
||||
class UndefinedMethodFatalErrorHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideUndefinedMethodData
|
||||
*/
|
||||
public function testUndefinedMethod(array $error, string $translatedMessage)
|
||||
{
|
||||
$handler = new UndefinedMethodFatalErrorHandler();
|
||||
$exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line']));
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\ErrorHandler\Exception\UndefinedMethodException', $exception);
|
||||
$this->assertSame($translatedMessage, $exception->getMessage());
|
||||
$this->assertSame($error['type'], $exception->getSeverity());
|
||||
$this->assertSame($error['file'], $exception->getFile());
|
||||
$this->assertSame($error['line'], $exception->getLine());
|
||||
}
|
||||
|
||||
public function provideUndefinedMethodData(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined method SplObjectStorage::what()',
|
||||
],
|
||||
'Attempted to call an undefined method named "what" of class "SplObjectStorage".',
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined method SplObjectStorage::walid()',
|
||||
],
|
||||
"Attempted to call an undefined method named \"walid\" of class \"SplObjectStorage\".\nDid you mean to call \"valid\"?",
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'line' => 12,
|
||||
'file' => 'foo.php',
|
||||
'message' => 'Call to undefined method SplObjectStorage::offsetFet()',
|
||||
],
|
||||
"Attempted to call an undefined method named \"offsetFet\" of class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?",
|
||||
],
|
||||
[
|
||||
[
|
||||
'type' => 1,
|
||||
'message' => 'Call to undefined method class@anonymous::test()',
|
||||
'file' => '/home/possum/work/symfony/test.php',
|
||||
'line' => 11,
|
||||
],
|
||||
'Attempted to call an undefined method named "test" of class "class@anonymous".',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -23,14 +23,12 @@ if (true) {
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Symfony\Component\ErrorHandler\Exception\ClassNotFoundException)#%d (8) {
|
||||
object(Symfony\Component\ErrorHandler\Error\ClassNotFoundError)#%d (7) {
|
||||
["message":protected]=>
|
||||
string(138) "Attempted to load class "missing" from namespace "Symfony\Component\ErrorHandler".
|
||||
Did you forget a "use" statement for another namespace?"
|
||||
["string":"Exception":private]=>
|
||||
["string":"Error":private]=>
|
||||
string(0) ""
|
||||
["code":protected]=>
|
||||
int(0)
|
||||
@ -38,10 +36,8 @@ Did you forget a "use" statement for another namespace?"
|
||||
string(%d) "%s"
|
||||
["line":protected]=>
|
||||
int(%d)
|
||||
["trace":"Exception":private]=>
|
||||
["trace":"Error":private]=>
|
||||
array(%d) {%A}
|
||||
["previous":"Exception":private]=>
|
||||
["previous":"Error":private]=>
|
||||
NULL
|
||||
["severity":protected]=>
|
||||
int(1)
|
||||
}
|
@ -35,7 +35,18 @@ array(1) {
|
||||
[0]=>
|
||||
string(37) "Error and exception handlers do match"
|
||||
}
|
||||
object(Symfony\Component\ErrorHandler\Exception\FatalErrorException)#%d (%d) {
|
||||
object(Symfony\Component\ErrorHandler\Error\FatalError)#%d (%d) {
|
||||
["error":"Symfony\Component\ErrorHandler\Error\FatalError":private]=>
|
||||
array(4) {
|
||||
["type"]=>
|
||||
int(1)
|
||||
["message"]=>
|
||||
string(179) "Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
|
||||
["file"]=>
|
||||
string(%d) "%s"
|
||||
["line"]=>
|
||||
int(%d)
|
||||
}
|
||||
["message":protected]=>
|
||||
string(186) "Error: Class Symfony\Component\ErrorHandler\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
|
||||
%a
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\ErrorRenderer\Exception;
|
||||
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
|
||||
use Symfony\Component\ErrorHandler\Exception\ErrorException;
|
||||
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
@ -22,6 +22,8 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
* Basically, this class removes all objects from the trace.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FlattenException
|
||||
{
|
||||
@ -37,6 +39,11 @@ class FlattenException
|
||||
private $file;
|
||||
private $line;
|
||||
|
||||
public static function create(\Exception $exception, int $statusCode = null, array $headers = []): self
|
||||
{
|
||||
return static::createFromThrowable($exception, $statusCode, $headers);
|
||||
}
|
||||
|
||||
public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): self
|
||||
{
|
||||
$e = new static();
|
||||
@ -64,7 +71,7 @@ class FlattenException
|
||||
$e->setStatusCode($statusCode);
|
||||
$e->setHeaders($headers);
|
||||
$e->setTraceFromThrowable($exception);
|
||||
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
|
||||
$e->setClass($exception instanceof ErrorException ? $exception->getOriginalClassName() : \get_class($exception));
|
||||
$e->setFile($exception->getFile());
|
||||
$e->setLine($exception->getLine());
|
||||
|
||||
|
@ -0,0 +1,25 @@
|
||||
<?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\Debug\Exception;
|
||||
|
||||
if (!class_exists(FlattenException::class, false)) {
|
||||
class_alias(\Symfony\Component\ErrorRenderer\Exception\FlattenException::class, FlattenException::class);
|
||||
}
|
||||
|
||||
if (false) {
|
||||
/**
|
||||
* @deprecated since Symfony 4.4, use Symfony\Component\ErrorRenderer\Exception\FlattenException instead.
|
||||
*/
|
||||
class FlattenException extends \Symfony\Component\ErrorRenderer\Exception\FlattenException
|
||||
{
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
namespace Symfony\Component\ErrorRenderer\Tests\Exception;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
|
||||
use Symfony\Component\ErrorHandler\Exception\ErrorException;
|
||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
||||
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
@ -132,7 +132,7 @@ class FlattenExceptionTest extends TestCase
|
||||
|
||||
public function testWrappedThrowable()
|
||||
{
|
||||
$exception = new FatalThrowableError(new \DivisionByZeroError('Ouch', 42));
|
||||
$exception = new ErrorException(new \DivisionByZeroError('Ouch', 42));
|
||||
$flattened = FlattenException::createFromThrowable($exception);
|
||||
|
||||
$this->assertSame('Ouch', $flattened->getMessage(), 'The message is copied from the original error.');
|
||||
|
@ -33,6 +33,7 @@
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\ErrorRenderer\\": "" },
|
||||
"classmap": [ "Resources/stubs/Exception/FlattenException.php" ],
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
|
@ -244,7 +244,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
|
||||
// Prevent curl from sending its default Accept and Expect headers
|
||||
foreach (['accept', 'expect'] as $header) {
|
||||
if (!isset($options['normalized_headers'][$header])) {
|
||||
if (!isset($options['normalized_headers'][$header][0])) {
|
||||
$curlopts[CURLOPT_HTTPHEADER][] = $header.':';
|
||||
}
|
||||
}
|
||||
@ -441,7 +441,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
return 0 !== stripos($h, 'Host:');
|
||||
});
|
||||
|
||||
if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) {
|
||||
if (isset($options['normalized_headers']['authorization'][0]) || isset($options['normalized_headers']['cookie'][0])) {
|
||||
$redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
|
||||
return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
|
||||
});
|
||||
|
@ -55,7 +55,7 @@ trait HttpClientTrait
|
||||
}
|
||||
|
||||
if (!isset($options['normalized_headers']['accept'])) {
|
||||
$options['normalized_headers']['accept'] = [$options['headers'][] = 'Accept: *'];
|
||||
$options['normalized_headers']['accept'] = [$options['headers'][] = 'Accept: */*'];
|
||||
}
|
||||
|
||||
if (isset($options['body'])) {
|
||||
|
@ -21,6 +21,34 @@ abstract class HttpClientTestCase extends BaseHttpClientTestCase
|
||||
$this->markTestSkipped('Implemented as of version 4.4');
|
||||
}
|
||||
|
||||
public function testAcceptHeader()
|
||||
{
|
||||
$client = $this->getHttpClient(__FUNCTION__);
|
||||
|
||||
$response = $client->request('GET', 'http://localhost:8057');
|
||||
$requestHeaders = $response->toArray();
|
||||
|
||||
$this->assertSame('*/*', $requestHeaders['HTTP_ACCEPT']);
|
||||
|
||||
$response = $client->request('GET', 'http://localhost:8057', [
|
||||
'headers' => [
|
||||
'Accept' => 'foo/bar',
|
||||
],
|
||||
]);
|
||||
$requestHeaders = $response->toArray();
|
||||
|
||||
$this->assertSame('foo/bar', $requestHeaders['HTTP_ACCEPT']);
|
||||
|
||||
$response = $client->request('GET', 'http://localhost:8057', [
|
||||
'headers' => [
|
||||
'Accept' => null,
|
||||
],
|
||||
]);
|
||||
$requestHeaders = $response->toArray();
|
||||
|
||||
$this->assertArrayNotHasKey('HTTP_ACCEPT', $requestHeaders);
|
||||
}
|
||||
|
||||
public function testToStream()
|
||||
{
|
||||
$client = $this->getHttpClient(__FUNCTION__);
|
||||
|
@ -172,7 +172,7 @@ class HttpClientTraitTest extends TestCase
|
||||
public function testAuthBearerOption()
|
||||
{
|
||||
[, $options] = self::prepareRequest('POST', 'http://example.com', ['auth_bearer' => 'foobar'], HttpClientInterface::OPTIONS_DEFAULTS);
|
||||
$this->assertSame(['Accept: *', 'Authorization: Bearer foobar'], $options['headers']);
|
||||
$this->assertSame(['Accept: */*', 'Authorization: Bearer foobar'], $options['headers']);
|
||||
$this->assertSame(['Authorization: Bearer foobar'], $options['normalized_headers']['authorization']);
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ class MockHttpClientTest extends HttpClientTestCase
|
||||
"SERVER_NAME": "127.0.0.1",
|
||||
"REQUEST_URI": "/",
|
||||
"REQUEST_METHOD": "GET",
|
||||
"HTTP_ACCEPT": "*/*",
|
||||
"HTTP_FOO": "baR",
|
||||
"HTTP_HOST": "localhost:8057"
|
||||
}';
|
||||
@ -114,6 +115,12 @@ class MockHttpClientTest extends HttpClientTestCase
|
||||
$responses[] = $mock;
|
||||
break;
|
||||
|
||||
case 'testAcceptHeader':
|
||||
$responses[] = new MockResponse($body, ['response_headers' => $headers]);
|
||||
$responses[] = new MockResponse(str_replace('*/*', 'foo/bar', $body), ['response_headers' => $headers]);
|
||||
$responses[] = new MockResponse(str_replace('"HTTP_ACCEPT": "*/*",', '', $body), ['response_headers' => $headers]);
|
||||
break;
|
||||
|
||||
case 'testResolve':
|
||||
$responses[] = new MockResponse($body, ['response_headers' => $headers]);
|
||||
$responses[] = new MockResponse($body, ['response_headers' => $headers]);
|
||||
|
@ -42,6 +42,8 @@ CHANGELOG
|
||||
* Marked all dispatched event classes as `@final`
|
||||
* Added `ErrorController` to enable the preview and error rendering mechanism
|
||||
* Getting the container from a non-booted kernel is deprecated.
|
||||
* Deprecated passing the `exception` attribute (instance of `Symfony\Component\Debug\Exception\FlattenException`)
|
||||
to the configured controller of the `ExceptionListener`
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
@ -37,12 +37,12 @@ class ErrorController
|
||||
$this->errorRenderer = $errorRenderer;
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, FlattenException $exception): Response
|
||||
public function __invoke(Request $request, FlattenException $e): Response
|
||||
{
|
||||
try {
|
||||
return new Response($this->errorRenderer->render($exception, $request->getPreferredFormat()), $exception->getStatusCode(), $exception->getHeaders());
|
||||
} catch (ErrorRendererNotFoundException $e) {
|
||||
return new Response($this->errorRenderer->render($exception), $exception->getStatusCode(), $exception->getHeaders());
|
||||
return new Response($this->errorRenderer->render($e, $request->getPreferredFormat()), $e->getStatusCode(), $e->getHeaders());
|
||||
} catch (ErrorRendererNotFoundException $_) {
|
||||
return new Response($this->errorRenderer->render($e), $e->getStatusCode(), $e->getHeaders());
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ class ErrorController
|
||||
*/
|
||||
$subRequest = $request->duplicate(null, null, [
|
||||
'_controller' => $this->controller,
|
||||
'exception' => $exception,
|
||||
'e' => $exception,
|
||||
'logger' => null,
|
||||
'showException' => false,
|
||||
]);
|
||||
|
@ -16,7 +16,7 @@ use Symfony\Component\Console\ConsoleEvents;
|
||||
use Symfony\Component\Console\Event\ConsoleEvent;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\ErrorHandler\ErrorHandler;
|
||||
use Symfony\Component\ErrorHandler\Exception\FatalThrowableError;
|
||||
use Symfony\Component\ErrorHandler\Exception\ErrorException;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
||||
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
||||
@ -112,7 +112,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
||||
}
|
||||
|
||||
if (!$e instanceof \Exception) {
|
||||
$e = new FatalThrowableError($e);
|
||||
$e = new ErrorException($e);
|
||||
}
|
||||
|
||||
$hasRun = true;
|
||||
@ -126,7 +126,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
||||
}
|
||||
$this->exceptionHandler = static function (\Throwable $e) use ($app, $output) {
|
||||
if (!$e instanceof \Exception) {
|
||||
$e = new FatalThrowableError($e);
|
||||
$e = new ErrorException($e);
|
||||
}
|
||||
|
||||
$app->renderException($e, $output);
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\HttpKernel\EventListener;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException;
|
||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
@ -114,9 +115,21 @@ class ExceptionListener implements EventSubscriberInterface
|
||||
*/
|
||||
protected function duplicateRequest(\Exception $exception, Request $request): Request
|
||||
{
|
||||
@trigger_error(sprintf('Passing the "exception" attribute (instance of "%s") to the configured controller of the "%s" class is deprecated since Symfony 4.4, use the passed "e" attribute (instance of "%s") instead.', LegacyFlattenException::class, self::class, FlattenException::class));
|
||||
|
||||
$flattenException = FlattenException::createFromThrowable($exception);
|
||||
|
||||
// BC layer to be removed in 5.0
|
||||
if (class_exists(\Symfony\Component\Debug\Debug::class, false)) {
|
||||
$legacyFlattenException = LegacyFlattenException::createFromThrowable($exception);
|
||||
} else {
|
||||
$legacyFlattenException = $flattenException;
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'_controller' => $this->controller,
|
||||
'exception' => FlattenException::createFromThrowable($exception),
|
||||
'exception' => $legacyFlattenException, // to be removed in 5.0
|
||||
'e' => $flattenException,
|
||||
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
|
||||
];
|
||||
$request = $request->duplicate(null, null, $attributes);
|
||||
|
@ -103,7 +103,7 @@ class ErrorControllerTest extends TestCase
|
||||
->method('handle')
|
||||
->with(
|
||||
$this->callback(function (Request $request) use ($_controller, $code) {
|
||||
$exception = $request->attributes->get('exception');
|
||||
$exception = $request->attributes->get('e');
|
||||
|
||||
$this->assertSame($_controller, $request->attributes->get('_controller'));
|
||||
$this->assertInstanceOf(FlattenException::class, $exception);
|
||||
|
@ -1251,6 +1251,7 @@ final class MimeTypes implements MimeTypesInterface
|
||||
'image/psd' => ['psd'],
|
||||
'image/rle' => ['rle'],
|
||||
'image/sgi' => ['sgi'],
|
||||
'image/svg' => ['svg'],
|
||||
'image/svg+xml' => ['svg', 'svgz'],
|
||||
'image/svg+xml-compressed' => ['svgz'],
|
||||
'image/tiff' => ['tiff', 'tif'],
|
||||
@ -2808,7 +2809,7 @@ final class MimeTypes implements MimeTypesInterface
|
||||
'sv4crc' => ['application/x-sv4crc'],
|
||||
'svc' => ['application/vnd.dvb.service'],
|
||||
'svd' => ['application/vnd.svd'],
|
||||
'svg' => ['image/svg+xml'],
|
||||
'svg' => ['image/svg+xml', 'image/svg'],
|
||||
'svgz' => ['image/svg+xml', 'image/svg+xml-compressed'],
|
||||
'svh' => ['text/x-svhdr'],
|
||||
'swa' => ['application/x-director'],
|
||||
|
@ -47,6 +47,8 @@ class MimeTypesTest extends AbstractMimeTypeGuesserTest
|
||||
$mt = new MimeTypes();
|
||||
$this->assertSame(['mbox'], $mt->getExtensions('application/mbox'));
|
||||
$this->assertSame(['ai', 'eps', 'ps'], $mt->getExtensions('application/postscript'));
|
||||
$this->assertContains('svg', $mt->getExtensions('image/svg+xml'));
|
||||
$this->assertContains('svg', $mt->getExtensions('image/svg'));
|
||||
$this->assertSame([], $mt->getExtensions('application/whatever-symfony'));
|
||||
}
|
||||
|
||||
@ -56,6 +58,8 @@ class MimeTypesTest extends AbstractMimeTypeGuesserTest
|
||||
$this->assertSame(['application/mbox'], $mt->getMimeTypes('mbox'));
|
||||
$this->assertContains('application/postscript', $mt->getMimeTypes('ai'));
|
||||
$this->assertContains('application/postscript', $mt->getMimeTypes('ps'));
|
||||
$this->assertContains('image/svg+xml', $mt->getMimeTypes('svg'));
|
||||
$this->assertContains('image/svg', $mt->getMimeTypes('svg'));
|
||||
$this->assertSame([], $mt->getMimeTypes('symfony'));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user