mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-23 18:48:51 +02:00
N°6934 - Symfony 6.4 - upgrade Symfony bundles to 6.4 (#580)
* Update Symfony lib to version ~6.4.0 * Update code missing return type * Add an iTop general configuration entry to store application secret (Symfony mandatory parameter) * Use dependency injection in ExceptionListener & UserProvider classes
This commit is contained in:
@@ -20,7 +20,7 @@ use Psr\Log\AbstractLogger;
|
||||
*/
|
||||
class BufferingLogger extends AbstractLogger
|
||||
{
|
||||
private $logs = [];
|
||||
private array $logs = [];
|
||||
|
||||
public function log($level, $message, array $context = []): void
|
||||
{
|
||||
@@ -35,14 +35,14 @@ class BufferingLogger extends AbstractLogger
|
||||
return $logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep()
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
@@ -51,21 +51,21 @@ class BufferingLogger extends AbstractLogger
|
||||
public function __destruct()
|
||||
{
|
||||
foreach ($this->logs as [$level, $message, $context]) {
|
||||
if (false !== strpos($message, '{')) {
|
||||
if (str_contains($message, '{')) {
|
||||
foreach ($context as $key => $val) {
|
||||
if (null === $val || \is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) {
|
||||
$message = str_replace("{{$key}}", $val, $message);
|
||||
} elseif ($val instanceof \DateTimeInterface) {
|
||||
$message = str_replace("{{$key}}", $val->format(\DateTime::RFC3339), $message);
|
||||
$message = str_replace("{{$key}}", $val->format(\DateTimeInterface::RFC3339), $message);
|
||||
} elseif (\is_object($val)) {
|
||||
$message = str_replace("{{$key}}", '[object '.\get_class($val).']', $message);
|
||||
$message = str_replace("{{$key}}", '[object '.get_debug_type($val).']', $message);
|
||||
} else {
|
||||
$message = str_replace("{{$key}}", '['.\gettype($val).']', $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error_log(sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message));
|
||||
error_log(sprintf('%s [%s] %s', date(\DateTimeInterface::RFC3339), $level, $message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
6.4
|
||||
---
|
||||
|
||||
* `FlattenExceptionNormalizer` no longer implements `ContextAwareNormalizerInterface`
|
||||
|
||||
6.3
|
||||
---
|
||||
|
||||
* Display exception properties in the HTML error page
|
||||
|
||||
6.1
|
||||
---
|
||||
|
||||
* Report overridden `@final` constants and properties
|
||||
* Read environment variable `SYMFONY_IDE` to configure file link format
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
|
||||
@@ -22,16 +22,15 @@ class Debug
|
||||
{
|
||||
error_reporting(-1);
|
||||
|
||||
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
|
||||
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
|
||||
ini_set('display_errors', 0);
|
||||
} elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || \ini_get('error_log')) {
|
||||
} elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOL) || \ini_get('error_log')) {
|
||||
// CLI - display errors only if they're not already logged to STDERR
|
||||
ini_set('display_errors', 1);
|
||||
}
|
||||
|
||||
@ini_set('zend.assertions', 1);
|
||||
ini_set('assert.active', 1);
|
||||
ini_set('assert.warning', 0);
|
||||
ini_set('assert.exception', 1);
|
||||
|
||||
DebugClassLoader::enable();
|
||||
|
||||
@@ -21,6 +21,7 @@ use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Prophecy\Prophecy\ProphecySubjectInterface;
|
||||
use ProxyManager\Proxy\ProxyInterface;
|
||||
use Symfony\Component\ErrorHandler\Internal\TentativeTypes;
|
||||
use Symfony\Component\VarExporter\LazyObjectInterface;
|
||||
|
||||
/**
|
||||
* Autoloader checking if the class is really defined in the file found.
|
||||
@@ -56,7 +57,7 @@ class DebugClassLoader
|
||||
'null' => 'null',
|
||||
'resource' => 'resource',
|
||||
'boolean' => 'bool',
|
||||
'true' => 'bool',
|
||||
'true' => 'true',
|
||||
'false' => 'false',
|
||||
'integer' => 'int',
|
||||
'array' => 'array',
|
||||
@@ -74,6 +75,7 @@ class DebugClassLoader
|
||||
'$this' => 'static',
|
||||
'list' => 'array',
|
||||
'class-string' => 'string',
|
||||
'never' => 'never',
|
||||
];
|
||||
|
||||
private const BUILTIN_RETURN_TYPES = [
|
||||
@@ -91,6 +93,9 @@ class DebugClassLoader
|
||||
'parent' => true,
|
||||
'mixed' => true,
|
||||
'static' => true,
|
||||
'null' => true,
|
||||
'true' => true,
|
||||
'never' => true,
|
||||
];
|
||||
|
||||
private const MAGIC_METHODS = [
|
||||
@@ -101,34 +106,39 @@ class DebugClassLoader
|
||||
'__serialize' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $classLoader;
|
||||
private $isFinder;
|
||||
private $loaded = [];
|
||||
private $patchTypes;
|
||||
private bool $isFinder;
|
||||
private array $loaded = [];
|
||||
private array $patchTypes = [];
|
||||
|
||||
private static $caseCheck;
|
||||
private static $checkedClasses = [];
|
||||
private static $final = [];
|
||||
private static $finalMethods = [];
|
||||
private static $deprecated = [];
|
||||
private static $internal = [];
|
||||
private static $internalMethods = [];
|
||||
private static $annotatedParameters = [];
|
||||
private static $darwinCache = ['/' => ['/', []]];
|
||||
private static $method = [];
|
||||
private static $returnTypes = [];
|
||||
private static $methodTraits = [];
|
||||
private static $fileOffsets = [];
|
||||
private static int $caseCheck;
|
||||
private static array $checkedClasses = [];
|
||||
private static array $final = [];
|
||||
private static array $finalMethods = [];
|
||||
private static array $finalProperties = [];
|
||||
private static array $finalConstants = [];
|
||||
private static array $deprecated = [];
|
||||
private static array $internal = [];
|
||||
private static array $internalMethods = [];
|
||||
private static array $annotatedParameters = [];
|
||||
private static array $darwinCache = ['/' => ['/', []]];
|
||||
private static array $method = [];
|
||||
private static array $returnTypes = [];
|
||||
private static array $methodTraits = [];
|
||||
private static array $fileOffsets = [];
|
||||
|
||||
public function __construct(callable $classLoader)
|
||||
{
|
||||
$this->classLoader = $classLoader;
|
||||
$this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile');
|
||||
parse_str(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?: '', $this->patchTypes);
|
||||
parse_str($_ENV['SYMFONY_PATCH_TYPE_DECLARATIONS'] ?? $_SERVER['SYMFONY_PATCH_TYPE_DECLARATIONS'] ?? getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?: '', $this->patchTypes);
|
||||
$this->patchTypes += [
|
||||
'force' => null,
|
||||
'php' => \PHP_MAJOR_VERSION.'.'.\PHP_MINOR_VERSION,
|
||||
'deprecations' => \PHP_VERSION_ID >= 70400,
|
||||
'deprecations' => true,
|
||||
];
|
||||
|
||||
if ('phpdoc' === $this->patchTypes['force']) {
|
||||
@@ -146,7 +156,7 @@ class DebugClassLoader
|
||||
if (false === $test || false === $i) {
|
||||
// filesystem is case sensitive
|
||||
self::$caseCheck = 0;
|
||||
} elseif (substr($test, -\strlen($file)) === $file) {
|
||||
} elseif (str_ends_with($test, $file)) {
|
||||
// filesystem is case insensitive and realpath() normalizes the case of characters
|
||||
self::$caseCheck = 1;
|
||||
} elseif ('Darwin' === \PHP_OS_FAMILY) {
|
||||
@@ -245,6 +255,7 @@ class DebugClassLoader
|
||||
&& !is_subclass_of($symbols[$i], ProphecySubjectInterface::class)
|
||||
&& !is_subclass_of($symbols[$i], Proxy::class)
|
||||
&& !is_subclass_of($symbols[$i], ProxyInterface::class)
|
||||
&& !is_subclass_of($symbols[$i], LazyObjectInterface::class)
|
||||
&& !is_subclass_of($symbols[$i], LegacyProxy::class)
|
||||
&& !is_subclass_of($symbols[$i], MockInterface::class)
|
||||
&& !is_subclass_of($symbols[$i], IMock::class)
|
||||
@@ -332,7 +343,7 @@ class DebugClassLoader
|
||||
}
|
||||
|
||||
if (!$exists) {
|
||||
if (false !== strpos($class, '/')) {
|
||||
if (str_contains($class, '/')) {
|
||||
throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
|
||||
}
|
||||
|
||||
@@ -354,7 +365,7 @@ class DebugClassLoader
|
||||
}
|
||||
$deprecations = [];
|
||||
|
||||
$className = false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
|
||||
$className = str_contains($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
|
||||
|
||||
// Don't trigger deprecations for classes in the same vendor
|
||||
if ($class !== $className) {
|
||||
@@ -373,7 +384,7 @@ class DebugClassLoader
|
||||
|
||||
// Detect annotations on the class
|
||||
if ($doc = $this->parsePhpDoc($refl)) {
|
||||
$classIsTemplate = isset($doc['template']);
|
||||
$classIsTemplate = isset($doc['template']) || isset($doc['template-covariant']);
|
||||
|
||||
foreach (['final', 'deprecated', 'internal'] as $annotation) {
|
||||
if (null !== $description = $doc[$annotation][0] ?? null) {
|
||||
@@ -428,7 +439,7 @@ class DebugClassLoader
|
||||
}
|
||||
} elseif (!$refl->isInterface()) {
|
||||
if (!strncmp($vendor, str_replace('_', '\\', $use), $vendorLen)
|
||||
&& 0 === strpos($className, 'Symfony\\')
|
||||
&& str_starts_with($className, 'Symfony\\')
|
||||
&& (!class_exists(InstalledVersions::class)
|
||||
|| 'symfony/symfony' !== InstalledVersions::getRootPackage()['name'])
|
||||
) {
|
||||
@@ -466,8 +477,10 @@ class DebugClassLoader
|
||||
self::$finalMethods[$class] = [];
|
||||
self::$internalMethods[$class] = [];
|
||||
self::$annotatedParameters[$class] = [];
|
||||
self::$finalProperties[$class] = [];
|
||||
self::$finalConstants[$class] = [];
|
||||
foreach ($parentAndOwnInterfaces as $use) {
|
||||
foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes'] as $property) {
|
||||
foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes', 'finalProperties', 'finalConstants'] as $property) {
|
||||
if (isset(self::${$property}[$use])) {
|
||||
self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
|
||||
}
|
||||
@@ -483,7 +496,7 @@ class DebugClassLoader
|
||||
}
|
||||
$returnType = implode('|', $returnType);
|
||||
|
||||
self::$returnTypes[$class] += [$method => [$returnType, 0 === strpos($returnType, '?') ? substr($returnType, 1).'|null' : $returnType, $use, '']];
|
||||
self::$returnTypes[$class] += [$method => [$returnType, str_starts_with($returnType, '?') ? substr($returnType, 1).'|null' : $returnType, $use, '']];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -518,7 +531,7 @@ class DebugClassLoader
|
||||
// To read method annotations
|
||||
$doc = $this->parsePhpDoc($method);
|
||||
|
||||
if (($classIsTemplate || isset($doc['template'])) && $method->hasReturnType()) {
|
||||
if (($classIsTemplate || isset($doc['template']) || isset($doc['template-covariant'])) && $method->hasReturnType()) {
|
||||
unset($doc['return']);
|
||||
}
|
||||
|
||||
@@ -537,7 +550,7 @@ class DebugClassLoader
|
||||
|
||||
$forcePatchTypes = $this->patchTypes['force'];
|
||||
|
||||
if ($canAddReturnType = null !== $forcePatchTypes && false === strpos($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) {
|
||||
if ($canAddReturnType = null !== $forcePatchTypes && !str_contains($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) {
|
||||
if ('void' !== (self::MAGIC_METHODS[$method->name] ?? 'void')) {
|
||||
$this->patchTypes['force'] = $forcePatchTypes ?: 'docblock';
|
||||
}
|
||||
@@ -558,7 +571,7 @@ class DebugClassLoader
|
||||
$this->patchReturnTypeWillChange($method);
|
||||
}
|
||||
|
||||
if (null !== ($returnType ?? $returnType = self::MAGIC_METHODS[$method->name] ?? null) && !$method->hasReturnType() && !isset($doc['return'])) {
|
||||
if (null !== ($returnType ??= self::MAGIC_METHODS[$method->name] ?? null) && !$method->hasReturnType() && !isset($doc['return'])) {
|
||||
[$normalizedType, $returnType, $declaringClass, $declaringFile] = \is_string($returnType) ? [$returnType, $returnType, '', ''] : $returnType;
|
||||
|
||||
if ($canAddReturnType && 'docblock' !== $this->patchTypes['force']) {
|
||||
@@ -622,6 +635,31 @@ class DebugClassLoader
|
||||
}
|
||||
}
|
||||
|
||||
$finals = isset(self::$final[$class]) || $refl->isFinal() ? [] : [
|
||||
'finalConstants' => $refl->getReflectionConstants(\ReflectionClassConstant::IS_PUBLIC | \ReflectionClassConstant::IS_PROTECTED),
|
||||
'finalProperties' => $refl->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED),
|
||||
];
|
||||
foreach ($finals as $type => $reflectors) {
|
||||
foreach ($reflectors as $r) {
|
||||
if ($r->class !== $class) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = $this->parsePhpDoc($r);
|
||||
|
||||
foreach ($parentAndOwnInterfaces as $use) {
|
||||
if (isset(self::${$type}[$use][$r->name]) && !isset($doc['deprecated']) && ('finalConstants' === $type || substr($use, 0, strrpos($use, '\\')) !== substr($use, 0, strrpos($class, '\\')))) {
|
||||
$msg = 'finalConstants' === $type ? '%s" constant' : '$%s" property';
|
||||
$deprecations[] = sprintf('The "%s::'.$msg.' is considered final. You should not override it in "%s".', self::${$type}[$use][$r->name], $r->name, $class);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($doc['final']) || ('finalProperties' === $type && str_starts_with($class, 'Symfony\\') && !$r->hasType())) {
|
||||
self::${$type}[$class][$r->name] = $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $deprecations;
|
||||
}
|
||||
|
||||
@@ -704,7 +742,7 @@ class DebugClassLoader
|
||||
|
||||
$dirFiles = self::$darwinCache[$kDir][1];
|
||||
|
||||
if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) {
|
||||
if (!isset($dirFiles[$file]) && str_ends_with($file, ') : eval()\'d code')) {
|
||||
// Get the file name from "file_name.php(123) : eval()'d code"
|
||||
$file = substr($file, 0, strrpos($file, '(', -17));
|
||||
}
|
||||
@@ -720,7 +758,7 @@ class DebugClassLoader
|
||||
if ('.' !== $f[0]) {
|
||||
$dirFiles[$f] = $f;
|
||||
if ($f === $file) {
|
||||
$kFile = $k = $file;
|
||||
$kFile = $file;
|
||||
} elseif ($f !== $k = strtolower($f)) {
|
||||
$dirFiles[$k] = $f;
|
||||
}
|
||||
@@ -762,14 +800,20 @@ class DebugClassLoader
|
||||
return;
|
||||
}
|
||||
|
||||
if ($nullable = 0 === strpos($types, 'null|')) {
|
||||
if ('null' === $types) {
|
||||
self::$returnTypes[$class][$method] = ['null', 'null', $class, $filename];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($nullable = str_starts_with($types, 'null|')) {
|
||||
$types = substr($types, 5);
|
||||
} elseif ($nullable = '|null' === substr($types, -5)) {
|
||||
} elseif ($nullable = str_ends_with($types, '|null')) {
|
||||
$types = substr($types, 0, -5);
|
||||
}
|
||||
$arrayType = ['array' => 'array'];
|
||||
$typesMap = [];
|
||||
$glue = false !== strpos($types, '&') ? '&' : '|';
|
||||
$glue = str_contains($types, '&') ? '&' : '|';
|
||||
foreach (explode($glue, $types) as $t) {
|
||||
$t = self::SPECIAL_RETURN_TYPES[strtolower($t)] ?? $t;
|
||||
$typesMap[$this->normalizeType($t, $class, $parent, $returnType)][$t] = $t;
|
||||
@@ -794,7 +838,7 @@ class DebugClassLoader
|
||||
$iterable = $object = true;
|
||||
foreach ($typesMap as $n => $t) {
|
||||
if ('null' !== $n) {
|
||||
$iterable = $iterable && (\in_array($n, ['array', 'iterable']) || false !== strpos($n, 'Iterator'));
|
||||
$iterable = $iterable && (\in_array($n, ['array', 'iterable']) || str_contains($n, 'Iterator'));
|
||||
$object = $object && (\in_array($n, ['callable', 'object', '$this', 'static']) || !isset(self::SPECIAL_RETURN_TYPES[$n]));
|
||||
}
|
||||
}
|
||||
@@ -821,6 +865,11 @@ class DebugClassLoader
|
||||
return;
|
||||
}
|
||||
|
||||
if (!preg_match('/^(?:\\\\?[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)+$/', $n)) {
|
||||
// exclude any invalid PHP class name (e.g. `Cookie::SAMESITE_*`)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($phpTypes[''])) {
|
||||
$phpTypes[] = $n;
|
||||
}
|
||||
@@ -863,7 +912,7 @@ class DebugClassLoader
|
||||
// We could resolve "use" statements to return the FQDN
|
||||
// but this would be too expensive for a runtime checker
|
||||
|
||||
if ('[]' !== substr($type, -2)) {
|
||||
if (!str_ends_with($type, '[]')) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
@@ -881,9 +930,9 @@ class DebugClassLoader
|
||||
/**
|
||||
* Utility method to add #[ReturnTypeWillChange] where php triggers deprecations.
|
||||
*/
|
||||
private function patchReturnTypeWillChange(\ReflectionMethod $method)
|
||||
private function patchReturnTypeWillChange(\ReflectionMethod $method): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000 && \count($method->getAttributes(\ReturnTypeWillChange::class))) {
|
||||
if (\count($method->getAttributes(\ReturnTypeWillChange::class))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -909,7 +958,7 @@ class DebugClassLoader
|
||||
/**
|
||||
* Utility method to add @return annotations to the Symfony code-base where it triggers self-deprecations.
|
||||
*/
|
||||
private function patchMethod(\ReflectionMethod $method, string $returnType, string $declaringFile, string $normalizedType)
|
||||
private function patchMethod(\ReflectionMethod $method, string $returnType, string $declaringFile, string $normalizedType): void
|
||||
{
|
||||
static $patchedMethods = [];
|
||||
static $useStatements = [];
|
||||
@@ -921,10 +970,10 @@ class DebugClassLoader
|
||||
$patchedMethods[$file][$startLine] = true;
|
||||
$fileOffset = self::$fileOffsets[$file] ?? 0;
|
||||
$startLine += $fileOffset - 2;
|
||||
if ($nullable = '|null' === substr($returnType, -5)) {
|
||||
if ($nullable = str_ends_with($returnType, '|null')) {
|
||||
$returnType = substr($returnType, 0, -5);
|
||||
}
|
||||
$glue = false !== strpos($returnType, '&') ? '&' : '|';
|
||||
$glue = str_contains($returnType, '&') ? '&' : '|';
|
||||
$returnType = explode($glue, $returnType);
|
||||
$code = file($file);
|
||||
|
||||
@@ -940,10 +989,10 @@ class DebugClassLoader
|
||||
continue;
|
||||
}
|
||||
|
||||
[$namespace, $useOffset, $useMap] = $useStatements[$file] ?? $useStatements[$file] = self::getUseStatements($file);
|
||||
[$namespace, $useOffset, $useMap] = $useStatements[$file] ??= self::getUseStatements($file);
|
||||
|
||||
if ('\\' !== $type[0]) {
|
||||
[$declaringNamespace, , $declaringUseMap] = $useStatements[$declaringFile] ?? $useStatements[$declaringFile] = self::getUseStatements($declaringFile);
|
||||
[$declaringNamespace, , $declaringUseMap] = $useStatements[$declaringFile] ??= self::getUseStatements($declaringFile);
|
||||
|
||||
$p = strpos($type, '\\', 1);
|
||||
$alias = $p ? substr($type, 0, $p) : $type;
|
||||
@@ -981,7 +1030,7 @@ class DebugClassLoader
|
||||
if ('docblock' === $this->patchTypes['force'] || ('object' === $normalizedType && '7.1' === $this->patchTypes['php'])) {
|
||||
$returnType = implode($glue, $returnType).($nullable ? '|null' : '');
|
||||
|
||||
if (false !== strpos($code[$startLine], '#[')) {
|
||||
if (str_contains($code[$startLine], '#[')) {
|
||||
--$startLine;
|
||||
}
|
||||
|
||||
@@ -1022,15 +1071,15 @@ EOTXT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (0 === strpos($file[$i], 'namespace ')) {
|
||||
if (str_starts_with($file[$i], 'namespace ')) {
|
||||
$namespace = substr($file[$i], \strlen('namespace '), -2).'\\';
|
||||
$useOffset = $i + 2;
|
||||
}
|
||||
|
||||
if (0 === strpos($file[$i], 'use ')) {
|
||||
if (str_starts_with($file[$i], 'use ')) {
|
||||
$useOffset = $i;
|
||||
|
||||
for (; 0 === strpos($file[$i], 'use '); ++$i) {
|
||||
for (; str_starts_with($file[$i], 'use '); ++$i) {
|
||||
$u = explode(' as ', substr($file[$i], 4, -2), 2);
|
||||
|
||||
if (1 === \count($u)) {
|
||||
@@ -1048,7 +1097,7 @@ EOTXT;
|
||||
return [$namespace, $useOffset, $useMap];
|
||||
}
|
||||
|
||||
private function fixReturnStatements(\ReflectionMethod $method, string $returnType)
|
||||
private function fixReturnStatements(\ReflectionMethod $method, string $returnType): void
|
||||
{
|
||||
if ('docblock' !== $this->patchTypes['force']) {
|
||||
if ('7.1' === $this->patchTypes['php'] && 'object' === ltrim($returnType, '?')) {
|
||||
@@ -1059,11 +1108,11 @@ EOTXT;
|
||||
return;
|
||||
}
|
||||
|
||||
if ('8.0' > $this->patchTypes['php'] && (false !== strpos($returnType, '|') || \in_array($returnType, ['mixed', 'static'], true))) {
|
||||
if ('8.0' > $this->patchTypes['php'] && (str_contains($returnType, '|') || \in_array($returnType, ['mixed', 'static'], true))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('8.1' > $this->patchTypes['php'] && false !== strpos($returnType, '&')) {
|
||||
if ('8.1' > $this->patchTypes['php'] && str_contains($returnType, '&')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1080,7 +1129,20 @@ EOTXT;
|
||||
}
|
||||
|
||||
$end = $method->isGenerator() ? $i : $method->getEndLine();
|
||||
$inClosure = false;
|
||||
$braces = 0;
|
||||
for (; $i < $end; ++$i) {
|
||||
if (!$inClosure) {
|
||||
$inClosure = str_contains($code[$i], 'function (');
|
||||
}
|
||||
|
||||
if ($inClosure) {
|
||||
$braces += substr_count($code[$i], '{') - substr_count($code[$i], '}');
|
||||
$inClosure = $braces > 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('void' === $returnType) {
|
||||
$fixedCode[$i] = str_replace(' return null;', ' return;', $code[$i]);
|
||||
} elseif ('mixed' === $returnType || '?' === $returnType[0]) {
|
||||
|
||||
@@ -13,9 +13,6 @@ namespace Symfony\Component\ErrorHandler\Error;
|
||||
|
||||
class ClassNotFoundError extends \Error
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(string $message, \Throwable $previous)
|
||||
{
|
||||
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
|
||||
@@ -26,7 +23,6 @@ class ClassNotFoundError extends \Error
|
||||
'trace' => $previous->getTrace(),
|
||||
] as $property => $value) {
|
||||
$refl = new \ReflectionProperty(\Error::class, $property);
|
||||
$refl->setAccessible(true);
|
||||
$refl->setValue($this, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,9 @@ namespace Symfony\Component\ErrorHandler\Error;
|
||||
|
||||
class FatalError extends \Error
|
||||
{
|
||||
private $error;
|
||||
private array $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)
|
||||
@@ -73,15 +71,11 @@ class FatalError extends \Error
|
||||
] as $property => $value) {
|
||||
if (null !== $value) {
|
||||
$refl = new \ReflectionProperty(\Error::class, $property);
|
||||
$refl->setAccessible(true);
|
||||
$refl->setValue($this, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getError(): array
|
||||
{
|
||||
return $this->error;
|
||||
|
||||
@@ -13,9 +13,6 @@ namespace Symfony\Component\ErrorHandler\Error;
|
||||
|
||||
class UndefinedFunctionError extends \Error
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(string $message, \Throwable $previous)
|
||||
{
|
||||
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
|
||||
@@ -26,7 +23,6 @@ class UndefinedFunctionError extends \Error
|
||||
'trace' => $previous->getTrace(),
|
||||
] as $property => $value) {
|
||||
$refl = new \ReflectionProperty(\Error::class, $property);
|
||||
$refl->setAccessible(true);
|
||||
$refl->setValue($this, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ namespace Symfony\Component\ErrorHandler\Error;
|
||||
|
||||
class UndefinedMethodError extends \Error
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(string $message, \Throwable $previous)
|
||||
{
|
||||
parent::__construct($message, $previous->getCode(), $previous->getPrevious());
|
||||
@@ -26,7 +23,6 @@ class UndefinedMethodError extends \Error
|
||||
'trace' => $previous->getTrace(),
|
||||
] as $property => $value) {
|
||||
$refl = new \ReflectionProperty(\Error::class, $property);
|
||||
$refl->setAccessible(true);
|
||||
$refl->setValue($this, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@ use Symfony\Component\ErrorHandler\Error\FatalError;
|
||||
*/
|
||||
class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enhance(\Throwable $error): ?\Throwable
|
||||
{
|
||||
// Some specific versions of PHP produce a fatal error when extending a not found class.
|
||||
@@ -143,7 +140,7 @@ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
|
||||
];
|
||||
|
||||
if ($prefix) {
|
||||
$candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
|
||||
$candidates = array_filter($candidates, fn ($candidate) => str_starts_with($candidate, $prefix));
|
||||
}
|
||||
|
||||
// We cannot use the autoloader here as most of them use require; but if the class
|
||||
@@ -155,9 +152,17 @@ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
|
||||
}
|
||||
}
|
||||
|
||||
// Symfony may ship some polyfills, like "Normalizer". But if the Intl
|
||||
// extension is already installed, the next require_once will fail with
|
||||
// a compile error because the class is already defined. And this one
|
||||
// does not throw a Throwable. So it's better to skip it here.
|
||||
if (str_contains($file, 'Resources/stubs')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
require_once $file;
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,6 @@ use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError;
|
||||
*/
|
||||
class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enhance(\Throwable $error): ?\Throwable
|
||||
{
|
||||
if ($error instanceof FatalError) {
|
||||
@@ -42,7 +39,7 @@ class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface
|
||||
|
||||
$prefix = 'Call to undefined function ';
|
||||
$prefixLen = \strlen($prefix);
|
||||
if (0 !== strpos($message, $prefix)) {
|
||||
if (!str_starts_with($message, $prefix)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,6 @@ use Symfony\Component\ErrorHandler\Error\UndefinedMethodError;
|
||||
*/
|
||||
class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enhance(\Throwable $error): ?\Throwable
|
||||
{
|
||||
if ($error instanceof FatalError) {
|
||||
@@ -47,7 +44,7 @@ class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface
|
||||
$candidates = [];
|
||||
foreach ($methods as $definedMethodName) {
|
||||
$lev = levenshtein($methodName, $definedMethodName);
|
||||
if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) {
|
||||
if ($lev <= \strlen($methodName) / 3 || str_contains($definedMethodName, $methodName)) {
|
||||
$candidates[] = $definedMethodName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
|
||||
*/
|
||||
class ErrorHandler
|
||||
{
|
||||
private $levels = [
|
||||
private array $levels = [
|
||||
\E_DEPRECATED => 'Deprecated',
|
||||
\E_USER_DEPRECATED => 'User Deprecated',
|
||||
\E_NOTICE => 'Notice',
|
||||
@@ -68,7 +68,7 @@ class ErrorHandler
|
||||
\E_CORE_ERROR => 'Core Error',
|
||||
];
|
||||
|
||||
private $loggers = [
|
||||
private array $loggers = [
|
||||
\E_DEPRECATED => [null, LogLevel::INFO],
|
||||
\E_USER_DEPRECATED => [null, LogLevel::INFO],
|
||||
\E_NOTICE => [null, LogLevel::WARNING],
|
||||
@@ -86,24 +86,24 @@ class ErrorHandler
|
||||
\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 $configureException;
|
||||
private $debug;
|
||||
private int $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
|
||||
private int $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
|
||||
private int $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
|
||||
private int $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
|
||||
private int $loggedErrors = 0;
|
||||
private \Closure $configureException;
|
||||
private bool $debug;
|
||||
|
||||
private $isRecursive = 0;
|
||||
private $isRoot = false;
|
||||
private bool $isRecursive = false;
|
||||
private bool $isRoot = false;
|
||||
/** @var callable|null */
|
||||
private $exceptionHandler;
|
||||
private $bootstrappingLogger;
|
||||
private ?BufferingLogger $bootstrappingLogger = null;
|
||||
|
||||
private static $reservedMemory;
|
||||
private static $toStringException;
|
||||
private static $silencedErrorCache = [];
|
||||
private static $silencedErrorCount = 0;
|
||||
private static $exitCode = 0;
|
||||
private static ?string $reservedMemory = null;
|
||||
private static array $silencedErrorCache = [];
|
||||
private static int $silencedErrorCount = 0;
|
||||
private static int $exitCode = 0;
|
||||
|
||||
/**
|
||||
* Registers the error handler.
|
||||
@@ -112,7 +112,7 @@ class ErrorHandler
|
||||
{
|
||||
if (null === self::$reservedMemory) {
|
||||
self::$reservedMemory = str_repeat('x', 32768);
|
||||
register_shutdown_function(__CLASS__.'::handleFatalError');
|
||||
register_shutdown_function(self::handleFatalError(...));
|
||||
}
|
||||
|
||||
if ($handlerIsNew = null === $handler) {
|
||||
@@ -158,11 +158,9 @@ class ErrorHandler
|
||||
/**
|
||||
* Calls a function and turns any PHP error into \ErrorException.
|
||||
*
|
||||
* @return mixed What $function(...$arguments) returns
|
||||
*
|
||||
* @throws \ErrorException When $function(...$arguments) triggers a PHP error
|
||||
*/
|
||||
public static function call(callable $function, ...$arguments)
|
||||
public static function call(callable $function, mixed ...$arguments): mixed
|
||||
{
|
||||
set_error_handler(static function (int $type, string $message, string $file, int $line) {
|
||||
if (__FILE__ === $file) {
|
||||
@@ -188,7 +186,6 @@ class ErrorHandler
|
||||
$this->setDefaultLogger($bootstrappingLogger);
|
||||
}
|
||||
$traceReflector = new \ReflectionProperty(\Exception::class, 'trace');
|
||||
$traceReflector->setAccessible(true);
|
||||
$this->configureException = \Closure::bind(static function ($e, $trace, $file = null, $line = null) use ($traceReflector) {
|
||||
$traceReflector->setValue($e, $trace);
|
||||
$e->file = $file ?? $e->file;
|
||||
@@ -205,7 +202,7 @@ class ErrorHandler
|
||||
* @param array|int|null $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, bool $replace = false): void
|
||||
public function setDefaultLogger(LoggerInterface $logger, array|int|null $levels = \E_ALL, bool $replace = false): void
|
||||
{
|
||||
$loggers = [];
|
||||
|
||||
@@ -216,9 +213,7 @@ class ErrorHandler
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (null === $levels) {
|
||||
$levels = \E_ALL;
|
||||
}
|
||||
$levels ??= \E_ALL;
|
||||
foreach ($this->loggers as $type => $log) {
|
||||
if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
|
||||
$log[0] = $logger;
|
||||
@@ -235,8 +230,6 @@ class ErrorHandler
|
||||
*
|
||||
* @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
|
||||
*
|
||||
* @return array The previous map
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setLoggers(array $loggers): array
|
||||
@@ -283,13 +276,6 @@ class ErrorHandler
|
||||
return $prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a user exception handler.
|
||||
*
|
||||
* @param callable(\Throwable $e)|null $handler
|
||||
*
|
||||
* @return callable|null The previous exception handler
|
||||
*/
|
||||
public function setExceptionHandler(?callable $handler): ?callable
|
||||
{
|
||||
$prev = $this->exceptionHandler;
|
||||
@@ -303,8 +289,6 @@ class ErrorHandler
|
||||
*
|
||||
* @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(int $levels, bool $replace = false): int
|
||||
{
|
||||
@@ -323,8 +307,6 @@ class ErrorHandler
|
||||
*
|
||||
* @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(int $levels, bool $replace = false): int
|
||||
{
|
||||
@@ -342,8 +324,6 @@ class ErrorHandler
|
||||
*
|
||||
* @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(int $levels, bool $replace = false): int
|
||||
{
|
||||
@@ -361,8 +341,6 @@ class ErrorHandler
|
||||
*
|
||||
* @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(int $levels, bool $replace = false): int
|
||||
{
|
||||
@@ -381,7 +359,7 @@ class ErrorHandler
|
||||
private function reRegister(int $prev): void
|
||||
{
|
||||
if ($prev !== ($this->thrownErrors | $this->loggedErrors)) {
|
||||
$handler = set_error_handler('is_int');
|
||||
$handler = set_error_handler(static fn () => null);
|
||||
$handler = \is_array($handler) ? $handler[0] : null;
|
||||
restore_error_handler();
|
||||
if ($handler === $this) {
|
||||
@@ -406,7 +384,7 @@ class ErrorHandler
|
||||
*/
|
||||
public function handleError(int $type, string $message, string $file, int $line): bool
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 70300 && \E_WARNING === $type && '"' === $message[0] && false !== strpos($message, '" targeting switch is equivalent to "break')) {
|
||||
if (\E_WARNING === $type && '"' === $message[0] && str_contains($message, '" targeting switch is equivalent to "break')) {
|
||||
$type = \E_DEPRECATED;
|
||||
}
|
||||
|
||||
@@ -430,10 +408,7 @@ class ErrorHandler
|
||||
|
||||
$logMessage = $this->levels[$type].': '.$message;
|
||||
|
||||
if (null !== self::$toStringException) {
|
||||
$errorAsException = self::$toStringException;
|
||||
self::$toStringException = null;
|
||||
} elseif (!$throw && !($type & $level)) {
|
||||
if (!$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);
|
||||
@@ -457,7 +432,7 @@ class ErrorHandler
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (false !== strpos($message, '@anonymous')) {
|
||||
if (str_contains($message, '@anonymous')) {
|
||||
$backtrace = debug_backtrace(false, 5);
|
||||
|
||||
for ($i = 1; isset($backtrace[$i]); ++$i) {
|
||||
@@ -478,70 +453,26 @@ class ErrorHandler
|
||||
|
||||
if ($throw || $this->tracedErrors & $type) {
|
||||
$backtrace = $errorAsException->getTrace();
|
||||
$lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
|
||||
($this->configureException)($errorAsException, $lightTrace, $file, $line);
|
||||
$backtrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
|
||||
($this->configureException)($errorAsException, $backtrace, $file, $line);
|
||||
} else {
|
||||
($this->configureException)($errorAsException, []);
|
||||
$backtrace = [];
|
||||
}
|
||||
}
|
||||
|
||||
if ($throw) {
|
||||
if (\PHP_VERSION_ID < 70400 && \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.
|
||||
|
||||
$context = 4 < \func_num_args() ? (func_get_arg(4) ?: []) : [];
|
||||
|
||||
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 (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) {
|
||||
$currentErrorHandler = set_error_handler('is_int');
|
||||
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 (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) {
|
||||
set_error_handler($currentErrorHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,7 +484,7 @@ class ErrorHandler
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function handleException(\Throwable $exception)
|
||||
public function handleException(\Throwable $exception): void
|
||||
{
|
||||
$handlerException = null;
|
||||
|
||||
@@ -566,7 +497,7 @@ class ErrorHandler
|
||||
}
|
||||
|
||||
if ($this->loggedErrors & $type) {
|
||||
if (false !== strpos($message = $exception->getMessage(), "@anonymous\0")) {
|
||||
if (str_contains($message = $exception->getMessage(), "@anonymous\0")) {
|
||||
$message = $this->parseAnonymousClass($message);
|
||||
}
|
||||
|
||||
@@ -586,14 +517,7 @@ class ErrorHandler
|
||||
}
|
||||
}
|
||||
|
||||
if (!$exception instanceof OutOfMemoryError) {
|
||||
foreach ($this->getErrorEnhancers() as $errorEnhancer) {
|
||||
if ($e = $errorEnhancer->enhance($exception)) {
|
||||
$exception = $e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$exception = $this->enhanceError($exception);
|
||||
|
||||
$exceptionHandler = $this->exceptionHandler;
|
||||
$this->exceptionHandler = [$this, 'renderException'];
|
||||
@@ -604,9 +528,11 @@ class ErrorHandler
|
||||
|
||||
try {
|
||||
if (null !== $exceptionHandler) {
|
||||
return $exceptionHandler($exception);
|
||||
$exceptionHandler($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
$handlerException = $handlerException ?: $exception;
|
||||
$handlerException ??= $exception;
|
||||
} catch (\Throwable $handlerException) {
|
||||
}
|
||||
if ($exception === $handlerException && null === $this->exceptionHandler) {
|
||||
@@ -682,7 +608,7 @@ class ErrorHandler
|
||||
$handler->throwAt(0, true);
|
||||
$trace = $error['backtrace'] ?? null;
|
||||
|
||||
if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
|
||||
if (str_starts_with($error['message'], 'Allowed memory') || str_starts_with($error['message'], 'Out of memory')) {
|
||||
$fatalError = new OutOfMemoryError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, false, $trace);
|
||||
} else {
|
||||
$fatalError = new FatalError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, true, $trace);
|
||||
@@ -696,7 +622,7 @@ class ErrorHandler
|
||||
self::$exitCode = 255;
|
||||
$handler->handleException($fatalError);
|
||||
}
|
||||
} catch (FatalError $e) {
|
||||
} catch (FatalError) {
|
||||
// Ignore this re-throw
|
||||
}
|
||||
|
||||
@@ -714,7 +640,7 @@ class ErrorHandler
|
||||
*/
|
||||
private function renderException(\Throwable $exception): void
|
||||
{
|
||||
$renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer($this->debug);
|
||||
$renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer($this->debug);
|
||||
|
||||
$exception = $renderer->render($exception);
|
||||
|
||||
@@ -729,6 +655,21 @@ class ErrorHandler
|
||||
echo $exception->getAsString();
|
||||
}
|
||||
|
||||
public function enhanceError(\Throwable $exception): \Throwable
|
||||
{
|
||||
if ($exception instanceof OutOfMemoryError) {
|
||||
return $exception;
|
||||
}
|
||||
|
||||
foreach ($this->getErrorEnhancers() as $errorEnhancer) {
|
||||
if ($e = $errorEnhancer->enhance($exception)) {
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
return $exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you want to define more error enhancers.
|
||||
*
|
||||
@@ -791,8 +732,6 @@ class ErrorHandler
|
||||
*/
|
||||
private function parseAnonymousClass(string $message): string
|
||||
{
|
||||
return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
|
||||
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
|
||||
}, $message);
|
||||
return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,6 @@ class_exists(CliDumper::class);
|
||||
*/
|
||||
class CliErrorRenderer implements ErrorRendererInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(\Throwable $exception): FlattenException
|
||||
{
|
||||
$cloner = new VarCloner();
|
||||
|
||||
@@ -20,6 +20,16 @@ use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||
*/
|
||||
interface ErrorRendererInterface
|
||||
{
|
||||
public const IDE_LINK_FORMATS = [
|
||||
'textmate' => 'txmt://open?url=file://%f&line=%l',
|
||||
'macvim' => 'mvim://open?url=file://%f&line=%l',
|
||||
'emacs' => 'emacs://open?url=file://%f&line=%l',
|
||||
'sublime' => 'subl://open?url=file://%f&line=%l',
|
||||
'phpstorm' => 'phpstorm://open?file=%f&line=%l',
|
||||
'atom' => 'atom://core/open/file?filename=%f&line=%l',
|
||||
'vscode' => 'vscode://file/%f:%l',
|
||||
];
|
||||
|
||||
/**
|
||||
* Renders a Throwable as a FlattenException.
|
||||
*/
|
||||
|
||||
115
lib/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php
Normal file
115
lib/symfony/error-handler/ErrorRenderer/FileLinkFormatter.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?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\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
/**
|
||||
* Formats debug file links.
|
||||
*
|
||||
* @author Jérémy Romey <jeremy@free-agent.fr>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class FileLinkFormatter
|
||||
{
|
||||
private array|false $fileLinkFormat;
|
||||
private ?RequestStack $requestStack = null;
|
||||
private ?string $baseDir = null;
|
||||
private \Closure|string|null $urlFormat;
|
||||
|
||||
/**
|
||||
* @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand
|
||||
*/
|
||||
public function __construct(string|array $fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, string|\Closure $urlFormat = null)
|
||||
{
|
||||
$fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? '';
|
||||
|
||||
if (!\is_array($f = $fileLinkFormat)) {
|
||||
$f = (ErrorRendererInterface::IDE_LINK_FORMATS[$f] ?? $f) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l';
|
||||
$i = strpos($f, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f);
|
||||
$fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE);
|
||||
}
|
||||
|
||||
$this->fileLinkFormat = $fileLinkFormat;
|
||||
$this->requestStack = $requestStack;
|
||||
$this->baseDir = $baseDir;
|
||||
$this->urlFormat = $urlFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
public function format(string $file, int $line): string|bool
|
||||
{
|
||||
if ($fmt = $this->getFileLinkFormat()) {
|
||||
for ($i = 1; isset($fmt[$i]); ++$i) {
|
||||
if (str_starts_with($file, $k = $fmt[$i++])) {
|
||||
$file = substr_replace($file, $fmt[$i], 0, \strlen($k));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return strtr($fmt[0], ['%f' => $file, '%l' => $line]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
$this->fileLinkFormat = $this->getFileLinkFormat();
|
||||
|
||||
return ['fileLinkFormat'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function generateUrlFormat(UrlGeneratorInterface $router, string $routeName, string $queryString): ?string
|
||||
{
|
||||
try {
|
||||
return $router->generate($routeName).$queryString;
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getFileLinkFormat(): array|false
|
||||
{
|
||||
if ($this->fileLinkFormat) {
|
||||
return $this->fileLinkFormat;
|
||||
}
|
||||
|
||||
if ($this->requestStack && $this->baseDir && $this->urlFormat) {
|
||||
$request = $this->requestStack->getMainRequest();
|
||||
|
||||
if ($request instanceof Request && (!$this->urlFormat instanceof \Closure || $this->urlFormat = ($this->urlFormat)())) {
|
||||
return [
|
||||
$request->getSchemeAndHttpHost().$this->urlFormat,
|
||||
$this->baseDir.\DIRECTORY_SEPARATOR, '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists(\Symfony\Component\HttpKernel\Debug\FileLinkFormatter::class, false)) {
|
||||
class_alias(FileLinkFormatter::class, \Symfony\Component\HttpKernel\Debug\FileLinkFormatter::class);
|
||||
}
|
||||
@@ -15,8 +15,9 @@ use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
||||
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
|
||||
use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator;
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
|
||||
|
||||
/**
|
||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||
@@ -33,41 +34,29 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
||||
private const GHOST_HEART = 'M125.91386369681868,8.305165958366445 C128.95033202169043,-0.40540639102854037 140.8469835342744,8.305165958366445 125.91386369681868,19.504526138305664 C110.98208663272044,8.305165958366445 122.87795231771452,-0.40540639102854037 125.91386369681868,8.305165958366445 z';
|
||||
private const GHOST_PLUS = 'M111.36824226379395,8.969108581542969 L118.69175148010254,8.969108581542969 L118.69175148010254,1.6455793380737305 L126.20429420471191,1.6455793380737305 L126.20429420471191,8.969108581542969 L133.52781105041504,8.969108581542969 L133.52781105041504,16.481630325317383 L126.20429420471191,16.481630325317383 L126.20429420471191,23.805158615112305 L118.69175148010254,23.805158615112305 L118.69175148010254,16.481630325317383 L111.36824226379395,16.481630325317383 z';
|
||||
|
||||
private $debug;
|
||||
private $charset;
|
||||
private $fileLinkFormat;
|
||||
private $projectDir;
|
||||
private $outputBuffer;
|
||||
private $logger;
|
||||
private bool|\Closure $debug;
|
||||
private string $charset;
|
||||
private FileLinkFormatter $fileLinkFormat;
|
||||
private ?string $projectDir;
|
||||
private string|\Closure $outputBuffer;
|
||||
private ?LoggerInterface $logger;
|
||||
|
||||
private static $template = 'views/error.html.php';
|
||||
private static string $template = 'views/error.html.php';
|
||||
|
||||
/**
|
||||
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
|
||||
* @param string|FileLinkFormatter|null $fileLinkFormat
|
||||
* @param bool|callable $outputBuffer The output buffer as a string or a callable that should return it
|
||||
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
|
||||
* @param string|callable $outputBuffer The output buffer as a string or a callable that should return it
|
||||
*/
|
||||
public function __construct($debug = false, string $charset = null, $fileLinkFormat = null, string $projectDir = null, $outputBuffer = '', LoggerInterface $logger = null)
|
||||
public function __construct(bool|callable $debug = false, string $charset = null, string|FileLinkFormatter $fileLinkFormat = null, string $projectDir = null, string|callable $outputBuffer = '', LoggerInterface $logger = null)
|
||||
{
|
||||
if (!\is_bool($debug) && !\is_callable($debug)) {
|
||||
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug)));
|
||||
}
|
||||
|
||||
if (!\is_string($outputBuffer) && !\is_callable($outputBuffer)) {
|
||||
throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($outputBuffer)));
|
||||
}
|
||||
|
||||
$this->debug = $debug;
|
||||
$this->debug = \is_bool($debug) ? $debug : $debug(...);
|
||||
$this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8');
|
||||
$this->fileLinkFormat = $fileLinkFormat ?: (\ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'));
|
||||
$this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : new FileLinkFormatter($fileLinkFormat);
|
||||
$this->projectDir = $projectDir;
|
||||
$this->outputBuffer = $outputBuffer;
|
||||
$this->outputBuffer = \is_string($outputBuffer) ? $outputBuffer : $outputBuffer(...);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(\Throwable $exception): FlattenException
|
||||
{
|
||||
$headers = ['Content-Type' => 'text/html; charset='.$this->charset];
|
||||
@@ -76,7 +65,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
||||
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
|
||||
}
|
||||
|
||||
$exception = FlattenException::createFromThrowable($exception, null, $headers);
|
||||
$exception = FlattenException::createWithDataRepresentation($exception, null, $headers);
|
||||
|
||||
return $exception->setAsString($this->renderException($exception));
|
||||
}
|
||||
@@ -151,14 +140,19 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
||||
'exceptionMessage' => $exceptionMessage,
|
||||
'statusText' => $statusText,
|
||||
'statusCode' => $statusCode,
|
||||
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
|
||||
'logger' => DebugLoggerConfigurator::getDebugLogger($this->logger),
|
||||
'currentContent' => \is_string($this->outputBuffer) ? $this->outputBuffer : ($this->outputBuffer)(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an array as a string.
|
||||
*/
|
||||
private function dumpValue(Data $value): string
|
||||
{
|
||||
$dumper = new HtmlDumper();
|
||||
$dumper->setTheme('light');
|
||||
|
||||
return $dumper->dump($value, true);
|
||||
}
|
||||
|
||||
private function formatArgs(array $args): string
|
||||
{
|
||||
$result = [];
|
||||
@@ -183,7 +177,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
||||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
private function formatArgsAsText(array $args)
|
||||
private function formatArgsAsText(array $args): string
|
||||
{
|
||||
return strip_tags($this->formatArgs($args));
|
||||
}
|
||||
@@ -205,27 +199,13 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
||||
{
|
||||
$file = str_replace('\\', '/', $file);
|
||||
|
||||
if (null !== $this->projectDir && 0 === strpos($file, $this->projectDir)) {
|
||||
if (null !== $this->projectDir && str_starts_with($file, $this->projectDir)) {
|
||||
return ltrim(substr($file, \strlen($this->projectDir)), '/');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link for a given file/line pair.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
private function getFileLink(string $file, int $line)
|
||||
{
|
||||
if ($fmt = $this->fileLinkFormat) {
|
||||
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a file path.
|
||||
*
|
||||
@@ -249,11 +229,9 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
||||
$text .= ' at line '.$line;
|
||||
}
|
||||
|
||||
if (false !== $link = $this->getFileLink($file, $line)) {
|
||||
return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', $this->escape($link), $text);
|
||||
}
|
||||
$link = $this->fileLinkFormat->format($file, $line);
|
||||
|
||||
return $text;
|
||||
return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', $this->escape($link), $text);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,13 +247,21 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
||||
// highlight_file could throw warnings
|
||||
// see https://bugs.php.net/25725
|
||||
$code = @highlight_file($file, true);
|
||||
// remove main code/span tags
|
||||
$code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code);
|
||||
// split multiline spans
|
||||
$code = preg_replace_callback('#<span ([^>]++)>((?:[^<]*+<br \/>)++[^<]*+)</span>#', function ($m) {
|
||||
return "<span $m[1]>".str_replace('<br />', "</span><br /><span $m[1]>", $m[2]).'</span>';
|
||||
}, $code);
|
||||
$content = explode('<br />', $code);
|
||||
if (\PHP_VERSION_ID >= 80300) {
|
||||
// remove main pre/code tags
|
||||
$code = preg_replace('#^<pre.*?>\s*<code.*?>(.*)</code>\s*</pre>#s', '\\1', $code);
|
||||
// split multiline code tags
|
||||
$code = preg_replace_callback('#<code ([^>]++)>((?:[^<]*+\\n)++[^<]*+)</code>#', fn ($m) => "<code $m[1]>".str_replace("\n", "</code>\n<code $m[1]>", $m[2]).'</code>', $code);
|
||||
// Convert spaces to html entities to preserve indentation when rendered
|
||||
$code = str_replace(' ', ' ', $code);
|
||||
$content = explode("\n", $code);
|
||||
} else {
|
||||
// remove main code/span tags
|
||||
$code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code);
|
||||
// split multiline spans
|
||||
$code = preg_replace_callback('#<span ([^>]++)>((?:[^<]*+<br \/>)++[^<]*+)</span>#', fn ($m) => "<span $m[1]>".str_replace('<br />', "</span><br /><span $m[1]>", $m[2]).'</span>', $code);
|
||||
$content = explode('<br />', $code);
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
if (0 > $srcContext) {
|
||||
@@ -292,7 +278,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
||||
return '';
|
||||
}
|
||||
|
||||
private function fixCodeMarkup(string $line)
|
||||
private function fixCodeMarkup(string $line): string
|
||||
{
|
||||
// </span> ending tag from previous line
|
||||
$opening = strpos($line, '<span');
|
||||
@@ -311,16 +297,14 @@ class HtmlErrorRenderer implements ErrorRendererInterface
|
||||
return trim($line);
|
||||
}
|
||||
|
||||
private function formatFileFromText(string $text)
|
||||
private function formatFileFromText(string $text): string
|
||||
{
|
||||
return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) {
|
||||
return 'in '.$this->formatFile($match[2], $match[3]);
|
||||
}, $text);
|
||||
return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text);
|
||||
}
|
||||
|
||||
private function formatLogMessage(string $message, array $context)
|
||||
private function formatLogMessage(string $message, array $context): string
|
||||
{
|
||||
if ($context && false !== strpos($message, '{')) {
|
||||
if ($context && str_contains($message, '{')) {
|
||||
$replacements = [];
|
||||
foreach ($context as $key => $val) {
|
||||
if (\is_scalar($val)) {
|
||||
|
||||
@@ -24,38 +24,27 @@ use Symfony\Component\Serializer\SerializerInterface;
|
||||
*/
|
||||
class SerializerErrorRenderer implements ErrorRendererInterface
|
||||
{
|
||||
private $serializer;
|
||||
private $format;
|
||||
private $fallbackErrorRenderer;
|
||||
private $debug;
|
||||
private SerializerInterface $serializer;
|
||||
private string|\Closure $format;
|
||||
private ErrorRendererInterface $fallbackErrorRenderer;
|
||||
private bool|\Closure $debug;
|
||||
|
||||
/**
|
||||
* @param string|callable(FlattenException) $format The format as a string or a callable that should return it
|
||||
* formats not supported by Request::getMimeTypes() should be given as mime types
|
||||
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
|
||||
*/
|
||||
public function __construct(SerializerInterface $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null, $debug = false)
|
||||
public function __construct(SerializerInterface $serializer, string|callable $format, ErrorRendererInterface $fallbackErrorRenderer = null, bool|callable $debug = false)
|
||||
{
|
||||
if (!\is_string($format) && !\is_callable($format)) {
|
||||
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($format)));
|
||||
}
|
||||
|
||||
if (!\is_bool($debug) && !\is_callable($debug)) {
|
||||
throw new \TypeError(sprintf('Argument 4 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug)));
|
||||
}
|
||||
|
||||
$this->serializer = $serializer;
|
||||
$this->format = $format;
|
||||
$this->format = \is_string($format) ? $format : $format(...);
|
||||
$this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer();
|
||||
$this->debug = $debug;
|
||||
$this->debug = \is_bool($debug) ? $debug : $debug(...);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(\Throwable $exception): FlattenException
|
||||
{
|
||||
$headers = [];
|
||||
$headers = ['Vary' => 'Accept'];
|
||||
$debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception);
|
||||
if ($debug) {
|
||||
$headers['X-Debug-Exception'] = rawurlencode($exception->getMessage());
|
||||
@@ -66,19 +55,17 @@ class SerializerErrorRenderer implements ErrorRendererInterface
|
||||
|
||||
try {
|
||||
$format = \is_string($this->format) ? $this->format : ($this->format)($flattenException);
|
||||
$headers = [
|
||||
'Content-Type' => Request::getMimeTypes($format)[0] ?? $format,
|
||||
'Vary' => 'Accept',
|
||||
];
|
||||
$headers['Content-Type'] = Request::getMimeTypes($format)[0] ?? $format;
|
||||
|
||||
return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, [
|
||||
$flattenException->setAsString($this->serializer->serialize($flattenException, $format, [
|
||||
'exception' => $exception,
|
||||
'debug' => $debug,
|
||||
]))
|
||||
->setHeaders($flattenException->getHeaders() + $headers);
|
||||
} catch (NotEncodableValueException $e) {
|
||||
return $this->fallbackErrorRenderer->render($exception);
|
||||
]));
|
||||
} catch (NotEncodableValueException) {
|
||||
$flattenException = $this->fallbackErrorRenderer->render($exception);
|
||||
}
|
||||
|
||||
return $flattenException->setHeaders($flattenException->getHeaders() + $headers);
|
||||
}
|
||||
|
||||
public static function getPreferredFormat(RequestStack $requestStack): \Closure
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace Symfony\Component\ErrorHandler\Exception;
|
||||
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
use Symfony\Component\VarDumper\Caster\Caster;
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
use Symfony\Component\VarDumper\Cloner\Stub;
|
||||
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||
|
||||
/**
|
||||
* FlattenException wraps a PHP Error or Exception to be able to serialize it.
|
||||
@@ -24,54 +28,26 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
*/
|
||||
class FlattenException
|
||||
{
|
||||
/** @var string */
|
||||
private $message;
|
||||
private string $message;
|
||||
private string|int $code;
|
||||
private ?self $previous = null;
|
||||
private array $trace;
|
||||
private string $traceAsString;
|
||||
private string $class;
|
||||
private int $statusCode;
|
||||
private string $statusText;
|
||||
private array $headers;
|
||||
private string $file;
|
||||
private int $line;
|
||||
private ?string $asString = null;
|
||||
private Data $dataRepresentation;
|
||||
|
||||
/** @var int|string */
|
||||
private $code;
|
||||
|
||||
/** @var self|null */
|
||||
private $previous;
|
||||
|
||||
/** @var array */
|
||||
private $trace;
|
||||
|
||||
/** @var string */
|
||||
private $traceAsString;
|
||||
|
||||
/** @var string */
|
||||
private $class;
|
||||
|
||||
/** @var int */
|
||||
private $statusCode;
|
||||
|
||||
/** @var string */
|
||||
private $statusText;
|
||||
|
||||
/** @var array */
|
||||
private $headers;
|
||||
|
||||
/** @var string */
|
||||
private $file;
|
||||
|
||||
/** @var int */
|
||||
private $line;
|
||||
|
||||
/** @var string|null */
|
||||
private $asString;
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function create(\Exception $exception, int $statusCode = null, array $headers = []): self
|
||||
public static function create(\Exception $exception, int $statusCode = null, array $headers = []): static
|
||||
{
|
||||
return static::createFromThrowable($exception, $statusCode, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): self
|
||||
public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): static
|
||||
{
|
||||
$e = new static();
|
||||
$e->setMessage($exception->getMessage());
|
||||
@@ -84,9 +60,7 @@ class FlattenException
|
||||
$statusCode = 400;
|
||||
}
|
||||
|
||||
if (null === $statusCode) {
|
||||
$statusCode = 500;
|
||||
}
|
||||
$statusCode ??= 500;
|
||||
|
||||
if (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) {
|
||||
$statusText = Response::$statusTexts[$statusCode];
|
||||
@@ -98,7 +72,7 @@ class FlattenException
|
||||
$e->setStatusCode($statusCode);
|
||||
$e->setHeaders($headers);
|
||||
$e->setTraceFromThrowable($exception);
|
||||
$e->setClass(\get_class($exception));
|
||||
$e->setClass(get_debug_type($exception));
|
||||
$e->setFile($exception->getFile());
|
||||
$e->setLine($exception->getLine());
|
||||
|
||||
@@ -111,6 +85,33 @@ class FlattenException
|
||||
return $e;
|
||||
}
|
||||
|
||||
public static function createWithDataRepresentation(\Throwable $throwable, int $statusCode = null, array $headers = [], VarCloner $cloner = null): static
|
||||
{
|
||||
$e = static::createFromThrowable($throwable, $statusCode, $headers);
|
||||
|
||||
static $defaultCloner;
|
||||
|
||||
if (!$cloner ??= $defaultCloner) {
|
||||
$cloner = $defaultCloner = new VarCloner();
|
||||
$cloner->addCasters([
|
||||
\Throwable::class => function (\Throwable $e, array $a, Stub $s, bool $isNested): array {
|
||||
if (!$isNested) {
|
||||
unset($a[Caster::PREFIX_PROTECTED.'message']);
|
||||
unset($a[Caster::PREFIX_PROTECTED.'code']);
|
||||
unset($a[Caster::PREFIX_PROTECTED.'file']);
|
||||
unset($a[Caster::PREFIX_PROTECTED.'line']);
|
||||
unset($a["\0Error\0trace"], $a["\0Exception\0trace"]);
|
||||
unset($a["\0Error\0previous"], $a["\0Exception\0previous"]);
|
||||
}
|
||||
|
||||
return $a;
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
return $e->setDataRepresentation($cloner->cloneVar($throwable));
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$exceptions = [];
|
||||
@@ -119,6 +120,7 @@ class FlattenException
|
||||
'message' => $exception->getMessage(),
|
||||
'class' => $exception->getClass(),
|
||||
'trace' => $exception->getTrace(),
|
||||
'data' => $exception->getDataRepresentation(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -133,7 +135,7 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setStatusCode(int $code): self
|
||||
public function setStatusCode(int $code): static
|
||||
{
|
||||
$this->statusCode = $code;
|
||||
|
||||
@@ -148,7 +150,7 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeaders(array $headers): self
|
||||
public function setHeaders(array $headers): static
|
||||
{
|
||||
$this->headers = $headers;
|
||||
|
||||
@@ -163,9 +165,9 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setClass(string $class): self
|
||||
public function setClass(string $class): static
|
||||
{
|
||||
$this->class = false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
|
||||
$this->class = str_contains($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -178,7 +180,7 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setFile(string $file): self
|
||||
public function setFile(string $file): static
|
||||
{
|
||||
$this->file = $file;
|
||||
|
||||
@@ -193,7 +195,7 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setLine(int $line): self
|
||||
public function setLine(int $line): static
|
||||
{
|
||||
$this->line = $line;
|
||||
|
||||
@@ -208,7 +210,7 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setStatusText(string $statusText): self
|
||||
public function setStatusText(string $statusText): static
|
||||
{
|
||||
$this->statusText = $statusText;
|
||||
|
||||
@@ -223,12 +225,10 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setMessage(string $message): self
|
||||
public function setMessage(string $message): static
|
||||
{
|
||||
if (false !== strpos($message, "@anonymous\0")) {
|
||||
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
|
||||
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
|
||||
}, $message);
|
||||
if (str_contains($message, "@anonymous\0")) {
|
||||
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
|
||||
}
|
||||
|
||||
$this->message = $message;
|
||||
@@ -239,17 +239,15 @@ class FlattenException
|
||||
/**
|
||||
* @return int|string int most of the time (might be a string with PDOException)
|
||||
*/
|
||||
public function getCode()
|
||||
public function getCode(): int|string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string $code
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCode($code): self
|
||||
public function setCode(int|string $code): static
|
||||
{
|
||||
$this->code = $code;
|
||||
|
||||
@@ -264,7 +262,7 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setPrevious(?self $previous): self
|
||||
public function setPrevious(?self $previous): static
|
||||
{
|
||||
$this->previous = $previous;
|
||||
|
||||
@@ -293,7 +291,7 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setTraceFromThrowable(\Throwable $throwable): self
|
||||
public function setTraceFromThrowable(\Throwable $throwable): static
|
||||
{
|
||||
$this->traceAsString = $throwable->getTraceAsString();
|
||||
|
||||
@@ -303,7 +301,7 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setTrace(array $trace, ?string $file, ?int $line): self
|
||||
public function setTrace(array $trace, ?string $file, ?int $line): static
|
||||
{
|
||||
$this->trace = [];
|
||||
$this->trace[] = [
|
||||
@@ -340,6 +338,21 @@ class FlattenException
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataRepresentation(): ?Data
|
||||
{
|
||||
return $this->dataRepresentation ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setDataRepresentation(Data $data): static
|
||||
{
|
||||
$this->dataRepresentation = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function flattenArgs(array $args, int $level = 0, int &$count = 0): array
|
||||
{
|
||||
$result = [];
|
||||
@@ -350,7 +363,7 @@ class FlattenException
|
||||
if ($value instanceof \__PHP_Incomplete_Class) {
|
||||
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
|
||||
} elseif (\is_object($value)) {
|
||||
$result[$key] = ['object', \get_class($value)];
|
||||
$result[$key] = ['object', get_debug_type($value)];
|
||||
} elseif (\is_array($value)) {
|
||||
if ($level > 10) {
|
||||
$result[$key] = ['array', '*DEEP NESTED ARRAY*'];
|
||||
@@ -390,7 +403,7 @@ class FlattenException
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setAsString(?string $asString): self
|
||||
public function setAsString(?string $asString): static
|
||||
{
|
||||
$this->asString = $asString;
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ class SilencedErrorContext implements \JsonSerializable
|
||||
{
|
||||
public $count = 1;
|
||||
|
||||
private $severity;
|
||||
private $file;
|
||||
private $line;
|
||||
private $trace;
|
||||
private int $severity;
|
||||
private string $file;
|
||||
private int $line;
|
||||
private array $trace;
|
||||
|
||||
public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1)
|
||||
{
|
||||
|
||||
@@ -753,6 +753,7 @@ class TentativeTypes
|
||||
'isVariadic' => 'bool',
|
||||
'isStatic' => 'bool',
|
||||
'getClosureThis' => '?object',
|
||||
'getClosureCalledClass' => '?ReflectionClass',
|
||||
'getClosureScopeClass' => '?ReflectionClass',
|
||||
'getDocComment' => 'string|false',
|
||||
'getEndLine' => 'int|false',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2019-2022 Fabien Potencier
|
||||
Copyright (c) 2019-present 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
|
||||
|
||||
@@ -9,12 +9,23 @@
|
||||
--color-warning: #a46a1f;
|
||||
--color-error: #b0413e;
|
||||
--color-muted: #999;
|
||||
--tab-background: #fff;
|
||||
--tab-background: #f0f0f0;
|
||||
--tab-border-color: #e5e5e5;
|
||||
--tab-active-border-color: #d4d4d4;
|
||||
--tab-color: #444;
|
||||
--tab-active-background: #666;
|
||||
--tab-active-color: #fafafa;
|
||||
--tab-active-background: #fff;
|
||||
--tab-active-color: var(--color-text);
|
||||
--tab-disabled-background: #f5f5f5;
|
||||
--tab-disabled-color: #999;
|
||||
--selected-badge-background: #e5e5e5;
|
||||
--selected-badge-color: #525252;
|
||||
--selected-badge-shadow: inset 0 0 0 1px #d4d4d4;
|
||||
--selected-badge-warning-background: #fde496;
|
||||
--selected-badge-warning-color: #785b02;
|
||||
--selected-badge-warning-shadow: inset 0 0 0 1px #e6af05;
|
||||
--selected-badge-danger-background: #FCE9ED;
|
||||
--selected-badge-danger-color: #83122A;
|
||||
--selected-badge-danger-shadow: inset 0 0 0 1px #F5B8C5;
|
||||
--metric-value-background: #fff;
|
||||
--metric-value-color: inherit;
|
||||
--metric-unit-color: #999;
|
||||
@@ -47,12 +58,23 @@
|
||||
--color-text: #e0e0e0;
|
||||
--color-muted: #777;
|
||||
--color-error: #d43934;
|
||||
--tab-background: #555;
|
||||
--tab-color: #ccc;
|
||||
--tab-active-background: #888;
|
||||
--tab-active-color: #fafafa;
|
||||
--tab-background: #404040;
|
||||
--tab-border-color: #737373;
|
||||
--tab-active-border-color: #171717;
|
||||
--tab-color: var(--color-text);
|
||||
--tab-active-background: #d4d4d4;
|
||||
--tab-active-color: #262626;
|
||||
--tab-disabled-background: var(--page-background);
|
||||
--tab-disabled-color: #777;
|
||||
--tab-disabled-color: #a3a3a3;
|
||||
--selected-badge-background: #555;
|
||||
--selected-badge-color: #ddd;
|
||||
--selected-badge-shadow: none;
|
||||
--selected-badge-warning-background: #fcd55f;
|
||||
--selected-badge-warning-color: #785b02;
|
||||
--selected-badge-warning-shadow: inset 0 0 0 1px #af8503;
|
||||
--selected-badge-danger-background: #B41939;
|
||||
--selected-badge-danger-color: #FCE9ED;
|
||||
--selected-badge-danger-shadow: none;
|
||||
--metric-value-background: #555;
|
||||
--metric-value-color: inherit;
|
||||
--metric-unit-color: #999;
|
||||
@@ -83,7 +105,7 @@
|
||||
--card-label-color: var(--tab-active-color);
|
||||
}
|
||||
|
||||
html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}
|
||||
html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}summary{cursor: pointer}
|
||||
|
||||
html {
|
||||
/* always display the vertical scrollbar to avoid jumps when toggling contents */
|
||||
@@ -132,15 +154,96 @@ thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-vis
|
||||
.sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; }
|
||||
.sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; }
|
||||
|
||||
.tab-navigation { margin: 0 0 1em 0; padding: 0; }
|
||||
.tab-navigation li { background: var(--tab-background); border: 1px solid var(--table-border); color: var(--tab-color); cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; }
|
||||
.tab-navigation li .badge { background-color: var(--base-1); color: var(--base-4); display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; white-space: nowrap; }
|
||||
.tab-navigation li.disabled { background: var(--tab-disabled-background); color: var(--tab-disabled-color); }
|
||||
.tab-navigation li.active { background: var(--tab-active-background); color: var(--tab-active-color); z-index: 1100; }
|
||||
.tab-navigation li.active .badge { background-color: var(--base-5); color: var(--base-2); }
|
||||
.tab-content > *:first-child { margin-top: 0; }
|
||||
.tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; }
|
||||
.tab-navigation li .badge.status-error { background: var(--background-error); color: #FFF; }
|
||||
.tab-navigation {
|
||||
background-color: var(--tab-background);
|
||||
border-radius: 6px;
|
||||
box-shadow: inset 0 0 0 1px var(--tab-border-color), 0 0 0 5px var(--page-background);
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 0 15px;
|
||||
padding: 0;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.sf-tabs-sm .tab-navigation {
|
||||
box-shadow: inset 0 0 0 1px var(--tab-border-color), 0 0 0 4px var(--page-background);
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.tab-navigation .tab-control {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
transition: box-shadow .05s ease-in, background-color .05s ease-in;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 4px 14px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
.sf-tabs-sm .tab-navigation .tab-control {
|
||||
font-size: 13px;
|
||||
padding: 2.5px 10px;
|
||||
}
|
||||
.tab-navigation .tab-control:before {
|
||||
background: var(--tab-border-color);
|
||||
bottom: 15%;
|
||||
content: "";
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 15%;
|
||||
width: 1px;
|
||||
}
|
||||
.tab-navigation .tab-control:first-child:before,
|
||||
.tab-navigation .tab-control.active + .tab-control:before,
|
||||
.tab-navigation .tab-control.active:before {
|
||||
width: 0;
|
||||
}
|
||||
.tab-navigation .tab-control .badge {
|
||||
background: var(--selected-badge-background);
|
||||
box-shadow: var(--selected-badge-shadow);
|
||||
color: var(--selected-badge-color);
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
margin-left: 8px;
|
||||
min-width: 10px;
|
||||
padding: 2px 6px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tab-navigation .tab-control.disabled {
|
||||
color: var(--tab-disabled-color);
|
||||
}
|
||||
.tab-navigation .tab-control.active {
|
||||
background-color: var(--tab-active-background);
|
||||
border-radius: 6px;
|
||||
box-shadow: inset 0 0 0 1.5px var(--tab-active-border-color);
|
||||
color: var(--tab-active-color);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.theme-dark .tab-navigation li.active {
|
||||
box-shadow: inset 0 0 0 1px var(--tab-border-color);
|
||||
}
|
||||
.tab-content > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.tab-navigation .tab-control .badge.status-warning {
|
||||
background: var(--selected-badge-warning-background);
|
||||
box-shadow: var(--selected-badge-warning-shadow);
|
||||
color: var(--selected-badge-warning-color);
|
||||
}
|
||||
.tab-navigation .tab-control .badge.status-error {
|
||||
background: var(--selected-badge-danger-background);
|
||||
box-shadow: var(--selected-badge-danger-shadow);
|
||||
color: var(--selected-badge-danger-color);
|
||||
}
|
||||
|
||||
.sf-tabs .tab:not(:first-child) { display: none; }
|
||||
|
||||
[data-filters] { position: relative; }
|
||||
@@ -208,6 +311,10 @@ header .container { display: flex; justify-content: space-between; }
|
||||
.exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; }
|
||||
.exception-message a:hover { border-bottom-color: #ffffff; }
|
||||
|
||||
.exception-properties-wrapper { margin: .8em 0; }
|
||||
.exception-properties { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); }
|
||||
.exception-properties pre { margin: 0; padding: 0.2em 0; }
|
||||
|
||||
.exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; }
|
||||
|
||||
.trace + .trace { margin-top: 30px; }
|
||||
@@ -217,7 +324,7 @@ header .container { display: flex; justify-content: space-between; }
|
||||
.trace-head .icon { position: absolute; right: 0; top: 0; }
|
||||
.trace-head .icon svg { fill: var(--base-5); height: 24px; width: 24px; }
|
||||
|
||||
.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; table-layout: fixed; }
|
||||
.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 0 0 1em; table-layout: fixed; }
|
||||
|
||||
.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }
|
||||
|
||||
|
||||
@@ -1,297 +1,304 @@
|
||||
/* This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig.
|
||||
If you make any change in this file, verify the same change is needed in the other file. */
|
||||
/*<![CDATA[*/
|
||||
if (typeof Sfjs === 'undefined') {
|
||||
Sfjs = (function() {
|
||||
"use strict";
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
if ('classList' in document.documentElement) {
|
||||
var hasClass = function (el, cssClass) { return el.classList.contains(cssClass); };
|
||||
var removeClass = function(el, cssClass) { el.classList.remove(cssClass); };
|
||||
var addClass = function(el, cssClass) { el.classList.add(cssClass); };
|
||||
var toggleClass = function(el, cssClass) { el.classList.toggle(cssClass); };
|
||||
} else {
|
||||
var hasClass = function (el, cssClass) { return el.className.match(new RegExp('\\b' + cssClass + '\\b')); };
|
||||
var removeClass = function(el, cssClass) { el.className = el.className.replace(new RegExp('\\b' + cssClass + '\\b'), ' '); };
|
||||
var addClass = function(el, cssClass) { if (!hasClass(el, cssClass)) { el.className += " " + cssClass; } };
|
||||
var toggleClass = function(el, cssClass) { hasClass(el, cssClass) ? removeClass(el, cssClass) : addClass(el, cssClass); };
|
||||
if ('classList' in document.documentElement) {
|
||||
var hasClass = function (el, cssClass) { return el.classList.contains(cssClass); };
|
||||
var removeClass = function(el, cssClass) { el.classList.remove(cssClass); };
|
||||
var addClass = function(el, cssClass) { el.classList.add(cssClass); };
|
||||
var toggleClass = function(el, cssClass) { el.classList.toggle(cssClass); };
|
||||
} else {
|
||||
var hasClass = function (el, cssClass) { return el.className.match(new RegExp('\\b' + cssClass + '\\b')); };
|
||||
var removeClass = function(el, cssClass) { el.className = el.className.replace(new RegExp('\\b' + cssClass + '\\b'), ' '); };
|
||||
var addClass = function(el, cssClass) { if (!hasClass(el, cssClass)) { el.className += " " + cssClass; } };
|
||||
var toggleClass = function(el, cssClass) { hasClass(el, cssClass) ? removeClass(el, cssClass) : addClass(el, cssClass); };
|
||||
}
|
||||
|
||||
var addEventListener;
|
||||
|
||||
var el = document.createElement('div');
|
||||
if (!('addEventListener' in el)) {
|
||||
addEventListener = function (element, eventName, callback) {
|
||||
element.attachEvent('on' + eventName, callback);
|
||||
};
|
||||
} else {
|
||||
addEventListener = function (element, eventName, callback) {
|
||||
element.addEventListener(eventName, callback, false);
|
||||
};
|
||||
}
|
||||
|
||||
if (navigator.clipboard) {
|
||||
document.querySelectorAll('[data-clipboard-text]').forEach(function(element) {
|
||||
removeClass(element, 'hidden');
|
||||
element.addEventListener('click', function() {
|
||||
navigator.clipboard.writeText(element.getAttribute('data-clipboard-text'));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
(function createTabs() {
|
||||
/* the accessibility options of this component have been defined according to: */
|
||||
/* www.w3.org/WAI/ARIA/apg/example-index/tabs/tabs-manual.html */
|
||||
var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])');
|
||||
|
||||
/* create the tab navigation for each group of tabs */
|
||||
for (var i = 0; i < tabGroups.length; i++) {
|
||||
var tabs = tabGroups[i].querySelectorAll(':scope > .tab');
|
||||
var tabNavigation = document.createElement('div');
|
||||
tabNavigation.className = 'tab-navigation';
|
||||
tabNavigation.setAttribute('role', 'tablist');
|
||||
|
||||
var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */
|
||||
for (var j = 0; j < tabs.length; j++) {
|
||||
var tabId = 'tab-' + i + '-' + j;
|
||||
var tabTitle = tabs[j].querySelector('.tab-title').innerHTML;
|
||||
|
||||
var tabNavigationItem = document.createElement('button');
|
||||
addClass(tabNavigationItem, 'tab-control');
|
||||
tabNavigationItem.setAttribute('data-tab-id', tabId);
|
||||
tabNavigationItem.setAttribute('role', 'tab');
|
||||
tabNavigationItem.setAttribute('aria-controls', tabId);
|
||||
if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; }
|
||||
if (hasClass(tabs[j], 'disabled')) {
|
||||
addClass(tabNavigationItem, 'disabled');
|
||||
}
|
||||
tabNavigationItem.innerHTML = tabTitle;
|
||||
tabNavigation.appendChild(tabNavigationItem);
|
||||
|
||||
var tabContent = tabs[j].querySelector('.tab-content');
|
||||
tabContent.parentElement.setAttribute('id', tabId);
|
||||
}
|
||||
|
||||
tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild);
|
||||
addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active');
|
||||
}
|
||||
|
||||
var addEventListener;
|
||||
/* display the active tab and add the 'click' event listeners */
|
||||
for (i = 0; i < tabGroups.length; i++) {
|
||||
tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation .tab-control');
|
||||
|
||||
var el = document.createElement('div');
|
||||
if (!('addEventListener' in el)) {
|
||||
addEventListener = function (element, eventName, callback) {
|
||||
element.attachEvent('on' + eventName, callback);
|
||||
};
|
||||
} else {
|
||||
addEventListener = function (element, eventName, callback) {
|
||||
element.addEventListener(eventName, callback, false);
|
||||
};
|
||||
}
|
||||
for (j = 0; j < tabNavigation.length; j++) {
|
||||
tabId = tabNavigation[j].getAttribute('data-tab-id');
|
||||
var tabPanel = document.getElementById(tabId);
|
||||
tabPanel.setAttribute('role', 'tabpanel');
|
||||
tabPanel.setAttribute('aria-labelledby', tabId);
|
||||
tabPanel.querySelector('.tab-title').className = 'hidden';
|
||||
|
||||
if (navigator.clipboard) {
|
||||
document.querySelectorAll('[data-clipboard-text]').forEach(function(element) {
|
||||
removeClass(element, 'hidden');
|
||||
element.addEventListener('click', function() {
|
||||
navigator.clipboard.writeText(element.getAttribute('data-clipboard-text'));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
addEventListener: addEventListener,
|
||||
|
||||
createTabs: function() {
|
||||
var tabGroups = document.querySelectorAll('.sf-tabs:not([data-processed=true])');
|
||||
|
||||
/* create the tab navigation for each group of tabs */
|
||||
for (var i = 0; i < tabGroups.length; i++) {
|
||||
var tabs = tabGroups[i].querySelectorAll(':scope > .tab');
|
||||
var tabNavigation = document.createElement('ul');
|
||||
tabNavigation.className = 'tab-navigation';
|
||||
|
||||
var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */
|
||||
for (var j = 0; j < tabs.length; j++) {
|
||||
var tabId = 'tab-' + i + '-' + j;
|
||||
var tabTitle = tabs[j].querySelector('.tab-title').innerHTML;
|
||||
|
||||
var tabNavigationItem = document.createElement('li');
|
||||
tabNavigationItem.setAttribute('data-tab-id', tabId);
|
||||
if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; }
|
||||
if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); }
|
||||
tabNavigationItem.innerHTML = tabTitle;
|
||||
tabNavigation.appendChild(tabNavigationItem);
|
||||
|
||||
var tabContent = tabs[j].querySelector('.tab-content');
|
||||
tabContent.parentElement.setAttribute('id', tabId);
|
||||
}
|
||||
|
||||
tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild);
|
||||
addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active');
|
||||
if (hasClass(tabNavigation[j], 'active')) {
|
||||
tabPanel.className = 'block';
|
||||
tabNavigation[j].setAttribute('aria-selected', 'true');
|
||||
tabNavigation[j].removeAttribute('tabindex');
|
||||
} else {
|
||||
tabPanel.className = 'hidden';
|
||||
tabNavigation[j].removeAttribute('aria-selected');
|
||||
tabNavigation[j].setAttribute('tabindex', '-1');
|
||||
}
|
||||
|
||||
/* display the active tab and add the 'click' event listeners */
|
||||
for (i = 0; i < tabGroups.length; i++) {
|
||||
tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li');
|
||||
tabNavigation[j].addEventListener('click', function(e) {
|
||||
var activeTab = e.target || e.srcElement;
|
||||
|
||||
for (j = 0; j < tabNavigation.length; j++) {
|
||||
tabId = tabNavigation[j].getAttribute('data-tab-id');
|
||||
document.getElementById(tabId).querySelector('.tab-title').className = 'hidden';
|
||||
|
||||
if (hasClass(tabNavigation[j], 'active')) {
|
||||
document.getElementById(tabId).className = 'block';
|
||||
} else {
|
||||
document.getElementById(tabId).className = 'hidden';
|
||||
}
|
||||
|
||||
tabNavigation[j].addEventListener('click', function(e) {
|
||||
var activeTab = e.target || e.srcElement;
|
||||
|
||||
/* needed because when the tab contains HTML contents, user can click */
|
||||
/* on any of those elements instead of their parent '<li>' element */
|
||||
while (activeTab.tagName.toLowerCase() !== 'li') {
|
||||
activeTab = activeTab.parentNode;
|
||||
}
|
||||
|
||||
/* get the full list of tabs through the parent of the active tab element */
|
||||
var tabNavigation = activeTab.parentNode.children;
|
||||
for (var k = 0; k < tabNavigation.length; k++) {
|
||||
var tabId = tabNavigation[k].getAttribute('data-tab-id');
|
||||
document.getElementById(tabId).className = 'hidden';
|
||||
removeClass(tabNavigation[k], 'active');
|
||||
}
|
||||
|
||||
addClass(activeTab, 'active');
|
||||
var activeTabId = activeTab.getAttribute('data-tab-id');
|
||||
document.getElementById(activeTabId).className = 'block';
|
||||
});
|
||||
/* needed because when the tab contains HTML contents, user can click */
|
||||
/* on any of those elements instead of their parent '<button>' element */
|
||||
while (activeTab.tagName.toLowerCase() !== 'button') {
|
||||
activeTab = activeTab.parentNode;
|
||||
}
|
||||
|
||||
tabGroups[i].setAttribute('data-processed', 'true');
|
||||
}
|
||||
},
|
||||
|
||||
createToggles: function() {
|
||||
var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])');
|
||||
|
||||
for (var i = 0; i < toggles.length; i++) {
|
||||
var elementSelector = toggles[i].getAttribute('data-toggle-selector');
|
||||
var element = document.querySelector(elementSelector);
|
||||
|
||||
addClass(element, 'sf-toggle-content');
|
||||
|
||||
if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') {
|
||||
addClass(toggles[i], 'sf-toggle-on');
|
||||
addClass(element, 'sf-toggle-visible');
|
||||
} else {
|
||||
addClass(toggles[i], 'sf-toggle-off');
|
||||
addClass(element, 'sf-toggle-hidden');
|
||||
/* get the full list of tabs through the parent of the active tab element */
|
||||
var tabNavigation = activeTab.parentNode.children;
|
||||
for (var k = 0; k < tabNavigation.length; k++) {
|
||||
var tabId = tabNavigation[k].getAttribute('data-tab-id');
|
||||
document.getElementById(tabId).className = 'hidden';
|
||||
removeClass(tabNavigation[k], 'active');
|
||||
tabNavigation[k].removeAttribute('aria-selected');
|
||||
tabNavigation[k].setAttribute('tabindex', '-1');
|
||||
}
|
||||
|
||||
addEventListener(toggles[i], 'click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if ('' !== window.getSelection().toString()) {
|
||||
/* Don't do anything on text selection */
|
||||
return;
|
||||
}
|
||||
|
||||
var toggle = e.target || e.srcElement;
|
||||
|
||||
/* needed because when the toggle contains HTML contents, user can click */
|
||||
/* on any of those elements instead of their parent '.sf-toggle' element */
|
||||
while (!hasClass(toggle, 'sf-toggle')) {
|
||||
toggle = toggle.parentNode;
|
||||
}
|
||||
|
||||
var element = document.querySelector(toggle.getAttribute('data-toggle-selector'));
|
||||
|
||||
toggleClass(toggle, 'sf-toggle-on');
|
||||
toggleClass(toggle, 'sf-toggle-off');
|
||||
toggleClass(element, 'sf-toggle-hidden');
|
||||
toggleClass(element, 'sf-toggle-visible');
|
||||
|
||||
/* the toggle doesn't change its contents when clicking on it */
|
||||
if (!toggle.hasAttribute('data-toggle-alt-content')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!toggle.hasAttribute('data-toggle-original-content')) {
|
||||
toggle.setAttribute('data-toggle-original-content', toggle.innerHTML);
|
||||
}
|
||||
|
||||
var currentContent = toggle.innerHTML;
|
||||
var originalContent = toggle.getAttribute('data-toggle-original-content');
|
||||
var altContent = toggle.getAttribute('data-toggle-alt-content');
|
||||
toggle.innerHTML = currentContent !== altContent ? altContent : originalContent;
|
||||
});
|
||||
|
||||
/* Prevents from disallowing clicks on links inside toggles */
|
||||
var toggleLinks = toggles[i].querySelectorAll('a');
|
||||
for (var j = 0; j < toggleLinks.length; j++) {
|
||||
addEventListener(toggleLinks[j], 'click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
/* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */
|
||||
var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]');
|
||||
for (var k = 0; k < copyToClipboardElements.length; k++) {
|
||||
addEventListener(copyToClipboardElements[k], 'click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
toggles[i].setAttribute('data-processed', 'true');
|
||||
}
|
||||
},
|
||||
|
||||
createFilters: function() {
|
||||
document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) {
|
||||
var filters = filter.closest('[data-filters]'),
|
||||
type = 'choice',
|
||||
name = filter.dataset.filter,
|
||||
ucName = name.charAt(0).toUpperCase()+name.slice(1),
|
||||
list = document.createElement('ul'),
|
||||
values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'),
|
||||
labels = {},
|
||||
defaults = null,
|
||||
indexed = {},
|
||||
processed = {};
|
||||
if (typeof values === 'string') {
|
||||
type = 'level';
|
||||
labels = values.split(',');
|
||||
values = values.toLowerCase().split(',');
|
||||
defaults = values.length - 1;
|
||||
}
|
||||
addClass(list, 'filter-list');
|
||||
addClass(list, 'filter-list-'+type);
|
||||
values.forEach(function (value, i) {
|
||||
if (value instanceof HTMLElement) {
|
||||
value = value.dataset['filter'+ucName];
|
||||
}
|
||||
if (value in processed) {
|
||||
return;
|
||||
}
|
||||
var option = document.createElement('li'),
|
||||
label = i in labels ? labels[i] : value,
|
||||
active = false,
|
||||
matches;
|
||||
if ('' === label) {
|
||||
option.innerHTML = '<em>(none)</em>';
|
||||
} else {
|
||||
option.innerText = label;
|
||||
}
|
||||
option.dataset.filter = value;
|
||||
option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows');
|
||||
indexed[value] = i;
|
||||
list.appendChild(option);
|
||||
addEventListener(option, 'click', function () {
|
||||
if ('choice' === type) {
|
||||
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
|
||||
if (option.dataset.filter === row.dataset['filter'+ucName]) {
|
||||
toggleClass(row, 'filter-hidden-'+name);
|
||||
}
|
||||
});
|
||||
toggleClass(option, 'active');
|
||||
} else if ('level' === type) {
|
||||
if (i === this.parentNode.querySelectorAll('.active').length - 1) {
|
||||
return;
|
||||
}
|
||||
this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) {
|
||||
if (j <= i) {
|
||||
addClass(currentOption, 'active');
|
||||
if (i === j) {
|
||||
addClass(currentOption, 'last-active');
|
||||
} else {
|
||||
removeClass(currentOption, 'last-active');
|
||||
}
|
||||
} else {
|
||||
removeClass(currentOption, 'active');
|
||||
removeClass(currentOption, 'last-active');
|
||||
}
|
||||
});
|
||||
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
|
||||
if (i < indexed[row.dataset['filter'+ucName]]) {
|
||||
addClass(row, 'filter-hidden-'+name);
|
||||
} else {
|
||||
removeClass(row, 'filter-hidden-'+name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if ('choice' === type) {
|
||||
active = null === defaults || 0 <= defaults.indexOf(value);
|
||||
} else if ('level' === type) {
|
||||
active = i <= defaults;
|
||||
if (active && i === defaults) {
|
||||
addClass(option, 'last-active');
|
||||
}
|
||||
}
|
||||
if (active) {
|
||||
addClass(option, 'active');
|
||||
} else {
|
||||
filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) {
|
||||
toggleClass(row, 'filter-hidden-'+name);
|
||||
});
|
||||
}
|
||||
processed[value] = true;
|
||||
});
|
||||
|
||||
if (1 < list.childNodes.length) {
|
||||
filter.appendChild(list);
|
||||
filter.dataset.filtered = '';
|
||||
}
|
||||
addClass(activeTab, 'active');
|
||||
activeTab.setAttribute('aria-selected', 'true');
|
||||
activeTab.removeAttribute('tabindex');
|
||||
var activeTabId = activeTab.getAttribute('data-tab-id');
|
||||
document.getElementById(activeTabId).className = 'block';
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
tabGroups[i].setAttribute('data-processed', 'true');
|
||||
}
|
||||
})();
|
||||
|
||||
Sfjs.addEventListener(document, 'DOMContentLoaded', function() {
|
||||
Sfjs.createTabs();
|
||||
Sfjs.createToggles();
|
||||
Sfjs.createFilters();
|
||||
});
|
||||
}
|
||||
(function createToggles() {
|
||||
var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])');
|
||||
|
||||
for (var i = 0; i < toggles.length; i++) {
|
||||
var elementSelector = toggles[i].getAttribute('data-toggle-selector');
|
||||
var element = document.querySelector(elementSelector);
|
||||
|
||||
addClass(element, 'sf-toggle-content');
|
||||
|
||||
if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') {
|
||||
addClass(toggles[i], 'sf-toggle-on');
|
||||
addClass(element, 'sf-toggle-visible');
|
||||
} else {
|
||||
addClass(toggles[i], 'sf-toggle-off');
|
||||
addClass(element, 'sf-toggle-hidden');
|
||||
}
|
||||
|
||||
addEventListener(toggles[i], 'click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if ('' !== window.getSelection().toString()) {
|
||||
/* Don't do anything on text selection */
|
||||
return;
|
||||
}
|
||||
|
||||
var toggle = e.target || e.srcElement;
|
||||
|
||||
/* needed because when the toggle contains HTML contents, user can click */
|
||||
/* on any of those elements instead of their parent '.sf-toggle' element */
|
||||
while (!hasClass(toggle, 'sf-toggle')) {
|
||||
toggle = toggle.parentNode;
|
||||
}
|
||||
|
||||
var element = document.querySelector(toggle.getAttribute('data-toggle-selector'));
|
||||
|
||||
toggleClass(toggle, 'sf-toggle-on');
|
||||
toggleClass(toggle, 'sf-toggle-off');
|
||||
toggleClass(element, 'sf-toggle-hidden');
|
||||
toggleClass(element, 'sf-toggle-visible');
|
||||
|
||||
/* the toggle doesn't change its contents when clicking on it */
|
||||
if (!toggle.hasAttribute('data-toggle-alt-content')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!toggle.hasAttribute('data-toggle-original-content')) {
|
||||
toggle.setAttribute('data-toggle-original-content', toggle.innerHTML);
|
||||
}
|
||||
|
||||
var currentContent = toggle.innerHTML;
|
||||
var originalContent = toggle.getAttribute('data-toggle-original-content');
|
||||
var altContent = toggle.getAttribute('data-toggle-alt-content');
|
||||
toggle.innerHTML = currentContent !== altContent ? altContent : originalContent;
|
||||
});
|
||||
|
||||
/* Prevents from disallowing clicks on links inside toggles */
|
||||
var toggleLinks = toggles[i].querySelectorAll('a');
|
||||
for (var j = 0; j < toggleLinks.length; j++) {
|
||||
addEventListener(toggleLinks[j], 'click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
/* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */
|
||||
var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]');
|
||||
for (var k = 0; k < copyToClipboardElements.length; k++) {
|
||||
addEventListener(copyToClipboardElements[k], 'click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
toggles[i].setAttribute('data-processed', 'true');
|
||||
}
|
||||
})();
|
||||
|
||||
(function createFilters() {
|
||||
document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) {
|
||||
var filters = filter.closest('[data-filters]'),
|
||||
type = 'choice',
|
||||
name = filter.dataset.filter,
|
||||
ucName = name.charAt(0).toUpperCase()+name.slice(1),
|
||||
list = document.createElement('ul'),
|
||||
values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'),
|
||||
labels = {},
|
||||
defaults = null,
|
||||
indexed = {},
|
||||
processed = {};
|
||||
if (typeof values === 'string') {
|
||||
type = 'level';
|
||||
labels = values.split(',');
|
||||
values = values.toLowerCase().split(',');
|
||||
defaults = values.length - 1;
|
||||
}
|
||||
addClass(list, 'filter-list');
|
||||
addClass(list, 'filter-list-'+type);
|
||||
values.forEach(function (value, i) {
|
||||
if (value instanceof HTMLElement) {
|
||||
value = value.dataset['filter'+ucName];
|
||||
}
|
||||
if (value in processed) {
|
||||
return;
|
||||
}
|
||||
var option = document.createElement('li'),
|
||||
label = i in labels ? labels[i] : value,
|
||||
active = false,
|
||||
matches;
|
||||
if ('' === label) {
|
||||
option.innerHTML = '<em>(none)</em>';
|
||||
} else {
|
||||
option.innerText = label;
|
||||
}
|
||||
option.dataset.filter = value;
|
||||
option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows');
|
||||
indexed[value] = i;
|
||||
list.appendChild(option);
|
||||
addEventListener(option, 'click', function () {
|
||||
if ('choice' === type) {
|
||||
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
|
||||
if (option.dataset.filter === row.dataset['filter'+ucName]) {
|
||||
toggleClass(row, 'filter-hidden-'+name);
|
||||
}
|
||||
});
|
||||
toggleClass(option, 'active');
|
||||
} else if ('level' === type) {
|
||||
if (i === this.parentNode.querySelectorAll('.active').length - 1) {
|
||||
return;
|
||||
}
|
||||
this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) {
|
||||
if (j <= i) {
|
||||
addClass(currentOption, 'active');
|
||||
if (i === j) {
|
||||
addClass(currentOption, 'last-active');
|
||||
} else {
|
||||
removeClass(currentOption, 'last-active');
|
||||
}
|
||||
} else {
|
||||
removeClass(currentOption, 'active');
|
||||
removeClass(currentOption, 'last-active');
|
||||
}
|
||||
});
|
||||
filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) {
|
||||
if (i < indexed[row.dataset['filter'+ucName]]) {
|
||||
addClass(row, 'filter-hidden-'+name);
|
||||
} else {
|
||||
removeClass(row, 'filter-hidden-'+name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if ('choice' === type) {
|
||||
active = null === defaults || 0 <= defaults.indexOf(value);
|
||||
} else if ('level' === type) {
|
||||
active = i <= defaults;
|
||||
if (active && i === defaults) {
|
||||
addClass(option, 'last-active');
|
||||
}
|
||||
}
|
||||
if (active) {
|
||||
addClass(option, 'active');
|
||||
} else {
|
||||
filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) {
|
||||
toggleClass(row, 'filter-hidden-'+name);
|
||||
});
|
||||
}
|
||||
processed[value] = true;
|
||||
});
|
||||
|
||||
if (1 < list.childNodes.length) {
|
||||
filter.appendChild(list);
|
||||
filter.dataset.filtered = '';
|
||||
}
|
||||
});
|
||||
})();
|
||||
})();
|
||||
/*]]>*/
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
if ('cli' !== \PHP_SAPI) {
|
||||
throw new Exception('This script must be run from the command line.');
|
||||
}
|
||||
|
||||
// Run from the root of the php-src repository, this script generates
|
||||
// a table with all the methods that have a tentative return type.
|
||||
//
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
if ('cli' !== \PHP_SAPI) {
|
||||
throw new Exception('This script must be run from the command line.');
|
||||
}
|
||||
|
||||
if (\in_array('-h', $argv) || \in_array('--help', $argv)) {
|
||||
echo implode(PHP_EOL, [
|
||||
' Patches type declarations based on "@return" PHPDoc and triggers deprecations for',
|
||||
@@ -20,7 +24,7 @@ if (\in_array('-h', $argv) || \in_array('--help', $argv)) {
|
||||
'',
|
||||
' Available configuration via environment variables:',
|
||||
' SYMFONY_PATCH_TYPE_DECLARATIONS',
|
||||
' An url-encoded string to change the behavior of the script. Available parameters:',
|
||||
' A url-encoded string to change the behavior of the script. Available parameters:',
|
||||
' - "force": any value enables deprecation notices - can be any of:',
|
||||
' - "phpdoc" to patch only docblock annotations',
|
||||
' - "2" to add all possible return types',
|
||||
@@ -71,7 +75,7 @@ set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$dep
|
||||
|
||||
$exclude = getenv('SYMFONY_PATCH_TYPE_EXCLUDE') ?: null;
|
||||
foreach ($loader->getClassMap() as $class => $file) {
|
||||
if (false !== strpos($file = realpath($file), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) {
|
||||
if (str_contains($file = realpath($file), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -89,6 +93,6 @@ foreach ($deprecations as $class => $classDeprecations) {
|
||||
echo implode(PHP_EOL, $classDeprecations).PHP_EOL.PHP_EOL;
|
||||
}
|
||||
|
||||
if ($deprecations && false !== strpos(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?? '', 'force')) {
|
||||
if ($deprecations && str_contains(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?? '', 'force')) {
|
||||
echo 'These deprecations might be fixed by the patch script, run this again to check for type deprecations.'.PHP_EOL;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="<?= $this->charset; ?>" />
|
||||
<meta name="robots" content="noindex,nofollow,noarchive" />
|
||||
<meta charset="<?= $this->charset; ?>">
|
||||
<meta name="robots" content="noindex,nofollow,noarchive">
|
||||
<title>An Error Occurred: <?= $statusText; ?></title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>❌</text></svg>">
|
||||
<style><?= $this->include('assets/css/error.css'); ?></style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="exception-message-wrapper">
|
||||
<div class="container">
|
||||
<h1 class="break-long-words exception-message<?= mb_strlen($exceptionMessage) > 180 ? ' long' : ''; ?>"><?= $this->formatFileFromText(nl2br($exceptionMessage)); ?></h1>
|
||||
@@ -35,7 +34,7 @@
|
||||
$last = $exceptionAsArrayCount - 1;
|
||||
foreach ($exceptionAsArray as $i => $e) {
|
||||
foreach ($e['trace'] as $trace) {
|
||||
if ($trace['file'] && false === mb_strpos($trace['file'], '/vendor/') && false === mb_strpos($trace['file'], '/var/cache/') && $i < $last) {
|
||||
if ($trace['file'] && !str_contains($trace['file'], '/vendor/') && !str_contains($trace['file'], '/var/cache/') && $i < $last) {
|
||||
$exceptionWithUserCode[] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="<?= $this->charset; ?>" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta charset="<?= $this->charset; ?>">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title><?= $_message; ?></title>
|
||||
<link rel="icon" type="image/png" href="<?= $this->include('assets/images/favicon.png.base64'); ?>">
|
||||
<style><?= $this->include('assets/css/exception.css'); ?></style>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<?php if ($trace['file']) { ?>
|
||||
<?php
|
||||
$lineNumber = $trace['line'] ?: 1;
|
||||
$fileLink = $this->getFileLink($trace['file'], $lineNumber);
|
||||
$fileLink = $this->fileLinkFormat->format($trace['file'], $lineNumber);
|
||||
$filePath = strtr(strip_tags($this->formatFile($trace['file'], $lineNumber)), [' at line '.$lineNumber => '']);
|
||||
$filePathParts = explode(\DIRECTORY_SEPARATOR, $filePath);
|
||||
?>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
$class = substr($exception['class'], $separator);
|
||||
?>
|
||||
<?php if ('' === $class) { ?>
|
||||
</br>
|
||||
<br>
|
||||
<?php } else { ?>
|
||||
<h3 class="trace-class">
|
||||
<?php if ('' !== $namespace) { ?>
|
||||
@@ -25,13 +25,21 @@
|
||||
<p class="break-long-words trace-message"><?= $this->escape($exception['message']); ?></p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php if (\count($exception['data'] ?? [])) { ?>
|
||||
<details class="exception-properties-wrapper">
|
||||
<summary>Show exception properties</summary>
|
||||
<div class="exception-properties">
|
||||
<?= $this->dumpValue($exception['data']) ?>
|
||||
</div>
|
||||
</details>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<div id="trace-html-<?= $index; ?>" class="sf-toggle-content">
|
||||
<?php
|
||||
$isFirstUserCode = true;
|
||||
foreach ($exception['trace'] as $i => $trace) {
|
||||
$isVendorTrace = $trace['file'] && (false !== mb_strpos($trace['file'], '/vendor/') || false !== mb_strpos($trace['file'], '/var/cache/'));
|
||||
$isVendorTrace = $trace['file'] && (str_contains($trace['file'], '/vendor/') || str_contains($trace['file'], '/var/cache/'));
|
||||
$displayCodeSnippet = $isFirstUserCode && !$isVendorTrace;
|
||||
if ($displayCodeSnippet) {
|
||||
$isFirstUserCode = false;
|
||||
|
||||
@@ -18,10 +18,7 @@ use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
|
||||
*/
|
||||
class ThrowableUtils
|
||||
{
|
||||
/**
|
||||
* @param SilencedErrorContext|\Throwable
|
||||
*/
|
||||
public static function getSeverity($throwable): int
|
||||
public static function getSeverity(SilencedErrorContext|\Throwable $throwable): int
|
||||
{
|
||||
if ($throwable instanceof \ErrorException || $throwable instanceof SilencedErrorContext) {
|
||||
return $throwable->getSeverity();
|
||||
|
||||
@@ -16,14 +16,18 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"php": ">=8.1",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/var-dumper": "^4.4|^5.0|^6.0"
|
||||
"symfony/var-dumper": "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/http-kernel": "^4.4|^5.0|^6.0",
|
||||
"symfony/serializer": "^4.4|^5.0|^6.0",
|
||||
"symfony/deprecation-contracts": "^2.1|^3"
|
||||
"symfony/http-kernel": "^6.4|^7.0",
|
||||
"symfony/serializer": "^5.4|^6.0|^7.0",
|
||||
"symfony/deprecation-contracts": "^2.5|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/deprecation-contracts": "<2.5",
|
||||
"symfony/http-kernel": "<6.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\ErrorHandler\\": "" },
|
||||
|
||||
Reference in New Issue
Block a user