N°8834 - Add compatibility with PHP 8.4 (#819)

* N°8834 - Add compatibility with PHP 8.4

* Rollback of scssphp/scssphp version upgrade due to compilation error
This commit is contained in:
Lenaick
2026-02-26 10:36:32 +01:00
committed by GitHub
parent d4821b7edc
commit fc967c06ce
961 changed files with 12298 additions and 7130 deletions

View File

@@ -35,15 +35,12 @@ class BufferingLogger extends AbstractLogger
return $logs;
}
public function __sleep(): array
public function __serialize(): array
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
/**
* @return void
*/
public function __wakeup()
public function __unserialize(array $data): void
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
@@ -65,7 +62,7 @@ class BufferingLogger extends AbstractLogger
}
}
error_log(sprintf('%s [%s] %s', date(\DateTimeInterface::RFC3339), $level, $message));
error_log(\sprintf('%s [%s] %s', date(\DateTimeInterface::RFC3339), $level, $message));
}
}
}

View File

@@ -20,7 +20,7 @@ class Debug
{
public static function enable(): ErrorHandler
{
error_reporting(-1);
error_reporting(\E_ALL & ~\E_DEPRECATED & ~\E_USER_DEPRECATED);
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
ini_set('display_errors', 0);

View File

@@ -18,8 +18,10 @@ use Mockery\MockInterface;
use Phake\IMock;
use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\MockObject\Stub;
use Prophecy\Prophecy\ProphecySubjectInterface;
use ProxyManager\Proxy\ProxyInterface;
use Symfony\Component\DependencyInjection\Argument\LazyClosure;
use Symfony\Component\ErrorHandler\Internal\TentativeTypes;
use Symfony\Component\VarExporter\LazyObjectInterface;
@@ -180,7 +182,7 @@ class DebugClassLoader
public static function enable(): void
{
// Ensures we don't hit https://bugs.php.net/42098
class_exists(\Symfony\Component\ErrorHandler\ErrorHandler::class);
class_exists(ErrorHandler::class);
class_exists(\Psr\Log\LogLevel::class);
if (!\is_array($functions = spl_autoload_functions())) {
@@ -252,6 +254,7 @@ class DebugClassLoader
for (; $i < \count($symbols); ++$i) {
if (!is_subclass_of($symbols[$i], MockObject::class)
&& !is_subclass_of($symbols[$i], Stub::class)
&& !is_subclass_of($symbols[$i], ProphecySubjectInterface::class)
&& !is_subclass_of($symbols[$i], Proxy::class)
&& !is_subclass_of($symbols[$i], ProxyInterface::class)
@@ -259,6 +262,7 @@ class DebugClassLoader
&& !is_subclass_of($symbols[$i], LegacyProxy::class)
&& !is_subclass_of($symbols[$i], MockInterface::class)
&& !is_subclass_of($symbols[$i], IMock::class)
&& !(is_subclass_of($symbols[$i], LazyClosure::class) && str_contains($symbols[$i], "@anonymous\0"))
) {
$loader->checkClass($symbols[$i]);
}
@@ -307,7 +311,7 @@ class DebugClassLoader
$this->checkClass($class, $file);
}
private function checkClass(string $class, string $file = null): void
private function checkClass(string $class, ?string $file = null): void
{
$exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
@@ -328,7 +332,7 @@ class DebugClassLoader
$name = $refl->getName();
if ($name !== $class && 0 === strcasecmp($name, $class)) {
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
throw new \RuntimeException(\sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
}
$deprecations = $this->checkAnnotations($refl, $name);
@@ -344,14 +348,14 @@ class DebugClassLoader
if (!$exists) {
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));
throw new \RuntimeException(\sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
}
throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
throw new \RuntimeException(\sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
}
if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) {
throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2]));
throw new \RuntimeException(\sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2]));
}
}
@@ -412,7 +416,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);
}
}
@@ -425,10 +429,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()) {
@@ -454,7 +458,7 @@ class DebugClassLoader
}
$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"%s', $className, ($static ? 'static ' : '').$interface, $name, $returnType ? ': '.$returnType : '', null === $description ? '.' : ': '.$description);
$deprecations[] = \sprintf('Class "%s" should implement method "%s::%s%s"%s', $className, ($static ? 'static ' : '').$interface, $name, $returnType ? ': '.$returnType : '', null === $description ? '.' : ': '.$description);
}
}
}
@@ -518,13 +522,13 @@ 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);
}
}
@@ -543,7 +547,7 @@ class DebugClassLoader
foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) {
if (!isset($definedParameters[$parameterName]) && !isset($doc['param'][$parameterName])) {
$deprecations[] = sprintf($deprecation, $className);
$deprecations[] = \sprintf($deprecation, $className);
}
}
}
@@ -581,7 +585,7 @@ class DebugClassLoader
if ('docblock' === $this->patchTypes['force']) {
$this->patchMethod($method, $returnType, $declaringFile, $normalizedType);
} elseif ('' !== $declaringClass && $this->patchTypes['deprecations']) {
$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);
$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);
}
}
}
@@ -630,7 +634,7 @@ class DebugClassLoader
}
foreach ($doc['param'] as $parameterName => $parameterType) {
if (!isset($definedParameters[$parameterName])) {
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);
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);
}
}
}
@@ -650,7 +654,7 @@ class DebugClassLoader
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);
$deprecations[] = \sprintf('The "%s::'.$msg.' is considered final. You should not override it in "%s".', self::${$type}[$use][$r->name], $r->name, $class);
}
}
@@ -794,7 +798,7 @@ class DebugClassLoader
return $ownInterfaces;
}
private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, \ReflectionType $returnType = null): void
private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, ?\ReflectionType $returnType = null): void
{
if ('__construct' === $method) {
return;
@@ -891,8 +895,8 @@ class DebugClassLoader
}
}
$phpType = sprintf($nullable ? (1 < \count($phpTypes) ? '%s|null' : '?%s') : '%s', implode($glue, $phpTypes));
$docType = sprintf($nullable ? '%s|null' : '%s', implode($glue, $docTypes));
$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];
}
@@ -1024,7 +1028,7 @@ class DebugClassLoader
++$fileOffset;
}
$returnType[$i] = null !== $format ? sprintf($format, $alias) : $alias;
$returnType[$i] = null !== $format ? \sprintf($format, $alias) : $alias;
}
if ('docblock' === $this->patchTypes['force'] || ('object' === $normalizedType && '7.1' === $this->patchTypes['php'])) {
@@ -1212,7 +1216,7 @@ EOTXT;
$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)) {
if (\in_array($p, ['', 'callable'], true) || \in_array(substr($returnType, -1), ['|', '&'], true) || \in_array($p[0], ['|', '&'], true)) {
$returnType .= trim($parts[$i - 1] ?? '').$p;
continue;
}

View File

@@ -18,7 +18,7 @@ class FatalError extends \Error
/**
* @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)
public function __construct(string $message, int $code, array $error, ?int $traceOffset = null, bool $traceArgs = true, ?array $trace = null)
{
parent::__construct($message, $code);
@@ -31,7 +31,7 @@ class FatalError extends \Error
}
}
} elseif (null !== $traceOffset) {
if (\function_exists('xdebug_get_function_stack') && $trace = @xdebug_get_function_stack()) {
if (\function_exists('xdebug_get_function_stack') && \in_array(\ini_get('xdebug.mode'), ['develop', false], true) && $trace = @xdebug_get_function_stack()) {
if (0 < $traceOffset) {
array_splice($trace, -$traceOffset);
}

View File

@@ -34,11 +34,11 @@ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
$className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
$namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
$message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
$message = \sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
$tail = ' for another namespace?';
} else {
$className = $fullyQualifiedClassName;
$message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
$message = \sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
$tail = '?';
}
@@ -107,7 +107,8 @@ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
private function findClassInPath(string $path, string $class, string $prefix): array
{
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path);
if (!$path || !is_dir($path)) {
return [];
}

View File

@@ -47,10 +47,10 @@ class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface
if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) {
$functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1);
$namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex);
$message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
$message = \sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix);
} else {
$functionName = $fullyQualifiedFunctionName;
$message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
$message = \sprintf('Attempted to call function "%s" from the global namespace.', $functionName);
}
$candidates = [];

View File

@@ -34,7 +34,7 @@ class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface
$className = $matches[1];
$methodName = $matches[2];
$message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);
$message = \sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className);
if ('' === $methodName || !class_exists($className) || null === $methods = get_class_methods($className)) {
// failed to get the class or its methods on which an unknown method was called (for example on an anonymous class)

View File

@@ -55,7 +55,6 @@ class ErrorHandler
\E_USER_DEPRECATED => 'User Deprecated',
\E_NOTICE => 'Notice',
\E_USER_NOTICE => 'User Notice',
\E_STRICT => 'Runtime Notice',
\E_WARNING => 'Warning',
\E_USER_WARNING => 'User Warning',
\E_COMPILE_WARNING => 'Compile Warning',
@@ -73,7 +72,6 @@ class ErrorHandler
\E_USER_DEPRECATED => [null, LogLevel::INFO],
\E_NOTICE => [null, LogLevel::WARNING],
\E_USER_NOTICE => [null, LogLevel::WARNING],
\E_STRICT => [null, LogLevel::WARNING],
\E_WARNING => [null, LogLevel::WARNING],
\E_USER_WARNING => [null, LogLevel::WARNING],
\E_COMPILE_WARNING => [null, LogLevel::WARNING],
@@ -108,7 +106,7 @@ class ErrorHandler
/**
* Registers the error handler.
*/
public static function register(self $handler = null, bool $replace = true): self
public static function register(?self $handler = null, bool $replace = true): self
{
if (null === self::$reservedMemory) {
self::$reservedMemory = str_repeat('x', 32768);
@@ -179,8 +177,13 @@ class ErrorHandler
}
}
public function __construct(BufferingLogger $bootstrappingLogger = null, bool $debug = false)
public function __construct(?BufferingLogger $bootstrappingLogger = null, bool $debug = false)
{
if (\PHP_VERSION_ID < 80400) {
$this->levels[\E_STRICT] = 'Runtime Notice';
$this->loggers[\E_STRICT] = [null, LogLevel::WARNING];
}
if ($bootstrappingLogger) {
$this->bootstrappingLogger = $bootstrappingLogger;
$this->setDefaultLogger($bootstrappingLogger);
@@ -190,7 +193,7 @@ class ErrorHandler
$traceReflector->setValue($e, $trace);
$e->file = $file ?? $e->file;
$e->line = $line ?? $e->line;
}, null, new class() extends \Exception {
}, null, new class extends \Exception {
});
$this->debug = $debug;
}
@@ -432,7 +435,7 @@ class ErrorHandler
return true;
}
} else {
if (str_contains($message, '@anonymous')) {
if (\PHP_VERSION_ID < 80303 && str_contains($message, '@anonymous')) {
$backtrace = debug_backtrace(false, 5);
for ($i = 1; isset($backtrace[$i]); ++$i) {
@@ -440,8 +443,7 @@ class ErrorHandler
&& ('trigger_error' === $backtrace[$i]['function'] || 'user_error' === $backtrace[$i]['function'])
) {
if ($backtrace[$i]['args'][0] !== $message) {
$message = $this->parseAnonymousClass($backtrace[$i]['args'][0]);
$logMessage = $this->levels[$type].': '.$message;
$message = $backtrace[$i]['args'][0];
}
break;
@@ -449,6 +451,11 @@ class ErrorHandler
}
}
if (str_contains($message, "@anonymous\0")) {
$message = $this->parseAnonymousClass($message);
$logMessage = $this->levels[$type].': '.$message;
}
$errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
if ($throw || $this->tracedErrors & $type) {
@@ -559,7 +566,7 @@ class ErrorHandler
*
* @internal
*/
public static function handleFatalError(array $error = null): void
public static function handleFatalError(?array $error = null): void
{
if (null === self::$reservedMemory) {
return;
@@ -591,6 +598,10 @@ class ErrorHandler
set_exception_handler($h);
}
if (!$handler) {
if (null === $error && $exitCode = self::$exitCode) {
register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
}
return;
}
if ($handler !== $h) {
@@ -626,8 +637,7 @@ class ErrorHandler
// Ignore this re-throw
}
if ($exit && self::$exitCode) {
$exitCode = self::$exitCode;
if ($exit && $exitCode = self::$exitCode) {
register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
}
}
@@ -732,6 +742,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 fn ($m) => 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);
}
}

View File

@@ -26,7 +26,7 @@ class CliErrorRenderer implements ErrorRendererInterface
public function render(\Throwable $exception): FlattenException
{
$cloner = new VarCloner();
$dumper = new class() extends CliDumper {
$dumper = new class extends CliDumper {
protected function supportsColors(): bool
{
$outputStream = $this->outputStream;

View File

@@ -30,9 +30,9 @@ class FileLinkFormatter
private \Closure|string|null $urlFormat;
/**
* @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand
* @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)
public function __construct(string|array|null $fileLinkFormat = null, ?RequestStack $requestStack = null, ?string $baseDir = null, string|\Closure|null $urlFormat = null)
{
$fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? '';
@@ -67,14 +67,11 @@ class FileLinkFormatter
return false;
}
/**
* @internal
*/
public function __sleep(): array
public function __serialize(): array
{
$this->fileLinkFormat = $this->getFileLinkFormat();
return ['fileLinkFormat'];
return ['fileLinkFormat' => $this->fileLinkFormat];
}
/**

View File

@@ -47,7 +47,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
* @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(bool|callable $debug = false, string $charset = null, string|FileLinkFormatter $fileLinkFormat = null, string $projectDir = null, string|callable $outputBuffer = '', LoggerInterface $logger = null)
public function __construct(bool|callable $debug = false, ?string $charset = null, string|FileLinkFormatter|null $fileLinkFormat = null, ?string $projectDir = null, string|callable $outputBuffer = '', ?LoggerInterface $logger = null)
{
$this->debug = \is_bool($debug) ? $debug : $debug(...);
$this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8');
@@ -61,7 +61,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
{
$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'] = rawurlencode(substr($exception->getMessage(), 0, 2000));
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}
@@ -140,7 +140,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
'exceptionMessage' => $exceptionMessage,
'statusText' => $statusText,
'statusCode' => $statusCode,
'logger' => DebugLoggerConfigurator::getDebugLogger($this->logger),
'logger' => null !== $this->logger && class_exists(DebugLoggerConfigurator::class) ? DebugLoggerConfigurator::getDebugLogger($this->logger) : null,
'currentContent' => \is_string($this->outputBuffer) ? $this->outputBuffer : ($this->outputBuffer)(),
]);
}
@@ -158,20 +158,22 @@ class HtmlErrorRenderer implements ErrorRendererInterface
$result = [];
foreach ($args as $key => $item) {
if ('object' === $item[0]) {
$formattedValue = sprintf('<em>object</em>(%s)', $this->abbrClass($item[1]));
$formattedValue = \sprintf('<em>object</em>(%s)', $this->abbrClass($item[1]));
} elseif ('array' === $item[0]) {
$formattedValue = sprintf('<em>array</em>(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
$formattedValue = \sprintf('<em>array</em>(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
} elseif ('null' === $item[0]) {
$formattedValue = '<em>null</em>';
} elseif ('boolean' === $item[0]) {
$formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
} elseif ('resource' === $item[0]) {
$formattedValue = '<em>resource</em>';
} elseif (preg_match('/[^\x07-\x0D\x1B\x20-\xFF]/', $item[1])) {
$formattedValue = '<em>binary string</em>';
} else {
$formattedValue = str_replace("\n", '', $this->escape(var_export($item[1], true)));
}
$result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escape($key), $formattedValue);
$result[] = \is_int($key) ? $formattedValue : \sprintf("'%s' => %s", $this->escape($key), $formattedValue);
}
return implode(', ', $result);
@@ -192,7 +194,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
$parts = explode('\\', $class);
$short = array_pop($parts);
return sprintf('<abbr title="%s">%s</abbr>', $class, $short);
return \sprintf('<abbr title="%s">%s</abbr>', $class, $short);
}
private function getFileRelative(string $file): ?string
@@ -213,7 +215,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
* @param int $line The line number
* @param string $text Use this text for the link rather than the file path
*/
private function formatFile(string $file, int $line, string $text = null): string
private function formatFile(string $file, int $line, ?string $text = null): string
{
$file = trim($file);
@@ -221,7 +223,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
$text = $file;
if (null !== $rel = $this->getFileRelative($text)) {
$rel = explode('/', $rel, 2);
$text = sprintf('<abbr title="%s%2$s">%s</abbr>%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? ''));
$text = \sprintf('<abbr title="%s%2$s">%s</abbr>%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? ''));
}
}
@@ -229,9 +231,13 @@ class HtmlErrorRenderer implements ErrorRendererInterface
$text .= ' at line '.$line;
}
if (!file_exists($file)) {
return $text;
}
$link = $this->fileLinkFormat->format($file, $line);
return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', $this->escape($link), $text);
return \sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', $this->escape($link), $text);
}
/**
@@ -250,10 +256,10 @@ class HtmlErrorRenderer implements ErrorRendererInterface
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(' ', '&nbsp;', $code);
// split multiline span tags
$code = preg_replace_callback('#<span ([^>]++)>((?:[^<\\n]*+\\n)++[^<]*+)</span>#', function ($m) {
return "<span $m[1]>".str_replace("\n", "</span>\n<span $m[1]>", $m[2]).'</span>';
}, $code);
$content = explode("\n", $code);
} else {
// remove main code/span tags
@@ -299,7 +305,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
private function formatFileFromText(string $text): string
{
return preg_replace_callback('/in ("|&quot;)?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text);
return preg_replace_callback('/in ("|&quot;)?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text) ?? $text;
}
private function formatLogMessage(string $message, array $context): string

View File

@@ -34,7 +34,7 @@ class SerializerErrorRenderer implements ErrorRendererInterface
* 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, string|callable $format, ErrorRendererInterface $fallbackErrorRenderer = null, bool|callable $debug = false)
public function __construct(SerializerInterface $serializer, string|callable $format, ?ErrorRendererInterface $fallbackErrorRenderer = null, bool|callable $debug = false)
{
$this->serializer = $serializer;
$this->format = \is_string($format) ? $format : $format(...);
@@ -47,7 +47,7 @@ class SerializerErrorRenderer implements ErrorRendererInterface
$headers = ['Vary' => 'Accept'];
$debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception);
if ($debug) {
$headers['X-Debug-Exception'] = rawurlencode($exception->getMessage());
$headers['X-Debug-Exception'] = rawurlencode(substr($exception->getMessage(), 0, 2000));
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}

View File

@@ -42,12 +42,12 @@ class FlattenException
private ?string $asString = null;
private Data $dataRepresentation;
public static function create(\Exception $exception, int $statusCode = null, array $headers = []): static
public static function create(\Exception $exception, ?int $statusCode = null, array $headers = []): static
{
return static::createFromThrowable($exception, $statusCode, $headers);
}
public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): static
public static function createFromThrowable(\Throwable $exception, ?int $statusCode = null, array $headers = []): static
{
$e = new static();
$e->setMessage($exception->getMessage());
@@ -85,7 +85,7 @@ class FlattenException
return $e;
}
public static function createWithDataRepresentation(\Throwable $throwable, int $statusCode = null, array $headers = [], VarCloner $cloner = null): static
public static function createWithDataRepresentation(\Throwable $throwable, ?int $statusCode = null, array $headers = [], ?VarCloner $cloner = null): static
{
$e = static::createFromThrowable($throwable, $statusCode, $headers);
@@ -228,7 +228,7 @@ class FlattenException
public function setMessage(string $message): static
{
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);
$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;

View File

@@ -57,7 +57,7 @@
--page-background: #36393e;
--color-text: #e0e0e0;
--color-muted: #777;
--color-error: #d43934;
--color-error: #f76864;
--tab-background: #404040;
--tab-border-color: #737373;
--tab-active-border-color: #171717;
@@ -80,7 +80,7 @@
--metric-unit-color: #999;
--metric-label-background: #777;
--metric-label-color: #e0e0e0;
--trace-selected-background: #71663acc;
--trace-selected-background: #5d5227cc;
--table-border: #444;
--table-background: #333;
--table-header: #555;
@@ -92,7 +92,7 @@
--background-error: #b0413e;
--highlight-comment: #dedede;
--highlight-default: var(--base-6);
--highlight-keyword: #ff413c;
--highlight-keyword: #de8986;
--highlight-string: #70a6fd;
--base-0: #2e3136;
--base-1: #444;
@@ -349,7 +349,7 @@ header .container { display: flex; justify-content: space-between; }
.trace-code li { color: #969896; margin: 0; padding-left: 10px; float: left; width: 100%; }
.trace-code li + li { margin-top: 5px; }
.trace-code li.selected { background: var(--trace-selected-background); margin-top: 2px; }
.trace-code li code { color: var(--base-6); white-space: nowrap; }
.trace-code li code { color: var(--base-6); white-space: pre; }
.trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; }

View File

@@ -145,6 +145,12 @@
}
addEventListener(toggles[i], 'click', function(e) {
var toggle = e.currentTarget;
if (e.target.closest('a, span[data-clipboard-text], .sf-toggle') !== toggle) {
return;
}
e.preventDefault();
if ('' !== window.getSelection().toString()) {
@@ -152,14 +158,6 @@
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');
@@ -182,22 +180,6 @@
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');
}
})();

View File

View File

@@ -1,10 +1,10 @@
<!DOCTYPE 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>">
<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>

View File

@@ -2,11 +2,11 @@
<!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'); ?>">
<link rel="icon" type="image/png" href="<?= $this->include('assets/images/favicon.png.base64'); ?>" />
<style><?= $this->include('assets/css/exception.css'); ?></style>
<style><?= $this->include('assets/css/exception_full.css'); ?></style>
</head>