From 64908ae925a52b500fcd1106858a68d163cc07f1 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 20 Jun 2024 17:52:34 +0200 Subject: [PATCH 01/17] Prefix all sprintf() calls --- BufferingLogger.php | 2 +- DebugClassLoader.php | 34 +++++++++---------- ErrorEnhancer/ClassNotFoundErrorEnhancer.php | 4 +-- .../UndefinedFunctionErrorEnhancer.php | 4 +-- .../UndefinedMethodErrorEnhancer.php | 2 +- ErrorRenderer/HtmlErrorRenderer.php | 12 +++---- Tests/Exception/FlattenExceptionTest.php | 2 +- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/BufferingLogger.php b/BufferingLogger.php index 6790974..e6597b4 100644 --- a/BufferingLogger.php +++ b/BufferingLogger.php @@ -62,7 +62,7 @@ public function __destruct() } } - error_log(sprintf('%s [%s] %s', date(\DateTimeInterface::RFC3339), $level, $message)); + error_log(\sprintf('%s [%s] %s', date(\DateTimeInterface::RFC3339), $level, $message)); } } } diff --git a/DebugClassLoader.php b/DebugClassLoader.php index 5c8672e..6a6338d 100644 --- a/DebugClassLoader.php +++ b/DebugClassLoader.php @@ -332,7 +332,7 @@ private function checkClass(string $class, ?string $file = null): void $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); @@ -348,14 +348,14 @@ private function checkClass(string $class, ?string $file = null): void 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])); } } @@ -416,7 +416,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array } 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); } } @@ -429,10 +429,10 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array $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()) { @@ -458,7 +458,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array } $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); } } } @@ -522,13 +522,13 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array 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); } } @@ -547,7 +547,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array 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); } } } @@ -583,7 +583,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array 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); } } } @@ -632,7 +632,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array } 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); } } } @@ -652,7 +652,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array 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); } } @@ -893,8 +893,8 @@ private function setReturnType(string $types, string $class, string $method, str } } - $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]; } @@ -1026,7 +1026,7 @@ private function patchMethod(\ReflectionMethod $method, string $returnType, stri ++$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'])) { diff --git a/ErrorEnhancer/ClassNotFoundErrorEnhancer.php b/ErrorEnhancer/ClassNotFoundErrorEnhancer.php index b4623cf..fc243a6 100644 --- a/ErrorEnhancer/ClassNotFoundErrorEnhancer.php +++ b/ErrorEnhancer/ClassNotFoundErrorEnhancer.php @@ -34,11 +34,11 @@ public function enhance(\Throwable $error): ?\Throwable 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 = '?'; } diff --git a/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php b/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php index 0458c26..e1d54ab 100644 --- a/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php +++ b/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php @@ -47,10 +47,10 @@ public function enhance(\Throwable $error): ?\Throwable 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 = []; diff --git a/ErrorEnhancer/UndefinedMethodErrorEnhancer.php b/ErrorEnhancer/UndefinedMethodErrorEnhancer.php index 80eaec9..e331c17 100644 --- a/ErrorEnhancer/UndefinedMethodErrorEnhancer.php +++ b/ErrorEnhancer/UndefinedMethodErrorEnhancer.php @@ -34,7 +34,7 @@ public function enhance(\Throwable $error): ?\Throwable $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) diff --git a/ErrorRenderer/HtmlErrorRenderer.php b/ErrorRenderer/HtmlErrorRenderer.php index 7cb7cd3..16f7588 100644 --- a/ErrorRenderer/HtmlErrorRenderer.php +++ b/ErrorRenderer/HtmlErrorRenderer.php @@ -160,9 +160,9 @@ private function formatArgs(array $args): string $result = []; foreach ($args as $key => $item) { if ('object' === $item[0]) { - $formattedValue = sprintf('object(%s)', $this->abbrClass($item[1])); + $formattedValue = \sprintf('object(%s)', $this->abbrClass($item[1])); } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + $formattedValue = \sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); } elseif ('null' === $item[0]) { $formattedValue = 'null'; } elseif ('boolean' === $item[0]) { @@ -175,7 +175,7 @@ private function formatArgs(array $args): string $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); @@ -196,7 +196,7 @@ private function abbrClass(string $class): string $parts = explode('\\', $class); $short = array_pop($parts); - return sprintf('%s', $class, $short); + return \sprintf('%s', $class, $short); } private function getFileRelative(string $file): ?string @@ -225,7 +225,7 @@ private function formatFile(string $file, int $line, ?string $text = null): stri $text = $file; if (null !== $rel = $this->getFileRelative($text)) { $rel = explode('/', $rel, 2); - $text = sprintf('%s%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? '')); + $text = \sprintf('%s%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? '')); } } @@ -235,7 +235,7 @@ private function formatFile(string $file, int $line, ?string $text = null): stri $link = $this->fileLinkFormat->format($file, $line); - return sprintf('%s', $this->escape($link), $text); + return \sprintf('%s', $this->escape($link), $text); } /** diff --git a/Tests/Exception/FlattenExceptionTest.php b/Tests/Exception/FlattenExceptionTest.php index a3ff2f6..737c246 100644 --- a/Tests/Exception/FlattenExceptionTest.php +++ b/Tests/Exception/FlattenExceptionTest.php @@ -255,7 +255,7 @@ public function testAnonymousClass() $this->assertSame('Symfony\Component\HttpKernel\Exception\NotFoundHttpException@anonymous', $flattened->getClass()); - $flattened = FlattenException::createFromThrowable(new \Exception(sprintf('Class "%s" blah.', (new class() extends \RuntimeException { + $flattened = FlattenException::createFromThrowable(new \Exception(\sprintf('Class "%s" blah.', (new class() extends \RuntimeException { })::class))); $this->assertSame('Class "RuntimeException@anonymous" blah.', $flattened->getMessage()); From 9faaecf0003801392ab1fdc150fdee45b12ef7c8 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 16 Jun 2024 17:17:26 +0200 Subject: [PATCH 02/17] chore: CS fixes --- DebugClassLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DebugClassLoader.php b/DebugClassLoader.php index 6a6338d..fd69b97 100644 --- a/DebugClassLoader.php +++ b/DebugClassLoader.php @@ -1135,7 +1135,7 @@ private function fixReturnStatements(\ReflectionMethod $method, string $returnTy $braces = 0; for (; $i < $end; ++$i) { if (!$inClosure) { - $inClosure = false !== strpos($code[$i], 'function ('); + $inClosure = str_contains($code[$i], 'function ('); } if ($inClosure) { From f97e68e55daa802548aa47b4c6e89db973b63dc5 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 6 Jul 2024 09:57:16 +0200 Subject: [PATCH 03/17] Update .gitattributes --- .gitattributes | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 84c7add..14c3c35 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore From 04d41f32dd20bf1350b2d47850c62ba0c3c6653d Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 22 Jul 2024 10:27:43 +0200 Subject: [PATCH 04/17] Use CPP where possible --- ErrorHandler.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ErrorHandler.php b/ErrorHandler.php index fd83eb3..f1d805d 100644 --- a/ErrorHandler.php +++ b/ErrorHandler.php @@ -92,7 +92,6 @@ class ErrorHandler private int $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE private int $loggedErrors = 0; private \Closure $configureException; - private bool $debug; private bool $isRecursive = false; private bool $isRoot = false; @@ -179,8 +178,10 @@ public static function call(callable $function, mixed ...$arguments): mixed } } - public function __construct(?BufferingLogger $bootstrappingLogger = null, bool $debug = false) - { + public function __construct( + ?BufferingLogger $bootstrappingLogger = null, + private bool $debug = false, + ) { if ($bootstrappingLogger) { $this->bootstrappingLogger = $bootstrappingLogger; $this->setDefaultLogger($bootstrappingLogger); @@ -192,7 +193,6 @@ public function __construct(?BufferingLogger $bootstrappingLogger = null, bool $ $e->line = $line ?? $e->line; }, null, new class() extends \Exception { }); - $this->debug = $debug; } /** From 3e985c910c25e97f36ea6b397bc1d6d40c776c98 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 5 Aug 2024 09:12:25 +0200 Subject: [PATCH 05/17] Fix multiple CS errors --- Resources/views/exception_full.html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/views/exception_full.html.php b/Resources/views/exception_full.html.php index af04db1..a865b1c 100644 --- a/Resources/views/exception_full.html.php +++ b/Resources/views/exception_full.html.php @@ -1,4 +1,4 @@ - + From 0ee7b0fcbdcaf6bedddaaec82dcd847fb1daab64 Mon Sep 17 00:00:00 2001 From: Roy de Vos Burchart Date: Thu, 1 Aug 2024 17:21:17 +0200 Subject: [PATCH 06/17] Code style change in `@PER-CS2.0` affecting `@Symfony` (parentheses for anonymous classes) --- ErrorHandler.php | 2 +- ErrorRenderer/CliErrorRenderer.php | 2 +- Tests/ErrorHandlerTest.php | 4 ++-- Tests/Exception/FlattenExceptionTest.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ErrorHandler.php b/ErrorHandler.php index 0bce60a..a050fee 100644 --- a/ErrorHandler.php +++ b/ErrorHandler.php @@ -194,7 +194,7 @@ public function __construct( $traceReflector->setValue($e, $trace); $e->file = $file ?? $e->file; $e->line = $line ?? $e->line; - }, null, new class() extends \Exception { + }, null, new class extends \Exception { }); } diff --git a/ErrorRenderer/CliErrorRenderer.php b/ErrorRenderer/CliErrorRenderer.php index 04b3edb..c414c83 100644 --- a/ErrorRenderer/CliErrorRenderer.php +++ b/ErrorRenderer/CliErrorRenderer.php @@ -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; diff --git a/Tests/ErrorHandlerTest.php b/Tests/ErrorHandlerTest.php index e5632ca..46e01ff 100644 --- a/Tests/ErrorHandlerTest.php +++ b/Tests/ErrorHandlerTest.php @@ -333,7 +333,7 @@ public function testHandleError() public function testHandleErrorWithAnonymousClass() { - $anonymousObject = new class() extends \stdClass { + $anonymousObject = new class extends \stdClass { }; $handler = ErrorHandler::register(); @@ -422,7 +422,7 @@ public static function handleExceptionProvider(): array ['Uncaught Exception: foo', new \Exception('foo')], ['Uncaught Exception: foo', new class('foo') extends \RuntimeException { }], - ['Uncaught Exception: foo stdClass@anonymous bar', new \RuntimeException('foo '.(new class() extends \stdClass { + ['Uncaught Exception: foo stdClass@anonymous bar', new \RuntimeException('foo '.(new class extends \stdClass { })::class.' bar')], ['Uncaught Error: bar', new \Error('bar')], ['Uncaught ccc', new \ErrorException('ccc')], diff --git a/Tests/Exception/FlattenExceptionTest.php b/Tests/Exception/FlattenExceptionTest.php index 737c246..c6efb3c 100644 --- a/Tests/Exception/FlattenExceptionTest.php +++ b/Tests/Exception/FlattenExceptionTest.php @@ -245,7 +245,7 @@ public static function stringAndIntDataProvider(): array public function testAnonymousClass() { - $flattened = FlattenException::createFromThrowable(new class() extends \RuntimeException { + $flattened = FlattenException::createFromThrowable(new class extends \RuntimeException { }); $this->assertSame('RuntimeException@anonymous', $flattened->getClass()); @@ -255,7 +255,7 @@ public function testAnonymousClass() $this->assertSame('Symfony\Component\HttpKernel\Exception\NotFoundHttpException@anonymous', $flattened->getClass()); - $flattened = FlattenException::createFromThrowable(new \Exception(\sprintf('Class "%s" blah.', (new class() extends \RuntimeException { + $flattened = FlattenException::createFromThrowable(new \Exception(\sprintf('Class "%s" blah.', (new class extends \RuntimeException { })::class))); $this->assertSame('Class "RuntimeException@anonymous" blah.', $flattened->getMessage()); From 04b8d14d42fb4f11a1265748787c8dafd052d966 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 26 Aug 2024 17:35:30 +0200 Subject: [PATCH 07/17] Use Stringable whenever possible --- BufferingLogger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BufferingLogger.php b/BufferingLogger.php index e6597b4..cbbe499 100644 --- a/BufferingLogger.php +++ b/BufferingLogger.php @@ -50,7 +50,7 @@ public function __destruct() foreach ($this->logs as [$level, $message, $context]) { if (str_contains($message, '{')) { foreach ($context as $key => $val) { - if (null === $val || \is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) { + if (null === $val || \is_scalar($val) || $val instanceof \Stringable) { $message = str_replace("{{$key}}", $val, $message); } elseif ($val instanceof \DateTimeInterface) { $message = str_replace("{{$key}}", $val->format(\DateTimeInterface::RFC3339), $message); From 4a831eb99455e10540601d99967ed0fb27185be0 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sat, 31 Aug 2024 00:31:12 +0200 Subject: [PATCH 08/17] CS: re-apply `trailing_comma_in_multiline` --- Tests/DebugClassLoaderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/DebugClassLoaderTest.php b/Tests/DebugClassLoaderTest.php index 6a910d7..4e8d2b3 100644 --- a/Tests/DebugClassLoaderTest.php +++ b/Tests/DebugClassLoaderTest.php @@ -421,7 +421,7 @@ class_exists('Test\\'.OverrideOutsideFinalProperty::class, true); 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalProperty\FinalProperty::$pub" property is considered final. You should not override it in "Symfony\Component\ErrorHandler\Tests\Fixtures\OverrideFinalProperty".', 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalProperty\FinalProperty::$prot" property is considered final. You should not override it in "Symfony\Component\ErrorHandler\Tests\Fixtures\OverrideFinalProperty".', 'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalProperty\FinalProperty::$implicitlyFinal" property is considered final. You should not override it in "Symfony\Component\ErrorHandler\Tests\Fixtures\OverrideFinalProperty".', - 'The "Test\Symfony\Component\ErrorHandler\Tests\FinalProperty\OutsideFinalProperty::$final" property is considered final. You should not override it in "Test\Symfony\Component\ErrorHandler\Tests\OverrideOutsideFinalProperty".' + 'The "Test\Symfony\Component\ErrorHandler\Tests\FinalProperty\OutsideFinalProperty::$final" property is considered final. You should not override it in "Test\Symfony\Component\ErrorHandler\Tests\OverrideOutsideFinalProperty".', ], $deprecations); } From 95d878c731759422f505e8505d8df2a5d2595aa8 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 4 Sep 2024 16:39:53 +0200 Subject: [PATCH 09/17] Remove no-op `ReflectionProperty::setAccessible()` calls --- Tests/ErrorHandlerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/ErrorHandlerTest.php b/Tests/ErrorHandlerTest.php index 46e01ff..5f55cfb 100644 --- a/Tests/ErrorHandlerTest.php +++ b/Tests/ErrorHandlerTest.php @@ -34,7 +34,6 @@ class ErrorHandlerTest extends TestCase protected function tearDown(): void { $r = new \ReflectionProperty(ErrorHandler::class, 'exitCode'); - $r->setAccessible(true); $r->setValue(null, 0); } From ad57ca04868ca877e7919c56d20bdfa8024b54d6 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 10 Oct 2024 15:54:54 +0200 Subject: [PATCH 10/17] resolve class constant types when patching return types --- DebugClassLoader.php | 26 +++++++++++++++++++++++- Tests/DebugClassLoaderTest.php | 22 ++++++++++++++++++++ Tests/Fixtures/ReturnType.php | 1 + Tests/Fixtures/ReturnTypeParent.php | 9 ++++++++ Tests/Fixtures/ReturnTypeParentPhp83.php | 23 +++++++++++++++++++++ Tests/Fixtures/ReturnTypePhp83.php | 11 ++++++++++ 6 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 Tests/Fixtures/ReturnTypeParentPhp83.php create mode 100644 Tests/Fixtures/ReturnTypePhp83.php diff --git a/DebugClassLoader.php b/DebugClassLoader.php index fd69b97..b4709ad 100644 --- a/DebugClassLoader.php +++ b/DebugClassLoader.php @@ -849,6 +849,30 @@ private function setReturnType(string $types, string $class, string $method, str $docTypes = []; foreach ($typesMap as $n => $t) { + if (str_contains($n, '::')) { + [$definingClass, $constantName] = explode('::', $n, 2); + $definingClass = match ($definingClass) { + 'self', 'static', 'parent' => $class, + default => $definingClass, + }; + + if (!\defined($definingClass.'::'.$constantName)) { + return; + } + + $constant = new \ReflectionClassConstant($definingClass, $constantName); + + if (\PHP_VERSION_ID >= 80300 && $constantType = $constant->getType()) { + if ($constantType instanceof \ReflectionNamedType) { + $n = $constantType->getName(); + } else { + return; + } + } else { + $n = \gettype($constant->getValue()); + } + } + if ('null' === $n) { $nullable = true; continue; @@ -872,7 +896,7 @@ private function setReturnType(string $types, string $class, string $method, str continue; } - if (!isset($phpTypes[''])) { + if (!isset($phpTypes['']) && !\in_array($n, $phpTypes, true)) { $phpTypes[] = $n; } } diff --git a/Tests/DebugClassLoaderTest.php b/Tests/DebugClassLoaderTest.php index 4e8d2b3..8575f89 100644 --- a/Tests/DebugClassLoaderTest.php +++ b/Tests/DebugClassLoaderTest.php @@ -401,6 +401,26 @@ class_exists('Test\\'.ReturnType::class, true); 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::true()" might add "true" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::never()" might add "never" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::null()" might add "null" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::classConstant()" might add "string" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.', + ], $deprecations); + } + + /** + * @requires PHP >= 8.3 + */ + public function testReturnTypePhp83() + { + $deprecations = []; + set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; }); + $e = error_reporting(E_USER_DEPRECATED); + + class_exists('Test\\'.ReturnTypePhp83::class, true); + + error_reporting($e); + restore_error_handler(); + + $this->assertSame([ + 'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParentPhp83::classConstantWithType()" might add "string" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnTypePhp83" now to avoid errors or add an explicit @return annotation to suppress this message.', ], $deprecations); } @@ -542,6 +562,8 @@ public function ownAbstractBaseMethod() { } }'); } elseif ('Test\\'.ReturnType::class === $class) { return $fixtureDir.\DIRECTORY_SEPARATOR.'ReturnType.php'; + } elseif ('Test\\'.ReturnTypePhp83::class === $class) { + return $fixtureDir.\DIRECTORY_SEPARATOR.'ReturnTypePhp83.php'; } elseif ('Test\\'.Fixtures\OutsideInterface::class === $class) { return $fixtureDir.\DIRECTORY_SEPARATOR.'OutsideInterface.php'; } elseif ('Test\\'.OverrideOutsideFinalProperty::class === $class) { diff --git a/Tests/Fixtures/ReturnType.php b/Tests/Fixtures/ReturnType.php index 1b81380..72570cf 100644 --- a/Tests/Fixtures/ReturnType.php +++ b/Tests/Fixtures/ReturnType.php @@ -51,4 +51,5 @@ public function true() { } public function never() { } public function null() { } public function outsideMethod() { } + public function classConstant() { } } diff --git a/Tests/Fixtures/ReturnTypeParent.php b/Tests/Fixtures/ReturnTypeParent.php index d42c7c8..3803a74 100644 --- a/Tests/Fixtures/ReturnTypeParent.php +++ b/Tests/Fixtures/ReturnTypeParent.php @@ -4,6 +4,8 @@ abstract class ReturnTypeParent extends ReturnTypeGrandParent implements ReturnTypeParentInterface { + const FOO = 'foo'; + /** * @return void */ @@ -254,4 +256,11 @@ public function null() public function notExtended() { } + + /** + * @return self::FOO + */ + public function classConstant() + { + } } diff --git a/Tests/Fixtures/ReturnTypeParentPhp83.php b/Tests/Fixtures/ReturnTypeParentPhp83.php new file mode 100644 index 0000000..c7d5ca8 --- /dev/null +++ b/Tests/Fixtures/ReturnTypeParentPhp83.php @@ -0,0 +1,23 @@ + Date: Sat, 7 Dec 2024 09:35:37 +0100 Subject: [PATCH 11/17] support non-empty-string/non-empty-list when patching return types --- DebugClassLoader.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DebugClassLoader.php b/DebugClassLoader.php index b4709ad..d3435e2 100644 --- a/DebugClassLoader.php +++ b/DebugClassLoader.php @@ -68,12 +68,14 @@ class DebugClassLoader 'iterable' => 'iterable', 'object' => 'object', 'string' => 'string', + 'non-empty-string' => 'string', 'self' => 'self', 'parent' => 'parent', 'mixed' => 'mixed', 'static' => 'static', '$this' => 'static', 'list' => 'array', + 'non-empty-list' => 'array', 'class-string' => 'string', 'never' => 'never', ]; From 3d4e55cd2b8f1979a65eba9ab749d6466c316f71 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sun, 2 Feb 2025 20:54:34 +0100 Subject: [PATCH 12/17] relax expected format for PHP 8.5 compatibility --- Tests/phpt/fatal_with_nested_handlers.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/phpt/fatal_with_nested_handlers.phpt b/Tests/phpt/fatal_with_nested_handlers.phpt index 81becaf..80a7645 100644 --- a/Tests/phpt/fatal_with_nested_handlers.phpt +++ b/Tests/phpt/fatal_with_nested_handlers.phpt @@ -40,7 +40,7 @@ object(Symfony\Component\ErrorHandler\Error\FatalError)#%d (%d) { string(209) "Error: Class Symfony\Component\ErrorHandler\Broken contains 5 abstract methods and must therefore be declared abstract or implement the remaining methods (Iterator::current, Iterator::next, Iterator::key, ...)" %a ["error":"Symfony\Component\ErrorHandler\Error\FatalError":private]=> - array(4) { + array(%d) { ["type"]=> int(1) ["message"]=> @@ -48,6 +48,6 @@ object(Symfony\Component\ErrorHandler\Error\FatalError)#%d (%d) { ["file"]=> string(%d) "%s" ["line"]=> - int(%d) + int(%d)%A } } From aa3bcf4f7674719df078e61cc8062e5b7f752031 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 28 Feb 2025 23:36:36 +0100 Subject: [PATCH 13/17] don't trigger "internal" deprecations for PHPUnit Stub objects --- DebugClassLoader.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DebugClassLoader.php b/DebugClassLoader.php index b6ad33a..3f2a136 100644 --- a/DebugClassLoader.php +++ b/DebugClassLoader.php @@ -18,6 +18,7 @@ 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; @@ -253,6 +254,7 @@ public static function checkClasses(): bool 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) From f344b88b3452afa01152a0b666c30b6fce07bd21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFck=20Piera?= Date: Tue, 5 Nov 2024 15:14:54 +0100 Subject: [PATCH 14/17] [ErrorHandler] Add a command to dump static error pages --- CHANGELOG.md | 5 ++ Command/ErrorDumpCommand.php | 85 ++++++++++++++++++ Tests/Command/ErrorDumpCommandTest.php | 114 +++++++++++++++++++++++++ composer.json | 4 +- 4 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 Command/ErrorDumpCommand.php create mode 100644 Tests/Command/ErrorDumpCommandTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c3037ad..cd8d07d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.3 +--- + + * Add `error:dump` command + 7.1 --- diff --git a/Command/ErrorDumpCommand.php b/Command/ErrorDumpCommand.php new file mode 100644 index 0000000..95b6f44 --- /dev/null +++ b/Command/ErrorDumpCommand.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\WebpackEncoreBundle\Asset\EntrypointLookupInterface; + +/** + * Dump error pages to plain HTML files that can be directly served by a web server. + * + * @author Loïck Piera + */ +#[AsCommand( + name: 'error:dump', + description: 'Dump error pages to plain HTML files that can be directly served by a web server', +)] +final class ErrorDumpCommand extends Command +{ + public function __construct( + private readonly Filesystem $filesystem, + private readonly ErrorRendererInterface $errorRenderer, + private readonly ?EntrypointLookupInterface $entrypointLookup = null, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->addArgument('path', InputArgument::REQUIRED, 'Path where to dump the error pages in') + ->addArgument('status-codes', InputArgument::IS_ARRAY, 'Status codes to dump error pages for, all of them by default') + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force directory removal before dumping new error pages') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $path = $input->getArgument('path'); + + $io = new SymfonyStyle($input, $output); + $io->title('Dumping error pages'); + + $this->dump($io, $path, $input->getArgument('status-codes'), (bool) $input->getOption('force')); + $io->success(\sprintf('Error pages have been dumped in "%s".', $path)); + + return Command::SUCCESS; + } + + private function dump(SymfonyStyle $io, string $path, array $statusCodes, bool $force = false): void + { + if (!$statusCodes) { + $statusCodes = array_filter(array_keys(Response::$statusTexts), fn ($statusCode) => $statusCode >= 400); + } + + if ($force || ($this->filesystem->exists($path) && $io->confirm(\sprintf('The "%s" directory already exists. Do you want to remove it before dumping the error pages?', $path), false))) { + $this->filesystem->remove($path); + } + + foreach ($statusCodes as $statusCode) { + // Avoid assets to be included only on the first dumped page + $this->entrypointLookup?->reset(); + + $this->filesystem->dumpFile($path.\DIRECTORY_SEPARATOR.$statusCode.'.html', $this->errorRenderer->render(new HttpException((int) $statusCode))->getAsString()); + } + } +} diff --git a/Tests/Command/ErrorDumpCommandTest.php b/Tests/Command/ErrorDumpCommandTest.php new file mode 100644 index 0000000..83b2bed --- /dev/null +++ b/Tests/Command/ErrorDumpCommandTest.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Tests\Command; + +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\TwigBundle\Tests\TestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\ErrorHandler\Command\ErrorDumpCommand; +use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\WebpackEncoreBundle\Asset\EntrypointLookupInterface; + +class ErrorDumpCommandTest extends TestCase +{ + private string $tmpDir = ''; + + protected function setUp(): void + { + $this->tmpDir = sys_get_temp_dir().'/error_pages'; + + $fs = new Filesystem(); + $fs->remove($this->tmpDir); + } + + public function testDumpPages() + { + $tester = $this->getCommandTester($this->getKernel(), []); + $tester->execute([ + 'path' => $this->tmpDir, + ]); + + $this->assertFileExists($this->tmpDir.\DIRECTORY_SEPARATOR.'404.html'); + $this->assertStringContainsString('Error 404', file_get_contents($this->tmpDir.\DIRECTORY_SEPARATOR.'404.html')); + } + + public function testDumpPagesOnlyForGivenStatusCodes() + { + $fs = new Filesystem(); + $fs->mkdir($this->tmpDir); + $fs->touch($this->tmpDir.\DIRECTORY_SEPARATOR.'test.html'); + + $tester = $this->getCommandTester($this->getKernel()); + $tester->execute([ + 'path' => $this->tmpDir, + 'status-codes' => ['400', '500'], + ]); + + $this->assertFileExists($this->tmpDir.\DIRECTORY_SEPARATOR.'test.html'); + $this->assertFileDoesNotExist($this->tmpDir.\DIRECTORY_SEPARATOR.'404.html'); + + $this->assertFileExists($this->tmpDir.\DIRECTORY_SEPARATOR.'400.html'); + $this->assertStringContainsString('Error 400', file_get_contents($this->tmpDir.\DIRECTORY_SEPARATOR.'400.html')); + } + + public function testForceRemovalPages() + { + $fs = new Filesystem(); + $fs->mkdir($this->tmpDir); + $fs->touch($this->tmpDir.\DIRECTORY_SEPARATOR.'test.html'); + + $tester = $this->getCommandTester($this->getKernel()); + $tester->execute([ + 'path' => $this->tmpDir, + '--force' => true, + ]); + + $this->assertFileDoesNotExist($this->tmpDir.\DIRECTORY_SEPARATOR.'test.html'); + $this->assertFileExists($this->tmpDir.\DIRECTORY_SEPARATOR.'404.html'); + } + + private function getKernel(): MockObject&KernelInterface + { + return $this->createMock(KernelInterface::class); + } + + private function getCommandTester(KernelInterface $kernel): CommandTester + { + $errorRenderer = $this->createStub(ErrorRendererInterface::class); + $errorRenderer + ->method('render') + ->willReturnCallback(function (HttpException $e) { + $exception = FlattenException::createFromThrowable($e); + $exception->setAsString(\sprintf('Error %s', $e->getStatusCode())); + + return $exception; + }) + ; + + $entrypointLookup = $this->createMock(EntrypointLookupInterface::class); + + $application = new Application($kernel); + $application->add(new ErrorDumpCommand( + new Filesystem(), + $errorRenderer, + $entrypointLookup, + )); + + return new CommandTester($application->find('error:dump')); + } +} diff --git a/composer.json b/composer.json index 987a68f..98b9432 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,11 @@ "symfony/var-dumper": "^6.4|^7.0" }, "require-dev": { + "symfony/console": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/serializer": "^6.4|^7.0", - "symfony/deprecation-contracts": "^2.5|^3" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "conflict": { "symfony/deprecation-contracts": "<2.5", From 070e14ba8214e793a989d93c98e28445c0607afe Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 3 Mar 2025 09:24:15 +0100 Subject: [PATCH 15/17] [ErrorHandler] Improve an error message --- ErrorEnhancer/UndefinedFunctionErrorEnhancer.php | 4 ++-- .../ErrorEnhancer/UndefinedFunctionErrorEnhancerTest.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php b/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php index e1d54ab..41d7af3 100644 --- a/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php +++ b/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php @@ -47,10 +47,10 @@ public function enhance(\Throwable $error): ?\Throwable 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 undefined 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 undefined function "%s" from the global namespace.', $functionName); } $candidates = []; diff --git a/Tests/ErrorEnhancer/UndefinedFunctionErrorEnhancerTest.php b/Tests/ErrorEnhancer/UndefinedFunctionErrorEnhancerTest.php index f9474f7..b5a0d91 100644 --- a/Tests/ErrorEnhancer/UndefinedFunctionErrorEnhancerTest.php +++ b/Tests/ErrorEnhancer/UndefinedFunctionErrorEnhancerTest.php @@ -39,19 +39,19 @@ public static function provideUndefinedFunctionData() return [ [ 'Call to undefined function test_namespaced_function()', - "Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\errorenhancer\\test_namespaced_function\"?", + "Attempted to call undefined function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\errorenhancer\\test_namespaced_function\"?", ], [ 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()', - "Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\errorenhancer\\test_namespaced_function\"?", + "Attempted to call undefined function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\errorhandler\\tests\\errorenhancer\\test_namespaced_function\"?", ], [ 'Call to undefined function foo()', - 'Attempted to call function "foo" from the global namespace.', + 'Attempted to call undefined function "foo" from the global namespace.', ], [ 'Call to undefined function Foo\\Bar\\Baz\\foo()', - 'Attempted to call function "foo" from namespace "Foo\Bar\Baz".', + 'Attempted to call undefined function "foo" from namespace "Foo\Bar\Baz".', ], ]; } From 47a96276149f049ba944cbd470f4d17bf42914e3 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Thu, 13 Mar 2025 23:58:12 +0100 Subject: [PATCH 16/17] chore: PHP CS Fixer fixes --- Tests/Command/ErrorDumpCommandTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/Command/ErrorDumpCommandTest.php b/Tests/Command/ErrorDumpCommandTest.php index 83b2bed..670adbd 100644 --- a/Tests/Command/ErrorDumpCommandTest.php +++ b/Tests/Command/ErrorDumpCommandTest.php @@ -15,7 +15,6 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\TwigBundle\Tests\TestCase; use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\DependencyInjection\Container; use Symfony\Component\ErrorHandler\Command\ErrorDumpCommand; use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; use Symfony\Component\ErrorHandler\Exception\FlattenException; From ce765a2d28b3cce61de1fb916e207767a73171d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 28 May 2025 14:00:15 +0200 Subject: [PATCH 17/17] [ErrorHandler] Do not transform file to link if it does not exist --- ErrorRenderer/HtmlErrorRenderer.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ErrorRenderer/HtmlErrorRenderer.php b/ErrorRenderer/HtmlErrorRenderer.php index 032f194..2572a8a 100644 --- a/ErrorRenderer/HtmlErrorRenderer.php +++ b/ErrorRenderer/HtmlErrorRenderer.php @@ -231,6 +231,10 @@ private function formatFile(string $file, int $line, ?string $text = null): stri $text .= ' at line '.$line; } + if (!file_exists($file)) { + return $text; + } + $link = $this->fileLinkFormat->format($file, $line); return sprintf('%s', $this->escape($link), $text);