upgraded dependencies

This commit is contained in:
RafficMohammed
2023-01-08 01:59:16 +05:30
parent 51056e3aad
commit f9ae387337
6895 changed files with 133617 additions and 178680 deletions

View File

@@ -1,6 +1,22 @@
CHANGELOG
=========
5.4
---
* Make `DebugClassLoader` trigger deprecation notices on missing return types
* Add `SYMFONY_PATCH_TYPE_DECLARATIONS='force=2'` mode to `DebugClassLoader` to turn annotations into native return types
5.2.0
-----
* added the ability to set `HtmlErrorRenderer::$template` to a custom template to render when not in debug mode.
5.1.0
-----
* The `HtmlErrorRenderer` and `SerializerErrorRenderer` add `X-Debug-Exception` and `X-Debug-Exception-File` headers in debug mode.
4.4.0
-----

View File

@@ -20,6 +20,7 @@ use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
use PHPUnit\Framework\MockObject\MockObject;
use Prophecy\Prophecy\ProphecySubjectInterface;
use ProxyManager\Proxy\ProxyInterface;
use Symfony\Component\ErrorHandler\Internal\TentativeTypes;
/**
* Autoloader checking if the class is really defined in the file found.
@@ -32,10 +33,9 @@ use ProxyManager\Proxy\ProxyInterface;
* This behavior is controlled by the SYMFONY_PATCH_TYPE_DECLARATIONS env var,
* which is a url-encoded array with the follow parameters:
* - "force": any value enables deprecation notices - can be any of:
* - "docblock" to patch only docblock annotations
* - "object" to turn union types to the "object" type when possible (not recommended)
* - "1" to add all possible return types including magic methods
* - "0" to add possible return types excluding magic methods
* - "phpdoc" to patch only docblock annotations
* - "2" to add all possible return types
* - "1" to add return types but only to tests/final/internal/private methods
* - "php": the target version of PHP - e.g. "7.1" doesn't generate "object" types
* - "deprecations": "1" to trigger a deprecation notice when a child class misses a
* return type while the parent declares an "@return" annotation
@@ -56,8 +56,8 @@ class DebugClassLoader
'null' => 'null',
'resource' => 'resource',
'boolean' => 'bool',
'true' => 'bool',
'false' => 'bool',
'true' => 'true',
'false' => 'false',
'integer' => 'int',
'array' => 'array',
'bool' => 'bool',
@@ -70,19 +70,17 @@ class DebugClassLoader
'self' => 'self',
'parent' => 'parent',
'mixed' => 'mixed',
'list' => 'array',
'class-string' => 'string',
] + (\PHP_VERSION_ID >= 80000 ? [
'static' => 'static',
'$this' => 'static',
] : [
'static' => 'object',
'$this' => 'object',
]);
'list' => 'array',
'class-string' => 'string',
'never' => 'never',
];
private const BUILTIN_RETURN_TYPES = [
'void' => true,
'array' => true,
'false' => true,
'bool' => true,
'callable' => true,
'float' => true,
@@ -92,78 +90,19 @@ class DebugClassLoader
'string' => true,
'self' => true,
'parent' => true,
] + (\PHP_VERSION_ID >= 80000 ? [
'mixed' => true,
'static' => true,
] : []);
private const MAGIC_METHODS = [
'__set' => 'void',
'__isset' => 'bool',
'__unset' => 'void',
'__sleep' => 'array',
'__wakeup' => 'void',
'__toString' => 'string',
'__clone' => 'void',
'__debugInfo' => 'array',
'__serialize' => 'array',
'__unserialize' => 'void',
'null' => true,
'true' => true,
'never' => true,
];
private const INTERNAL_TYPES = [
'ArrayAccess' => [
'offsetExists' => 'bool',
'offsetSet' => 'void',
'offsetUnset' => 'void',
],
'Countable' => [
'count' => 'int',
],
'Iterator' => [
'next' => 'void',
'valid' => 'bool',
'rewind' => 'void',
],
'IteratorAggregate' => [
'getIterator' => '\Traversable',
],
'OuterIterator' => [
'getInnerIterator' => '\Iterator',
],
'RecursiveIterator' => [
'hasChildren' => 'bool',
],
'SeekableIterator' => [
'seek' => 'void',
],
'Serializable' => [
'serialize' => 'string',
'unserialize' => 'void',
],
'SessionHandlerInterface' => [
'open' => 'bool',
'close' => 'bool',
'read' => 'string',
'write' => 'bool',
'destroy' => 'bool',
'gc' => 'bool',
],
'SessionIdInterface' => [
'create_sid' => 'string',
],
'SessionUpdateTimestampHandlerInterface' => [
'validateId' => 'bool',
'updateTimestamp' => 'bool',
],
'Throwable' => [
'getMessage' => 'string',
'getCode' => 'int',
'getFile' => 'string',
'getLine' => 'int',
'getTrace' => 'array',
'getPrevious' => '?\Throwable',
'getTraceAsString' => 'string',
],
private const MAGIC_METHODS = [
'__isset' => 'bool',
'__sleep' => 'array',
'__toString' => 'string',
'__debugInfo' => 'array',
'__serialize' => 'array',
];
private $classLoader;
@@ -192,12 +131,16 @@ class DebugClassLoader
parse_str(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?: '', $this->patchTypes);
$this->patchTypes += [
'force' => null,
'php' => null,
'deprecations' => false,
'php' => \PHP_MAJOR_VERSION.'.'.\PHP_MINOR_VERSION,
'deprecations' => \PHP_VERSION_ID >= 70400,
];
if ('phpdoc' === $this->patchTypes['force']) {
$this->patchTypes['force'] = 'docblock';
}
if (!isset(self::$caseCheck)) {
$file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR);
$file = is_file(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR);
$i = strrpos($file, \DIRECTORY_SEPARATOR);
$dir = substr($file, 0, 1 + $i);
$file = substr($file, 1 + $i);
@@ -210,7 +153,7 @@ class DebugClassLoader
} elseif (substr($test, -\strlen($file)) === $file) {
// filesystem is case insensitive and realpath() normalizes the case of characters
self::$caseCheck = 1;
} elseif (false !== stripos(\PHP_OS, 'darwin')) {
} elseif ('Darwin' === \PHP_OS_FAMILY) {
// on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
self::$caseCheck = 2;
} else {
@@ -220,11 +163,6 @@ class DebugClassLoader
}
}
/**
* Gets the wrapped class loader.
*
* @return callable The wrapped class loader
*/
public function getClassLoader(): callable
{
return $this->classLoader;
@@ -415,7 +353,6 @@ class DebugClassLoader
if (
'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7' === $class
|| 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV6' === $class
|| 'Test\Symfony\Component\Debug\Tests' === $refl->getNamespaceName()
) {
return [];
}
@@ -434,34 +371,31 @@ class DebugClassLoader
$vendor = str_replace('_', '\\', substr($class, 0, $vendorLen));
}
$parent = get_parent_class($class) ?: null;
self::$returnTypes[$class] = [];
$classIsTemplate = false;
// Detect annotations on the class
if (false !== $doc = $refl->getDocComment()) {
if ($doc = $this->parsePhpDoc($refl)) {
$classIsTemplate = isset($doc['template']);
foreach (['final', 'deprecated', 'internal'] as $annotation) {
if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
if (null !== $description = $doc[$annotation][0] ?? null) {
self::${$annotation}[$class] = '' !== $description ? ' '.$description.(preg_match('/[.!]$/', $description) ? '' : '.') : '.';
}
}
if ($refl->isInterface() && false !== strpos($doc, 'method') && preg_match_all('#\n \* @method\s+(static\s+)?+([\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\)]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#', $doc, $notice, \PREG_SET_ORDER)) {
foreach ($notice as $method) {
$static = '' !== $method[1] && !empty($method[2]);
$name = $method[3];
$description = $method[4] ?? null;
if (false === strpos($name, '(')) {
$name .= '()';
if ($refl->isInterface() && isset($doc['method'])) {
foreach ($doc['method'] as $name => [$static, $returnType, $signature, $description]) {
self::$method[$class][] = [$class, $static, $returnType, $name.$signature, $description];
if ('' !== $returnType) {
$this->setReturnType($returnType, $refl->name, $name, $refl->getFileName(), $parent);
}
if (null !== $description) {
$description = trim($description);
if (!isset($method[5])) {
$description .= '.';
}
}
self::$method[$class][] = [$class, $name, $static, $description];
}
}
}
$parent = get_parent_class($class) ?: null;
$parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent);
if ($parent) {
$parentAndOwnInterfaces[$parent] = $parent;
@@ -471,7 +405,7 @@ class DebugClassLoader
}
if (isset(self::$final[$parent])) {
$deprecations[] = sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $className);
$deprecations[] = sprintf('The "%s" class is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $className);
}
}
@@ -484,10 +418,10 @@ class DebugClassLoader
$type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
$verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
$deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $className, $type, $verb, $use, self::$deprecated[$use]);
$deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s', $className, $type, $verb, $use, self::$deprecated[$use]);
}
if (isset(self::$internal[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen)) {
$deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $className);
$deprecations[] = sprintf('The "%s" %s is considered internal%s It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $className);
}
if (isset(self::$method[$use])) {
if ($refl->isAbstract()) {
@@ -507,14 +441,13 @@ class DebugClassLoader
}
$hasCall = $refl->hasMethod('__call');
$hasStaticCall = $refl->hasMethod('__callStatic');
foreach (self::$method[$use] as $method) {
[$interface, $name, $static, $description] = $method;
foreach (self::$method[$use] as [$interface, $static, $returnType, $name, $description]) {
if ($static ? $hasStaticCall : $hasCall) {
continue;
}
$realName = substr($name, 0, strpos($name, '('));
if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) {
$deprecations[] = sprintf('Class "%s" should implement method "%s::%s"%s', $className, ($static ? 'static ' : '').$interface, $name, null == $description ? '.' : ': '.$description);
$deprecations[] = sprintf('Class "%s" should implement method "%s::%s%s"%s', $className, ($static ? 'static ' : '').$interface, $name, $returnType ? ': '.$returnType : '', null === $description ? '.' : ': '.$description);
}
}
}
@@ -537,7 +470,6 @@ class DebugClassLoader
self::$finalMethods[$class] = [];
self::$internalMethods[$class] = [];
self::$annotatedParameters[$class] = [];
self::$returnTypes[$class] = [];
foreach ($parentAndOwnInterfaces as $use) {
foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes'] as $property) {
if (isset(self::${$property}[$use])) {
@@ -545,11 +477,17 @@ class DebugClassLoader
}
}
if (null !== (self::INTERNAL_TYPES[$use] ?? null)) {
foreach (self::INTERNAL_TYPES[$use] as $method => $returnType) {
if ('void' !== $returnType) {
self::$returnTypes[$class] += [$method => [$returnType, $returnType, $use, '']];
if (null !== (TentativeTypes::RETURN_TYPES[$use] ?? null)) {
foreach (TentativeTypes::RETURN_TYPES[$use] as $method => $returnType) {
$returnType = explode('|', $returnType);
foreach ($returnType as $i => $t) {
if ('?' !== $t && !isset(self::BUILTIN_RETURN_TYPES[$t])) {
$returnType[$i] = '\\'.$t;
}
}
$returnType = implode('|', $returnType);
self::$returnTypes[$class] += [$method => [$returnType, 0 === strpos($returnType, '?') ? substr($returnType, 1).'|null' : $returnType, $use, '']];
}
}
}
@@ -571,18 +509,22 @@ class DebugClassLoader
if ($parent && isset(self::$finalMethods[$parent][$method->name])) {
[$declaringClass, $message] = self::$finalMethods[$parent][$method->name];
$deprecations[] = sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className);
$deprecations[] = sprintf('The "%s::%s()" method is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className);
}
if (isset(self::$internalMethods[$class][$method->name])) {
[$declaringClass, $message] = self::$internalMethods[$class][$method->name];
if (strncmp($ns, $declaringClass, $len)) {
$deprecations[] = sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className);
$deprecations[] = sprintf('The "%s::%s()" method is considered internal%s It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className);
}
}
// To read method annotations
$doc = $method->getDocComment();
$doc = $this->parsePhpDoc($method);
if (($classIsTemplate || isset($doc['template'])) && $method->hasReturnType()) {
unset($doc['return']);
}
if (isset(self::$annotatedParameters[$class][$method->name])) {
$definedParameters = [];
@@ -591,7 +533,7 @@ class DebugClassLoader
}
foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) {
if (!isset($definedParameters[$parameterName]) && !($doc && preg_match("/\\n\\s+\\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\\\${$parameterName}\\b/", $doc))) {
if (!isset($definedParameters[$parameterName]) && !isset($doc['param'][$parameterName])) {
$deprecations[] = sprintf($deprecation, $className);
}
}
@@ -604,32 +546,33 @@ class DebugClassLoader
$this->patchTypes['force'] = $forcePatchTypes ?: 'docblock';
}
$canAddReturnType = false !== strpos($refl->getFileName(), \DIRECTORY_SEPARATOR.'Tests'.\DIRECTORY_SEPARATOR)
$canAddReturnType = 2 === (int) $forcePatchTypes
|| false !== stripos($method->getFileName(), \DIRECTORY_SEPARATOR.'Tests'.\DIRECTORY_SEPARATOR)
|| $refl->isFinal()
|| $method->isFinal()
|| $method->isPrivate()
|| ('' === (self::$internal[$class] ?? null) && !$refl->isAbstract())
|| '' === (self::$final[$class] ?? null)
|| preg_match('/@(final|internal)$/m', $doc)
|| ('.' === (self::$internal[$class] ?? null) && !$refl->isAbstract())
|| '.' === (self::$final[$class] ?? null)
|| '' === ($doc['final'][0] ?? null)
|| '' === ($doc['internal'][0] ?? null)
;
}
if (null !== ($returnType = self::$returnTypes[$class][$method->name] ?? self::MAGIC_METHODS[$method->name] ?? null) && !$method->hasReturnType() && !($doc && preg_match('/\n\s+\* @return +([^\s<(]+)/', $doc))) {
[$normalizedType, $returnType, $declaringClass, $declaringFile] = \is_string($returnType) ? [$returnType, $returnType, '', ''] : $returnType;
if (null !== ($returnType = self::$returnTypes[$class][$method->name] ?? null) && 'docblock' === $this->patchTypes['force'] && !$method->hasReturnType() && isset(TentativeTypes::RETURN_TYPES[$returnType[2]][$method->name])) {
$this->patchReturnTypeWillChange($method);
}
if ('void' === $normalizedType) {
$canAddReturnType = false;
}
if (null !== ($returnType ?? $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']) {
$this->patchMethod($method, $returnType, $declaringFile, $normalizedType);
}
if (false === strpos($doc, '* @deprecated') && strncmp($ns, $declaringClass, $len)) {
if ($canAddReturnType && 'docblock' === $this->patchTypes['force'] && false === strpos($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) {
if (!isset($doc['deprecated']) && strncmp($ns, $declaringClass, $len)) {
if ('docblock' === $this->patchTypes['force']) {
$this->patchMethod($method, $returnType, $declaringFile, $normalizedType);
} elseif ('' !== $declaringClass && $this->patchTypes['deprecations']) {
$deprecations[] = sprintf('Method "%s::%s()" will return "%s" as of its next major version. Doing the same in %s "%s" will be required when upgrading.', $declaringClass, $method->name, $normalizedType, interface_exists($declaringClass) ? 'implementation' : 'child class', $className);
$deprecations[] = sprintf('Method "%s::%s()" might add "%s" as a native return type declaration in the future. Do the same in %s "%s" now to avoid errors or add an explicit @return annotation to suppress this message.', $declaringClass, $method->name, $normalizedType, interface_exists($declaringClass) ? 'implementation' : 'child class', $className);
}
}
}
@@ -640,11 +583,8 @@ class DebugClassLoader
continue;
}
$matches = [];
if (!$method->hasReturnType() && ((false !== strpos($doc, '@return') && preg_match('/\n\s+\* @return +([^\s<(]+)/', $doc, $matches)) || 'void' !== (self::MAGIC_METHODS[$method->name] ?? 'void'))) {
$matches = $matches ?: [1 => self::MAGIC_METHODS[$method->name]];
$this->setReturnType($matches[1], $method, $parent);
if (isset($doc['return']) || 'void' !== (self::MAGIC_METHODS[$method->name] ?? 'void')) {
$this->setReturnType($doc['return'] ?? self::MAGIC_METHODS[$method->name], $method->class, $method->name, $method->getFileName(), $parent, $method->getReturnType());
if (isset(self::$returnTypes[$class][$method->name][0]) && $canAddReturnType) {
$this->fixReturnStatements($method, self::$returnTypes[$class][$method->name][0]);
@@ -664,17 +604,13 @@ class DebugClassLoader
$finalOrInternal = false;
foreach (['final', 'internal'] as $annotation) {
if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
$message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message];
if (null !== $description = $doc[$annotation][0] ?? null) {
self::${$annotation.'Methods'}[$class][$method->name] = [$class, '' !== $description ? ' '.$description.(preg_match('/[[:punct:]]$/', $description) ? '' : '.') : '.'];
$finalOrInternal = true;
}
}
if ($finalOrInternal || $method->isConstructor() || false === strpos($doc, '@param') || StatelessInvocation::class === $class) {
continue;
}
if (!preg_match_all('#\n\s+\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\$([a-zA-Z0-9_\x7f-\xff]++)#', $doc, $matches, \PREG_SET_ORDER)) {
if ($finalOrInternal || $method->isConstructor() || !isset($doc['param']) || StatelessInvocation::class === $class) {
continue;
}
if (!isset(self::$annotatedParameters[$class][$method->name])) {
@@ -683,9 +619,8 @@ class DebugClassLoader
$definedParameters[$parameter->name] = true;
}
}
foreach ($matches as [, $parameterType, $parameterName]) {
foreach ($doc['param'] as $parameterName => $parameterType) {
if (!isset($definedParameters[$parameterName])) {
$parameterType = trim($parameterType);
self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its %s "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, interface_exists($className) ? 'interface' : 'parent class', $className);
}
}
@@ -779,7 +714,7 @@ class DebugClassLoader
}
if (isset($dirFiles[$file])) {
return $real .= $dirFiles[$file];
return $real.$dirFiles[$file];
}
$kFile = strtolower($file);
@@ -798,7 +733,7 @@ class DebugClassLoader
self::$darwinCache[$kDir][1] = $dirFiles;
}
return $real .= $dirFiles[$kFile];
return $real.$dirFiles[$kFile];
}
/**
@@ -825,25 +760,42 @@ class DebugClassLoader
return $ownInterfaces;
}
private function setReturnType(string $types, \ReflectionMethod $method, ?string $parent): void
private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, \ReflectionType $returnType = null): void
{
$nullable = false;
if ('__construct' === $method) {
return;
}
if ('null' === $types) {
self::$returnTypes[$class][$method] = ['null', 'null', $class, $filename];
return;
}
if ($nullable = 0 === strpos($types, 'null|')) {
$types = substr($types, 5);
} elseif ($nullable = '|null' === substr($types, -5)) {
$types = substr($types, 0, -5);
}
$arrayType = ['array' => 'array'];
$typesMap = [];
foreach (explode('|', $types) as $t) {
$typesMap[$this->normalizeType($t, $method->class, $parent)] = $t;
$glue = false !== strpos($types, '&') ? '&' : '|';
foreach (explode($glue, $types) as $t) {
$t = self::SPECIAL_RETURN_TYPES[strtolower($t)] ?? $t;
$typesMap[$this->normalizeType($t, $class, $parent, $returnType)][$t] = $t;
}
if (isset($typesMap['array'])) {
if (isset($typesMap['Traversable']) || isset($typesMap['\Traversable'])) {
$typesMap['iterable'] = 'array' !== $typesMap['array'] ? $typesMap['array'] : 'iterable';
$typesMap['iterable'] = $arrayType !== $typesMap['array'] ? $typesMap['array'] : ['iterable'];
unset($typesMap['array'], $typesMap['Traversable'], $typesMap['\Traversable']);
} elseif ('array' !== $typesMap['array'] && isset(self::$returnTypes[$method->class][$method->name])) {
} elseif ($arrayType !== $typesMap['array'] && isset(self::$returnTypes[$class][$method]) && !$returnType) {
return;
}
}
if (isset($typesMap['array']) && isset($typesMap['iterable'])) {
if ('[]' === substr($typesMap['array'], -2)) {
if ($arrayType !== $typesMap['array']) {
$typesMap['iterable'] = $typesMap['array'];
}
unset($typesMap['array']);
@@ -857,43 +809,56 @@ class DebugClassLoader
}
}
$normalizedType = key($typesMap);
$returnType = current($typesMap);
$phpTypes = [];
$docTypes = [];
foreach ($typesMap as $n => $t) {
if ('null' === $n) {
$nullable = true;
} elseif ('null' === $normalizedType) {
$normalizedType = $t;
$returnType = $t;
} elseif ($n !== $normalizedType || !preg_match('/^\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $n)) {
if ($iterable) {
$normalizedType = $returnType = 'iterable';
} elseif ($object && 'object' === $this->patchTypes['force']) {
$normalizedType = $returnType = 'object';
} else {
// ignore multi-types return declarations
return;
}
continue;
}
$docTypes[] = $t;
if ('mixed' === $n || 'void' === $n) {
$nullable = false;
$phpTypes = ['' => $n];
continue;
}
if ('resource' === $n) {
// there is no native type for "resource"
return;
}
if (!isset($phpTypes[''])) {
$phpTypes[] = $n;
}
}
$docTypes = array_merge([], ...$docTypes);
if ('void' === $normalizedType || (\PHP_VERSION_ID >= 80000 && 'mixed' === $normalizedType)) {
$nullable = false;
} elseif (!isset(self::BUILTIN_RETURN_TYPES[$normalizedType]) && isset(self::SPECIAL_RETURN_TYPES[$normalizedType])) {
// ignore other special return types
if (!$phpTypes) {
return;
}
if ($nullable) {
$normalizedType = '?'.$normalizedType;
$returnType .= '|null';
if (1 < \count($phpTypes)) {
if ($iterable && '8.0' > $this->patchTypes['php']) {
$phpTypes = $docTypes = ['iterable'];
} elseif ($object && 'object' === $this->patchTypes['force']) {
$phpTypes = $docTypes = ['object'];
} elseif ('8.0' > $this->patchTypes['php']) {
// ignore multi-types return declarations
return;
}
}
self::$returnTypes[$method->class][$method->name] = [$normalizedType, $returnType, $method->class, $method->getFileName()];
$phpType = sprintf($nullable ? (1 < \count($phpTypes) ? '%s|null' : '?%s') : '%s', implode($glue, $phpTypes));
$docType = sprintf($nullable ? '%s|null' : '%s', implode($glue, $docTypes));
self::$returnTypes[$class][$method] = [$phpType, $docType, $class, $filename];
}
private function normalizeType(string $type, string $class, ?string $parent): string
private function normalizeType(string $type, string $class, ?string $parent, ?\ReflectionType $returnType): string
{
if (isset(self::SPECIAL_RETURN_TYPES[$lcType = strtolower($type)])) {
if ('parent' === $lcType = self::SPECIAL_RETURN_TYPES[$lcType]) {
@@ -905,47 +870,78 @@ class DebugClassLoader
return $lcType;
}
if ('[]' === substr($type, -2)) {
return 'array';
}
if (preg_match('/^(array|iterable|callable) *[<(]/', $lcType, $m)) {
return $m[1];
}
// We could resolve "use" statements to return the FQDN
// but this would be too expensive for a runtime checker
return $type;
if ('[]' !== substr($type, -2)) {
return $type;
}
if ($returnType instanceof \ReflectionNamedType) {
$type = $returnType->getName();
if ('mixed' !== $type) {
return isset(self::SPECIAL_RETURN_TYPES[$type]) ? $type : '\\'.$type;
}
}
return 'array';
}
/**
* Utility method to add @return annotations to the Symfony code-base where it triggers a self-deprecations.
* Utility method to add #[ReturnTypeWillChange] where php triggers deprecations.
*/
private function patchReturnTypeWillChange(\ReflectionMethod $method)
{
if (\PHP_VERSION_ID >= 80000 && \count($method->getAttributes(\ReturnTypeWillChange::class))) {
return;
}
if (!is_file($file = $method->getFileName())) {
return;
}
$fileOffset = self::$fileOffsets[$file] ?? 0;
$code = file($file);
$startLine = $method->getStartLine() + $fileOffset - 2;
if (false !== stripos($code[$startLine], 'ReturnTypeWillChange')) {
return;
}
$code[$startLine] .= " #[\\ReturnTypeWillChange]\n";
self::$fileOffsets[$file] = 1 + $fileOffset;
file_put_contents($file, $code);
}
/**
* 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)
{
static $patchedMethods = [];
static $useStatements = [];
if (!file_exists($file = $method->getFileName()) || isset($patchedMethods[$file][$startLine = $method->getStartLine()])) {
if (!is_file($file = $method->getFileName()) || isset($patchedMethods[$file][$startLine = $method->getStartLine()])) {
return;
}
$patchedMethods[$file][$startLine] = true;
$fileOffset = self::$fileOffsets[$file] ?? 0;
$startLine += $fileOffset - 2;
$nullable = '?' === $normalizedType[0] ? '?' : '';
$normalizedType = ltrim($normalizedType, '?');
$returnType = explode('|', $returnType);
if ($nullable = '|null' === substr($returnType, -5)) {
$returnType = substr($returnType, 0, -5);
}
$glue = false !== strpos($returnType, '&') ? '&' : '|';
$returnType = explode($glue, $returnType);
$code = file($file);
foreach ($returnType as $i => $type) {
if (preg_match('/((?:\[\])+)$/', $type, $m)) {
$type = substr($type, 0, -\strlen($m[1]));
$format = '%s'.$m[1];
} elseif (preg_match('/^(array|iterable)<([^,>]++)>$/', $type, $m)) {
$type = $m[2];
$format = $m[1].'<%s>';
} else {
$format = null;
}
@@ -990,14 +986,14 @@ class DebugClassLoader
}
$returnType[$i] = null !== $format ? sprintf($format, $alias) : $alias;
if (!isset(self::SPECIAL_RETURN_TYPES[$normalizedType]) && !isset(self::SPECIAL_RETURN_TYPES[$returnType[$i]])) {
$normalizedType = $returnType[$i];
}
}
if ('docblock' === $this->patchTypes['force'] || ('object' === $normalizedType && '7.1' === $this->patchTypes['php'])) {
$returnType = implode('|', $returnType);
$returnType = implode($glue, $returnType).($nullable ? '|null' : '');
if (false !== strpos($code[$startLine], '#[')) {
--$startLine;
}
if ($method->getDocComment()) {
$code[$startLine] = " * @return $returnType\n".$code[$startLine];
@@ -1016,7 +1012,7 @@ EOTXT;
self::$fileOffsets[$file] = $fileOffset;
file_put_contents($file, $code);
$this->fixReturnStatements($method, $nullable.$normalizedType);
$this->fixReturnStatements($method, $normalizedType);
}
private static function getUseStatements(string $file): array
@@ -1025,7 +1021,7 @@ EOTXT;
$useMap = [];
$useOffset = 0;
if (!file_exists($file)) {
if (!is_file($file)) {
return [$namespace, $useOffset, $useMap];
}
@@ -1064,11 +1060,25 @@ EOTXT;
private function fixReturnStatements(\ReflectionMethod $method, string $returnType)
{
if ('7.1' === $this->patchTypes['php'] && 'object' === ltrim($returnType, '?') && 'docblock' !== $this->patchTypes['force']) {
return;
if ('docblock' !== $this->patchTypes['force']) {
if ('7.1' === $this->patchTypes['php'] && 'object' === ltrim($returnType, '?')) {
return;
}
if ('7.4' > $this->patchTypes['php'] && $method->hasReturnType()) {
return;
}
if ('8.0' > $this->patchTypes['php'] && (false !== strpos($returnType, '|') || \in_array($returnType, ['mixed', 'static'], true))) {
return;
}
if ('8.1' > $this->patchTypes['php'] && false !== strpos($returnType, '&')) {
return;
}
}
if (!file_exists($file = $method->getFileName())) {
if (!is_file($file = $method->getFileName())) {
return;
}
@@ -1076,7 +1086,7 @@ EOTXT;
$i = (self::$fileOffsets[$file] ?? 0) + $method->getStartLine();
if ('?' !== $returnType && 'docblock' !== $this->patchTypes['force']) {
$fixedCode[$i - 1] = preg_replace('/\)(;?\n)/', "): $returnType\\1", $code[$i - 1]);
$fixedCode[$i - 1] = preg_replace('/\)(?::[^;\n]++)?(;?\n)/', "): $returnType\\1", $code[$i - 1]);
}
$end = $method->isGenerator() ? $i : $method->getEndLine();
@@ -1094,4 +1104,118 @@ EOTXT;
file_put_contents($file, $fixedCode);
}
}
/**
* @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector
*/
private function parsePhpDoc(\Reflector $reflector): array
{
if (!$doc = $reflector->getDocComment()) {
return [];
}
$tagName = '';
$tagContent = '';
$tags = [];
foreach (explode("\n", substr($doc, 3, -2)) as $line) {
$line = ltrim($line);
$line = ltrim($line, '*');
if ('' === $line = trim($line)) {
if ('' !== $tagName) {
$tags[$tagName][] = $tagContent;
}
$tagName = $tagContent = '';
continue;
}
if ('@' === $line[0]) {
if ('' !== $tagName) {
$tags[$tagName][] = $tagContent;
$tagContent = '';
}
if (preg_match('{^@([-a-zA-Z0-9_:]++)(\s|$)}', $line, $m)) {
$tagName = $m[1];
$tagContent = str_replace("\t", ' ', ltrim(substr($line, 2 + \strlen($tagName))));
} else {
$tagName = '';
}
} elseif ('' !== $tagName) {
$tagContent .= ' '.str_replace("\t", ' ', $line);
}
}
if ('' !== $tagName) {
$tags[$tagName][] = $tagContent;
}
foreach ($tags['method'] ?? [] as $i => $method) {
unset($tags['method'][$i]);
$parts = preg_split('{(\s++|\((?:[^()]*+|(?R))*\)(?: *: *[^ ]++)?|<(?:[^<>]*+|(?R))*>|\{(?:[^{}]*+|(?R))*\})}', $method, -1, \PREG_SPLIT_DELIM_CAPTURE);
$returnType = '';
$static = 'static' === $parts[0];
for ($i = $static ? 2 : 0; null !== $p = $parts[$i] ?? null; $i += 2) {
if (\in_array($p, ['', '|', '&', 'callable'], true) || \in_array(substr($returnType, -1), ['|', '&'], true)) {
$returnType .= trim($parts[$i - 1] ?? '').$p;
continue;
}
$signature = '(' === ($parts[$i + 1][0] ?? '(') ? $parts[$i + 1] ?? '()' : null;
if (null === $signature && '' === $returnType) {
$returnType = $p;
continue;
}
if ($static && 2 === $i) {
$static = false;
$returnType = 'static';
}
if (\in_array($description = trim(implode('', \array_slice($parts, 2 + $i))), ['', '.'], true)) {
$description = null;
} elseif (!preg_match('/[.!]$/', $description)) {
$description .= '.';
}
$tags['method'][$p] = [$static, $returnType, $signature ?? '()', $description];
break;
}
}
foreach ($tags['param'] ?? [] as $i => $param) {
unset($tags['param'][$i]);
if (\strlen($param) !== strcspn($param, '<{(')) {
$param = preg_replace('{\(([^()]*+|(?R))*\)(?: *: *[^ ]++)?|<([^<>]*+|(?R))*>|\{([^{}]*+|(?R))*\}}', '', $param);
}
if (false === $i = strpos($param, '$')) {
continue;
}
$type = 0 === $i ? '' : rtrim(substr($param, 0, $i), ' &');
$param = substr($param, 1 + $i, (strpos($param, ' ', $i) ?: (1 + $i + \strlen($param))) - $i - 1);
$tags['param'][$param] = $type;
}
foreach (['var', 'return'] as $k) {
if (null === $v = $tags[$k][0] ?? null) {
continue;
}
if (\strlen($v) !== strcspn($v, '<{(')) {
$v = preg_replace('{\(([^()]*+|(?R))*\)(?: *: *[^ ]++)?|<([^<>]*+|(?R))*>|\{([^{}]*+|(?R))*\}}', '', $v);
}
$tags[$k] = substr($v, 0, strpos($v, ' ') ?: \strlen($v)) ?: null;
}
return $tags;
}
}

View File

@@ -11,8 +11,7 @@
namespace Symfony\Component\ErrorHandler\ErrorEnhancer;
use Composer\Autoload\ClassLoader as ComposerClassLoader;
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
use Composer\Autoload\ClassLoader;
use Symfony\Component\ErrorHandler\DebugClassLoader;
use Symfony\Component\ErrorHandler\Error\ClassNotFoundError;
use Symfony\Component\ErrorHandler\Error\FatalError;
@@ -91,23 +90,22 @@ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
}
}
if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) {
if ($function[0] instanceof ClassLoader) {
foreach ($function[0]->getPrefixes() as $prefix => $paths) {
foreach ($paths as $path) {
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
$classes[] = $this->findClassInPath($path, $class, $prefix);
}
}
}
if ($function[0] instanceof ComposerClassLoader) {
foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
foreach ($paths as $path) {
$classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
$classes[] = $this->findClassInPath($path, $class, $prefix);
}
}
}
}
return array_unique($classes);
return array_unique(array_merge([], ...$classes));
}
private function findClassInPath(string $path, string $class, string $prefix): array

View File

@@ -91,7 +91,7 @@ class ErrorHandler
private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
private $loggedErrors = 0;
private $traceReflector;
private $configureException;
private $debug;
private $isRecursive = 0;
@@ -187,8 +187,14 @@ class ErrorHandler
$this->bootstrappingLogger = $bootstrappingLogger;
$this->setDefaultLogger($bootstrappingLogger);
}
$this->traceReflector = new \ReflectionProperty(\Exception::class, 'trace');
$this->traceReflector->setAccessible(true);
$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;
$e->line = $line ?? $e->line;
}, null, new class() extends \Exception {
});
$this->debug = $debug;
}
@@ -473,9 +479,9 @@ class ErrorHandler
if ($throw || $this->tracedErrors & $type) {
$backtrace = $errorAsException->getTrace();
$lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
$this->traceReflector->setValue($errorAsException, $lightTrace);
($this->configureException)($errorAsException, $lightTrace, $file, $line);
} else {
$this->traceReflector->setValue($errorAsException, []);
($this->configureException)($errorAsException, []);
$backtrace = [];
}
}
@@ -740,7 +746,7 @@ class ErrorHandler
/**
* Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader.
*/
private function cleanTrace(array $backtrace, int $type, string $file, int $line, bool $throw): array
private function cleanTrace(array $backtrace, int $type, string &$file, int &$line, bool $throw): array
{
$lightTrace = $backtrace;
@@ -750,6 +756,19 @@ class ErrorHandler
break;
}
}
if (\E_USER_DEPRECATED === $type) {
for ($i = 0; isset($lightTrace[$i]); ++$i) {
if (!isset($lightTrace[$i]['file'], $lightTrace[$i]['line'], $lightTrace[$i]['function'])) {
continue;
}
if (!isset($lightTrace[$i]['class']) && 'trigger_deprecation' === $lightTrace[$i]['function']) {
$file = $lightTrace[$i]['file'];
$line = $lightTrace[$i]['line'];
$lightTrace = \array_slice($lightTrace, 1 + $i);
break;
}
}
}
if (class_exists(DebugClassLoader::class, false)) {
for ($i = \count($lightTrace) - 2; 0 < $i; --$i) {
if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) {

View File

@@ -40,6 +40,8 @@ class HtmlErrorRenderer implements ErrorRendererInterface
private $outputBuffer;
private $logger;
private static $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
@@ -48,11 +50,11 @@ class HtmlErrorRenderer implements ErrorRendererInterface
public function __construct($debug = false, string $charset = null, $fileLinkFormat = null, string $projectDir = null, $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__, \is_object($debug) ? \get_class($debug) : \gettype($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__, \is_object($outputBuffer) ? \get_class($outputBuffer) : \gettype($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;
@@ -68,9 +70,13 @@ class HtmlErrorRenderer implements ErrorRendererInterface
*/
public function render(\Throwable $exception): FlattenException
{
$exception = FlattenException::createFromThrowable($exception, null, [
'Content-Type' => 'text/html; charset='.$this->charset,
]);
$headers = ['Content-Type' => 'text/html; charset='.$this->charset];
if (\is_bool($this->debug) ? $this->debug : ($this->debug)($exception)) {
$headers['X-Debug-Exception'] = rawurlencode($exception->getMessage());
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}
$exception = FlattenException::createFromThrowable($exception, null, $headers);
return $exception->setAsString($this->renderException($exception));
}
@@ -132,7 +138,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
$statusCode = $this->escape($exception->getStatusCode());
if (!$debug) {
return $this->include('views/error.html.php', [
return $this->include(self::$template, [
'statusText' => $statusText,
'statusCode' => $statusCode,
]);
@@ -209,7 +215,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
/**
* Returns the link for a given file/line pair.
*
* @return string|false A link or false
* @return string|false
*/
private function getFileLink(string $file, int $line)
{
@@ -256,8 +262,6 @@ class HtmlErrorRenderer implements ErrorRendererInterface
* @param string $file A file path
* @param int $line The selected line number
* @param int $srcContext The number of displayed lines around or -1 for the whole file
*
* @return string An HTML string
*/
private function fileExcerpt(string $file, int $line, int $srcContext = 3): string
{
@@ -279,7 +283,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
}
for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) {
$lines[] = '<li'.($i == $line ? ' class="selected"' : '').'><a class="anchor" name="line'.$i.'"></a><code>'.$this->fixCodeMarkup($content[$i - 1]).'</code></li>';
$lines[] = '<li'.($i == $line ? ' class="selected"' : '').'><code>'.$this->fixCodeMarkup($content[$i - 1]).'</code></li>';
}
return '<ol start="'.max($line - $srcContext, 1).'">'.implode("\n", $lines).'</ol>';
@@ -298,9 +302,9 @@ class HtmlErrorRenderer implements ErrorRendererInterface
}
// missing </span> tag at the end of line
$opening = strpos($line, '<span');
$closing = strpos($line, '</span>');
if (false !== $opening && (false === $closing || $closing > $opening)) {
$opening = strrpos($line, '<span');
$closing = strrpos($line, '</span>');
if (false !== $opening && (false === $closing || $closing < $opening)) {
$line .= '</span>';
}
@@ -345,8 +349,19 @@ class HtmlErrorRenderer implements ErrorRendererInterface
{
extract($context, \EXTR_SKIP);
ob_start();
include __DIR__.'/../Resources/'.$name;
include is_file(\dirname(__DIR__).'/Resources/'.$name) ? \dirname(__DIR__).'/Resources/'.$name : $name;
return trim(ob_get_clean());
}
/**
* Allows overriding the default non-debug template.
*
* @param string $template path to the custom template file to render
*/
public static function setTemplate(string $template): void
{
self::$template = $template;
}
}

View File

@@ -37,11 +37,11 @@ class SerializerErrorRenderer implements ErrorRendererInterface
public function __construct(SerializerInterface $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null, $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__, \is_object($format) ? \get_class($format) : \gettype($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__, \is_object($debug) ? \get_class($debug) : \gettype($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;
@@ -55,7 +55,14 @@ class SerializerErrorRenderer implements ErrorRendererInterface
*/
public function render(\Throwable $exception): FlattenException
{
$flattenException = FlattenException::createFromThrowable($exception);
$headers = [];
$debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception);
if ($debug) {
$headers['X-Debug-Exception'] = rawurlencode($exception->getMessage());
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}
$flattenException = FlattenException::createFromThrowable($exception, null, $headers);
try {
$format = \is_string($this->format) ? $this->format : ($this->format)($flattenException);
@@ -66,7 +73,7 @@ class SerializerErrorRenderer implements ErrorRendererInterface
return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, [
'exception' => $exception,
'debug' => \is_bool($this->debug) ? $this->debug : ($this->debug)($exception),
'debug' => $debug,
]))
->setHeaders($flattenException->getHeaders() + $headers);
} catch (NotEncodableValueException $e) {

View File

@@ -11,8 +11,6 @@
namespace Symfony\Component\ErrorHandler\Exception;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException;
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
@@ -24,7 +22,7 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FlattenException extends LegacyFlattenException
class FlattenException
{
/** @var string */
private $message;
@@ -65,7 +63,7 @@ class FlattenException extends LegacyFlattenException
/**
* @return static
*/
public static function create(\Exception $exception, $statusCode = null, array $headers = []): self
public static function create(\Exception $exception, int $statusCode = null, array $headers = []): self
{
return static::createFromThrowable($exception, $statusCode, $headers);
}
@@ -100,7 +98,7 @@ class FlattenException extends LegacyFlattenException
$e->setStatusCode($statusCode);
$e->setHeaders($headers);
$e->setTraceFromThrowable($exception);
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
$e->setClass(\get_class($exception));
$e->setFile($exception->getFile());
$e->setLine($exception->getLine());
@@ -133,11 +131,9 @@ class FlattenException extends LegacyFlattenException
}
/**
* @param int $code
*
* @return $this
*/
public function setStatusCode($code): self
public function setStatusCode(int $code): self
{
$this->statusCode = $code;
@@ -165,11 +161,9 @@ class FlattenException extends LegacyFlattenException
}
/**
* @param string $class
*
* @return $this
*/
public function setClass($class): self
public function setClass(string $class): self
{
$this->class = false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class;
@@ -182,11 +176,9 @@ class FlattenException extends LegacyFlattenException
}
/**
* @param string $file
*
* @return $this
*/
public function setFile($file): self
public function setFile(string $file): self
{
$this->file = $file;
@@ -199,11 +191,9 @@ class FlattenException extends LegacyFlattenException
}
/**
* @param int $line
*
* @return $this
*/
public function setLine($line): self
public function setLine(int $line): self
{
$this->line = $line;
@@ -215,6 +205,9 @@ class FlattenException extends LegacyFlattenException
return $this->statusText;
}
/**
* @return $this
*/
public function setStatusText(string $statusText): self
{
$this->statusText = $statusText;
@@ -228,11 +221,9 @@ class FlattenException extends LegacyFlattenException
}
/**
* @param string $message
*
* @return $this
*/
public function setMessage($message): self
public function setMessage(string $message): self
{
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) {
@@ -265,10 +256,7 @@ class FlattenException extends LegacyFlattenException
return $this;
}
/**
* @return self|null
*/
public function getPrevious()
public function getPrevious(): ?self
{
return $this->previous;
}
@@ -276,7 +264,7 @@ class FlattenException extends LegacyFlattenException
/**
* @return $this
*/
final public function setPrevious(?LegacyFlattenException $previous): self
public function setPrevious(?self $previous): self
{
$this->previous = $previous;
@@ -302,16 +290,6 @@ class FlattenException extends LegacyFlattenException
return $this->trace;
}
/**
* @deprecated since 4.1, use {@see setTraceFromThrowable()} instead.
*/
public function setTraceFromException(\Exception $exception)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use "setTraceFromThrowable()" instead.', __METHOD__), \E_USER_DEPRECATED);
$this->setTraceFromThrowable($exception);
}
/**
* @return $this
*/
@@ -323,13 +301,9 @@ class FlattenException extends LegacyFlattenException
}
/**
* @param array $trace
* @param string|null $file
* @param int|null $line
*
* @return $this
*/
public function setTrace($trace, $file, $line): self
public function setTrace(array $trace, ?string $file, ?int $line): self
{
$this->trace = [];
$this->trace[] = [
@@ -374,7 +348,6 @@ class FlattenException extends LegacyFlattenException
return ['array', '*SKIPPED over 10000 entries*'];
}
if ($value instanceof \__PHP_Incomplete_Class) {
// is_object() returns false on PHP<=7.1
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
} elseif (\is_object($value)) {
$result[$key] = ['object', \get_class($value)];

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,9 @@ Debug::enable();
//ErrorHandler::register();
//DebugClassLoader::enable();
// If you want a custom generic template when debug is not enabled
// HtmlErrorRenderer::setTemplate('/path/to/custom/error.html.php');
$data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) {
// if any code executed inside this anonymous function fails, a PHP exception
// will be thrown, even if the code uses the '@' PHP silence operator

View File

@@ -42,13 +42,54 @@
--base-6: #222;
}
.theme-dark {
--page-background: #36393e;
--color-text: #e0e0e0;
--color-muted: #777;
--color-error: #d43934;
--tab-background: #555;
--tab-color: #ccc;
--tab-active-background: #888;
--tab-active-color: #fafafa;
--tab-disabled-background: var(--page-background);
--tab-disabled-color: #777;
--metric-value-background: #555;
--metric-value-color: inherit;
--metric-unit-color: #999;
--metric-label-background: #777;
--metric-label-color: #e0e0e0;
--trace-selected-background: #71663acc;
--table-border: #444;
--table-background: #333;
--table-header: #555;
--info-background: rgba(79, 148, 195, 0.5);
--tree-active-background: var(--metric-label-background);
--exception-title-color: var(--base-2);
--shadow: 0px 0px 1px rgba(32, 32, 32, .2);
--border: 1px solid #666;
--background-error: #b0413e;
--highlight-comment: #dedede;
--highlight-default: var(--base-6);
--highlight-keyword: #ff413c;
--highlight-string: #70a6fd;
--base-0: #2e3136;
--base-1: #444;
--base-2: #666;
--base-3: #666;
--base-4: #666;
--base-5: #e0e0e0;
--base-6: #f5f5f5;
--card-label-background: var(--tab-active-background);
--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 {
/* always display the vertical scrollbar to avoid jumps when toggling contents */
overflow-y: scroll;
}
body { background-color: #F9F9F9; color: var(--base-6); font: 14px/1.4 Helvetica, Arial, sans-serif; padding-bottom: 45px; }
body { background-color: var(--page-background); color: var(--base-6); font: 14px/1.4 Helvetica, Arial, sans-serif; padding-bottom: 45px; }
a { cursor: pointer; text-decoration: none; }
a:hover { text-decoration: underline; }
@@ -56,8 +97,8 @@ abbr[title] { border-bottom: none; cursor: help; text-decoration: none; }
code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; }
table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; }
table { background: #FFF; border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
table, tr, th, td { background: var(--base-0); border-collapse: collapse; vertical-align: top; }
table { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
table th, table td { border: solid var(--base-2); border-width: 1px 0; padding: 8px 10px; }
table th { background-color: var(--base-2); font-weight: bold; text-align: left; }
@@ -79,11 +120,11 @@ table th { background-color: var(--base-2); font-weight: bold; text-align: left;
.status-warning { background: rgba(240, 181, 24, 0.3); }
.status-error { background: rgba(176, 65, 62, 0.2); }
.status-success td, .status-warning td, .status-error td { background: transparent; }
tr.status-error td, tr.status-warning td { border-bottom: 1px solid #FAFAFA; border-top: 1px solid #FAFAFA; }
tr.status-error td, tr.status-warning td { border-bottom: 1px solid var(--base-2); border-top: 1px solid var(--base-2); }
.status-warning .colored { color: #A46A1F; }
.status-error .colored { color: var(--color-error); }
.sf-toggle { cursor: pointer; }
.sf-toggle { cursor: pointer; position: relative; }
.sf-toggle-content { -moz-transition: display .25s ease; -webkit-transition: display .25s ease; transition: display .25s ease; }
.sf-toggle-content.sf-toggle-hidden { display: none; }
.sf-toggle-content.sf-toggle-visible { display: block; }
@@ -139,7 +180,7 @@ thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-vis
.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
.container::after { content: ""; display: table; clear: both; }
header { background-color: var(--base-6); color: rgba(255, 255, 255, 0.75); font-size: 13px; height: 33px; line-height: 33px; padding: 0; }
header { background-color: #222; color: rgba(255, 255, 255, 0.75); font-size: 13px; height: 33px; line-height: 33px; padding: 0; }
header .container { display: flex; justify-content: space-between; }
.logo { flex: 1; font-size: 13px; font-weight: normal; margin: 0; padding: 0; }
.logo svg { height: 18px; width: 18px; opacity: .8; vertical-align: -5px; }
@@ -174,7 +215,7 @@ header .container { display: flex; justify-content: space-between; }
.trace-head .trace-class { color: var(--base-6); font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }
.trace-head .trace-namespace { color: #999; display: block; font-size: 13px; }
.trace-head .icon { position: absolute; right: 0; top: 0; }
.trace-head .icon svg { height: 24px; width: 24px; }
.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; }
@@ -185,7 +226,9 @@ header .container { display: flex; justify-content: space-between; }
.trace-line:hover { background: var(--base-1); }
.trace-line a { color: var(--base-6); }
.trace-line .icon { opacity: .4; position: absolute; left: 10px; }
.trace-line .icon svg { height: 16px; width: 16px; }
.trace-line .icon svg { fill: var(--base-5); height: 16px; width: 16px; }
.trace-line .icon.icon-copy { left: auto; top: auto; padding-left: 5px; display: none }
.trace-line:hover .icon.icon-copy:not(.hidden) { display: inline-block }
.trace-line-header { padding-left: 36px; padding-right: 10px; }
.trace-file-path, .trace-file-path a { color: var(--base-6); font-size: 13px; }

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>

After

Width:  |  Height:  |  Size: 265 B

View File

@@ -30,6 +30,15 @@ if (typeof Sfjs === 'undefined') {
};
}
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,
@@ -166,6 +175,14 @@ if (typeof Sfjs === 'undefined') {
});
}
/* 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');
}
},

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env php
<?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.
*/
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.
//
// Usage: find -name *.stub.php | sort | /path/to/extract-tentative-return-types.php > /path/to/TentativeTypes.php
echo <<<EOPHP
<?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\Internal;
/**
* This class has been generated by extract-tentative-return-types.php.
*
* @internal
*/
class TentativeTypes
{
public const RETURN_TYPES = [
EOPHP;
while (false !== $file = fgets(\STDIN)) {
$code = file_get_contents(substr($file, 0, -1));
if (!str_contains($code, '@tentative-return-type')) {
continue;
}
$code = preg_split('{^\s*(?:(?:abstract )?class|interface|trait) ([^\s]++)}m', $code, -1, \PREG_SPLIT_DELIM_CAPTURE);
if (1 === count($code)) {
continue;
}
for ($i = 1; null !== $class = $code[$i] ?? null; $i += 2) {
$methods = $code[1 + $i];
if (!str_contains($methods, '@tentative-return-type')) {
continue;
}
echo " '$class' => [\n";
preg_replace_callback('{@tentative-return-type.*?[\s]function ([^(]++)[^)]++\)\s*+:\s*+([^\n;\{]++)}s', function ($m) {
$m[2] = str_replace(' ', '', $m[2]);
echo " '$m[1]' => '$m[2]',\n";
return '';
}, $methods);
echo " ],\n";
}
}
echo <<<EOPHP
];
}
EOPHP;

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env php
<?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.
*/
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',
' incompatible method declarations.',
'',
' This assists you to make your package compatible with Symfony 6, but it can be used',
' for any class/package.',
'',
' Available configuration via environment variables:',
' SYMFONY_PATCH_TYPE_DECLARATIONS',
' An 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',
' - "1" to add return types but only to tests/final/internal/private methods',
' - "php": the target version of PHP - e.g. "7.1" doesn\'t generate "object" types',
' - "deprecations": "1" to trigger a deprecation notice when a child class misses a',
' return type while the parent declares an "@return" annotation',
'',
' SYMFONY_PATCH_TYPE_EXCLUDE',
' A regex matched against the full path to the class - any match will be excluded',
'',
' Example: "SYMFONY_PATCH_TYPE_DECLARATIONS=php=7.4 ./patch-type-declarations"',
]);
exit;
}
if (false === getenv('SYMFONY_PATCH_TYPE_DECLARATIONS')) {
putenv('SYMFONY_PATCH_TYPE_DECLARATIONS=force=2');
echo 'No SYMFONY_PATCH_TYPE_DECLARATIONS env var set, patching type declarations in all methods (run the command with "-h" for more information).'.PHP_EOL;
}
if (is_file($autoload = __DIR__.'/../../../../autoload.php')) {
// noop
} elseif (is_file($autoload = __DIR__.'/../../../../../../../autoload.php')) {
// noop
} else {
echo PHP_EOL.' /!\ Cannot find the Composer autoloader, did you forget to run "composer install"?'.PHP_EOL;
exit(1);
}
if (is_file($phpunitAutoload = dirname($autoload).'/bin/.phpunit/phpunit/vendor/autoload.php')) {
require $phpunitAutoload;
}
$loader = require $autoload;
Symfony\Component\ErrorHandler\DebugClassLoader::enable();
$deprecations = [];
set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations) {
if (\E_USER_DEPRECATED !== $type) {
return;
}
[,,,,, $class,] = explode('"', $msg);
$deprecations[$class][] = $msg;
});
$exclude = getenv('SYMFONY_PATCH_TYPE_EXCLUDE') ?: null;
foreach ($loader->getClassMap() as $class => $file) {
if (false !== strpos($file = realpath($file), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) {
continue;
}
if ($exclude && preg_match($exclude, $file)) {
continue;
}
class_exists($class);
}
Symfony\Component\ErrorHandler\DebugClassLoader::checkClasses();
foreach ($deprecations as $class => $classDeprecations) {
echo $class.' ('.\count($classDeprecations).')'.PHP_EOL;
echo implode(PHP_EOL, $classDeprecations).PHP_EOL.PHP_EOL;
}
if ($deprecations && false !== strpos(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;
}

View File

@@ -11,6 +11,12 @@
<style><?= $this->include('assets/css/exception_full.css'); ?></style>
</head>
<body>
<script>
document.body.classList.add(
localStorage.getItem('symfony/profiler/theme') || (matchMedia('(prefers-color-scheme: dark)').matches ? 'theme-dark' : 'theme-light')
);
</script>
<?php if (class_exists(\Symfony\Component\HttpKernel\Kernel::class)) { ?>
<header>
<div class="container">

View File

@@ -23,7 +23,7 @@
$status = \E_DEPRECATED === $severity || \E_USER_DEPRECATED === $severity ? 'warning' : 'normal';
} ?>
<tr class="status-<?= $status; ?>" data-filter-level="<?= strtolower($this->escape($log['priorityName'])); ?>"<?php if ($channelIsDefined) { ?> data-filter-channel="<?= $this->escape($log['channel']); ?>"<?php } ?>>
<td class="text-small" nowrap>
<td class="text-small nowrap">
<span class="colored text-bold"><?= $this->escape($log['priorityName']); ?></span>
<span class="text-muted newline"><?= date('H:i:s', $log['timestamp']); ?></span>
</td>

View File

@@ -25,6 +25,9 @@
<span class="trace-method"><?= $trace['function']; ?></span>
<?php } ?>
(line <?= $lineNumber; ?>)
<span class="icon icon-copy hidden" data-clipboard-text="<?php echo implode(\DIRECTORY_SEPARATOR, $filePathParts).':'.$lineNumber; ?>">
<?php echo $this->include('assets/images/icon-copy.svg'); ?>
</span>
</span>
<?php } ?>
</div>

View File

@@ -1,21 +1,30 @@
<div class="trace trace-as-html" id="trace-box-<?= $index; ?>">
<div class="trace-details">
<div class="trace-head">
<span class="sf-toggle" data-toggle-selector="#trace-html-<?= $index; ?>" data-toggle-initial="<?= $expand ? 'display' : ''; ?>">
<h3 class="trace-class">
<span class="icon icon-close"><?= $this->include('assets/images/icon-minus-square-o.svg'); ?></span>
<span class="icon icon-open"><?= $this->include('assets/images/icon-plus-square-o.svg'); ?></span>
<span class="trace-namespace">
<?= implode('\\', array_slice(explode('\\', $exception['class']), 0, -1)); ?><?= count(explode('\\', $exception['class'])) > 1 ? '\\' : ''; ?>
</span>
<?= ($parts = explode('\\', $exception['class'])) ? end($parts) : ''; ?>
</h3>
<div class="sf-toggle" data-toggle-selector="#trace-html-<?= $index; ?>" data-toggle-initial="<?= $expand ? 'display' : ''; ?>">
<span class="icon icon-close"><?= $this->include('assets/images/icon-minus-square-o.svg'); ?></span>
<span class="icon icon-open"><?= $this->include('assets/images/icon-plus-square-o.svg'); ?></span>
<?php
$separator = strrpos($exception['class'], '\\');
$separator = false === $separator ? 0 : $separator + 1;
$namespace = substr($exception['class'], 0, $separator);
$class = substr($exception['class'], $separator);
?>
<?php if ('' === $class) { ?>
<br>
<?php } else { ?>
<h3 class="trace-class">
<?php if ('' !== $namespace) { ?>
<span class="trace-namespace"><?= $namespace; ?></span>
<?php } ?>
<?= $class; ?>
</h3>
<?php } ?>
<?php if ($exception['message'] && $index > 1) { ?>
<p class="break-long-words trace-message"><?= $this->escape($exception['message']); ?></p>
<?php } ?>
</span>
</div>
</div>
<div id="trace-html-<?= $index; ?>" class="sf-toggle-content">

View File

@@ -2,14 +2,14 @@
<thead class="trace-head">
<tr>
<th class="sf-toggle" data-toggle-selector="#trace-text-<?= $index; ?>" data-toggle-initial="<?= 1 === $index ? 'display' : ''; ?>">
<h3 class="trace-class">
<div class="trace-class">
<?php if ($numExceptions > 1) { ?>
<span class="text-muted">[<?= $numExceptions - $index + 1; ?>/<?= $numExceptions; ?>]</span>
<?php } ?>
<?= ($parts = explode('\\', $exception['class'])) ? end($parts) : ''; ?>
<span class="icon icon-close"><?= $this->include('assets/images/icon-minus-square-o.svg'); ?></span>
<span class="icon icon-open"><?= $this->include('assets/images/icon-plus-square-o.svg'); ?></span>
</h3>
</div>
</th>
</tr>
</thead>

View File

@@ -16,14 +16,14 @@
}
],
"require": {
"php": ">=7.1.3",
"php": ">=7.2.5",
"psr/log": "^1|^2|^3",
"symfony/debug": "^4.4.5",
"symfony/var-dumper": "^4.4|^5.0"
"symfony/var-dumper": "^4.4|^5.0|^6.0"
},
"require-dev": {
"symfony/http-kernel": "^4.4|^5.0",
"symfony/serializer": "^4.4|^5.0"
"symfony/http-kernel": "^4.4|^5.0|^6.0",
"symfony/serializer": "^4.4|^5.0|^6.0",
"symfony/deprecation-contracts": "^2.1|^3"
},
"autoload": {
"psr-4": { "Symfony\\Component\\ErrorHandler\\": "" },
@@ -31,5 +31,8 @@
"/Tests/"
]
},
"bin": [
"Resources/bin/patch-type-declarations"
],
"minimum-stability": "dev"
}