diff --git a/UPGRADE-6.4.md b/UPGRADE-6.4.md index ddfc9883a5acc..280d06a073b5d 100644 --- a/UPGRADE-6.4.md +++ b/UPGRADE-6.4.md @@ -107,6 +107,7 @@ HttpKernel * [BC break] `BundleInterface` no longer extends `ContainerAwareInterface` * [BC break] Add native return types to `TraceableEventDispatcher` and to `MergeExtensionConfigurationPass` + * Deprecate `Kernel::stripComments()` Messenger --------- diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 92949d55f1f01..8a5b4b1467a59 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -44,7 +44,6 @@ use Symfony\Component\DependencyInjection\Variable; use Symfony\Component\ErrorHandler\DebugClassLoader; use Symfony\Component\ExpressionLanguage\Expression; -use Symfony\Component\HttpKernel\Kernel; /** * PhpDumper dumps a service container as a PHP class. @@ -571,7 +570,7 @@ private function generateProxyClasses(): array $proxyClasses = []; $alreadyGenerated = []; $definitions = $this->container->getDefinitions(); - $strip = '' === $this->docStar && method_exists(Kernel::class, 'stripComments'); + $strip = '' === $this->docStar; $proxyDumper = $this->getProxyDumper(); ksort($definitions); foreach ($definitions as $id => $definition) { @@ -620,7 +619,7 @@ private function generateProxyClasses(): array if ($strip) { $proxyCode = "inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode, 3)[1]; @@ -2339,4 +2338,65 @@ private function isProxyCandidate(Definition $definition, ?bool &$asGhostObject, return $this->getProxyDumper()->isProxyCandidate($definition, $asGhostObject, $id) ? $definition : null; } + + /** + * Removes comments from a PHP source string. + * + * We don't use the PHP php_strip_whitespace() function + * as we want the content to be readable and well-formatted. + */ + private static function stripComments(string $source): string + { + if (!\function_exists('token_get_all')) { + return $source; + } + + $rawChunk = ''; + $output = ''; + $tokens = token_get_all($source); + $ignoreSpace = false; + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + if (!isset($token[1]) || 'b"' === $token) { + $rawChunk .= $token; + } elseif (\T_START_HEREDOC === $token[0]) { + $output .= $rawChunk.$token[1]; + do { + $token = $tokens[++$i]; + $output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; + } while (\T_END_HEREDOC !== $token[0]); + $rawChunk = ''; + } elseif (\T_WHITESPACE === $token[0]) { + if ($ignoreSpace) { + $ignoreSpace = false; + + continue; + } + + // replace multiple new lines with a single newline + $rawChunk .= preg_replace(['/\n{2,}/S'], "\n", $token[1]); + } elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT])) { + if (!\in_array($rawChunk[\strlen($rawChunk) - 1], [' ', "\n", "\r", "\t"], true)) { + $rawChunk .= ' '; + } + $ignoreSpace = true; + } else { + $rawChunk .= $token[1]; + + // The PHP-open tag already has a new-line + if (\T_OPEN_TAG === $token[0]) { + $ignoreSpace = true; + } else { + $ignoreSpace = false; + } + } + } + + $output .= $rawChunk; + + unset($tokens, $rawChunk); + gc_mem_caches(); + + return $output; + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 55a4d231a8623..2d0a8aad0ce16 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -1976,6 +1976,110 @@ public function testCallableAdapterConsumer() $this->assertInstanceOf(SingleMethodInterface::class, $container->get('bar')->foo); $this->assertInstanceOf(Foo::class, $container->get('bar')->foo->theMethod()); } + + /** + * @dataProvider getStripCommentsCodes + */ + public function testStripComments(string $source, string $expected) + { + $reflection = new \ReflectionClass(PhpDumper::class); + $method = $reflection->getMethod('stripComments'); + + $output = $method->invoke(null, $source); + + // Heredocs are preserved, making the output mixing Unix and Windows line + // endings, switching to "\n" everywhere on Windows to avoid failure. + if ('\\' === \DIRECTORY_SEPARATOR) { + $expected = str_replace("\r\n", "\n", $expected); + $output = str_replace("\r\n", "\n", $output); + } + + $this->assertEquals($expected, $output); + } + + public static function getStripCommentsCodes(): array + { + return [ + ['expectDeprecation('Since symfony/http-kernel 6.4: Method "Symfony\Component\HttpKernel\Kernel::stripComments()" is deprecated without replacement.'); + $output = Kernel::stripComments($source); // Heredocs are preserved, making the output mixing Unix and Windows line