Restore void return type compatibility for extension points

This commit is contained in:
Fabien Potencier
2026-08-26 20:51:38 +02:00
parent 885cbdb58f
commit 6bbbb49c3e
15 changed files with 180 additions and 39 deletions
+42
View File
@@ -1,7 +1,9 @@
<?php
use PhpCsFixer\Config;
use PhpCsFixer\Config\RuleCustomisationPolicyInterface;
use PhpCsFixer\Finder;
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
return (new Config())
->setRules([
@@ -18,6 +20,46 @@ return (new Config())
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'allow_unused_params' => true],
])
->setRuleCustomisationPolicy(new class implements RuleCustomisationPolicyInterface {
public function getPolicyVersionForCache(): string
{
return hash_file('xxh128', __FILE__);
}
public function getRuleCustomisers(): array
{
return [
'void_return' => static function (\SplFileInfo $file) {
$pathname = str_replace('\\', '/', $file->getPathname());
// These files intentionally omit void return types on extension points to preserve Twig 3.x subclass compatibility.
foreach ([
'extra/twig-extra-bundle/DependencyInjection/Compiler/MissingExtensionSuggestorPass.php',
'extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php',
'extra/twig-extra-bundle/TwigExtraBundle.php',
'src/Environment.php',
'src/Extension/ProfilerExtension.php',
'src/Node/CheckSecurityCallNode.php',
'src/Node/Expression/CallExpression.php',
'src/Node/Expression/FunctionExpression.php',
'src/Node/IncludeNode.php',
'src/Node/Node.php',
'src/Node/TypesNode.php',
'src/Parser.php',
'src/Test/IntegrationTestCase.php',
'src/Test/NodeTestCase.php',
] as $excludedPathname) {
if ($excludedPathname === $pathname || str_ends_with($pathname, '/'.$excludedPathname)) {
return false;
}
}
return true;
},
];
}
})
->setRiskyAllowed(true)
->setParallelConfig(ParallelConfigFactory::detect())
->setFinder((new Finder())->in(__DIR__))
;
@@ -34,7 +34,8 @@ if (!method_exists(ContainerBuilder::class, 'getAutoconfiguredAttributes')) {
} else {
class MissingExtensionSuggestorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
/** @return void */
public function process(ContainerBuilder $container)
{
if (!$container->getParameter('kernel.debug')) {
return;
@@ -31,7 +31,8 @@ if (!method_exists(ContainerBuilder::class, 'getAutoconfiguredAttributes')) {
/** @internal */
trait TwigExtraExtensionTrait
{
public function load(array $configs, ContainerBuilder $container): void
/** @return void */
public function load(array $configs, ContainerBuilder $container)
{
$this->doLoad($configs, $container);
}
+2 -1
View File
@@ -29,7 +29,8 @@ if (method_exists(KernelInterface::class, 'getShareDir')) {
} else {
class TwigExtraBundle extends Bundle
{
public function build(ContainerBuilder $container): void
/** @return void */
public function build(ContainerBuilder $container)
{
parent::build($container);
+75 -21
View File
@@ -154,8 +154,10 @@ class Environment
/**
* Enables debugging mode.
*
* @return void
*/
public function enableDebug(): void
public function enableDebug()
{
$this->debug = true;
$this->updateOptionsHash();
@@ -163,8 +165,10 @@ class Environment
/**
* Disables debugging mode.
*
* @return void
*/
public function disableDebug(): void
public function disableDebug()
{
$this->debug = false;
$this->updateOptionsHash();
@@ -182,16 +186,20 @@ class Environment
/**
* Enables the auto_reload option.
*
* @return void
*/
public function enableAutoReload(): void
public function enableAutoReload()
{
$this->autoReload = true;
}
/**
* Disables the auto_reload option.
*
* @return void
*/
public function disableAutoReload(): void
public function disableAutoReload()
{
$this->autoReload = false;
}
@@ -208,8 +216,10 @@ class Environment
/**
* Enables the strict_variables option.
*
* @return void
*/
public function enableStrictVariables(): void
public function enableStrictVariables()
{
$this->strictVariables = true;
$this->updateOptionsHash();
@@ -217,8 +227,10 @@ class Environment
/**
* Disables the strict_variables option.
*
* @return void
*/
public function disableStrictVariables(): void
public function disableStrictVariables()
{
$this->strictVariables = false;
$this->updateOptionsHash();
@@ -266,8 +278,10 @@ class Environment
* @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation,
* an absolute path to the compiled templates,
* or false to disable cache
*
* @return void
*/
public function setCache($cache): void
public function setCache($cache)
{
if (\is_string($cache)) {
$this->originalCache = $cache;
@@ -502,7 +516,10 @@ class Environment
throw new LoaderError(\sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
}
public function setLexer(Lexer $lexer): void
/**
* @return void
*/
public function setLexer(Lexer $lexer)
{
$this->lexer = $lexer;
}
@@ -519,7 +536,10 @@ class Environment
return $this->lexer->tokenize($source);
}
public function setParser(Parser $parser): void
/**
* @return void
*/
public function setParser(Parser $parser)
{
$this->parser = $parser;
}
@@ -538,7 +558,10 @@ class Environment
return $this->parser->parse($stream);
}
public function setCompiler(Compiler $compiler): void
/**
* @return void
*/
public function setCompiler(Compiler $compiler)
{
$this->compiler = $compiler;
}
@@ -572,7 +595,10 @@ class Environment
}
}
public function setLoader(LoaderInterface $loader): void
/**
* @return void
*/
public function setLoader(LoaderInterface $loader)
{
$this->loader = $loader;
}
@@ -582,7 +608,10 @@ class Environment
return $this->loader;
}
public function setCharset(string $charset): void
/**
* @return void
*/
public function setCharset(string $charset)
{
if ('UTF8' === $charset = strtoupper($charset ?: '')) {
// iconv on Windows requires "UTF-8" instead of "UTF8"
@@ -602,7 +631,10 @@ class Environment
return $this->extensionSet->hasExtension($class);
}
public function addRuntimeLoader(RuntimeLoaderInterface $loader): void
/**
* @return void
*/
public function addRuntimeLoader(RuntimeLoaderInterface $loader)
{
$this->runtimeLoaders[] = $loader;
}
@@ -649,7 +681,10 @@ class Environment
throw new RuntimeError(\sprintf('Unable to load the "%s" runtime.', $class));
}
public function addExtension(ExtensionInterface $extension): void
/**
* @return void
*/
public function addExtension(ExtensionInterface $extension)
{
$this->extensionSet->addExtension($extension);
$this->updateOptionsHash();
@@ -657,8 +692,10 @@ class Environment
/**
* @param ExtensionInterface[] $extensions An array of extensions
*
* @return void
*/
public function setExtensions(array $extensions): void
public function setExtensions(array $extensions)
{
$this->extensionSet->setExtensions($extensions);
$this->updateOptionsHash();
@@ -672,7 +709,10 @@ class Environment
return $this->extensionSet->getExtensions();
}
public function addTokenParser(TokenParserInterface $parser): void
/**
* @return void
*/
public function addTokenParser(TokenParserInterface $parser)
{
$this->extensionSet->addTokenParser($parser);
}
@@ -703,7 +743,10 @@ class Environment
$this->extensionSet->registerUndefinedTokenParserCallback($callable);
}
public function addNodeVisitor(NodeVisitorInterface $visitor): void
/**
* @return void
*/
public function addNodeVisitor(NodeVisitorInterface $visitor)
{
$this->extensionSet->addNodeVisitor($visitor);
}
@@ -718,7 +761,10 @@ class Environment
return $this->extensionSet->getNodeVisitors();
}
public function addFilter(TwigFilter $filter): void
/**
* @return void
*/
public function addFilter(TwigFilter $filter)
{
$this->extensionSet->addFilter($filter);
}
@@ -755,7 +801,10 @@ class Environment
return $this->extensionSet->getFilters();
}
public function addTest(TwigTest $test): void
/**
* @return void
*/
public function addTest(TwigTest $test)
{
$this->extensionSet->addTest($test);
}
@@ -786,7 +835,10 @@ class Environment
$this->extensionSet->registerUndefinedTestCallback($callable);
}
public function addFunction(TwigFunction $function): void
/**
* @return void
*/
public function addFunction(TwigFunction $function)
{
$this->extensionSet->addFunction($function);
}
@@ -830,8 +882,10 @@ class Environment
* but after, you can only update existing globals.
*
* @param mixed $value The global value
*
* @return void
*/
public function addGlobal(string $name, $value): void
public function addGlobal(string $name, $value)
{
if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) {
throw new \LogicException(\sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
+8 -2
View File
@@ -23,13 +23,19 @@ class ProfilerExtension extends AbstractExtension
$this->actives[] = $profile;
}
public function enter(Profile $profile): void
/**
* @return void
*/
public function enter(Profile $profile)
{
$this->actives[0]->addProfile($profile);
array_unshift($this->actives, $profile);
}
public function leave(Profile $profile): void
/**
* @return void
*/
public function leave(Profile $profile)
{
$profile->leave();
array_shift($this->actives);
+4 -1
View File
@@ -20,7 +20,10 @@ use Twig\Compiler;
#[YieldReady]
class CheckSecurityCallNode extends Node
{
public function compile(Compiler $compiler): void
/**
* @return void
*/
public function compile(Compiler $compiler)
{
$compiler
->write("\$this->sandbox = \$this->extensions[SandboxExtension::class]->getChecker();\n")
+4 -1
View File
@@ -26,7 +26,10 @@ abstract class CallExpression extends AbstractExpression
{
private $reflector;
protected function compileCallable(Compiler $compiler): void
/**
* @return void
*/
protected function compileCallable(Compiler $compiler)
{
$twigCallable = $this->getTwigCallable();
$callable = $twigCallable->getCallable();
+4 -1
View File
@@ -55,7 +55,10 @@ class FunctionExpression extends CallExpression implements SupportDefinedTestInt
}
}
public function compile(Compiler $compiler): void
/**
* @return void
*/
public function compile(Compiler $compiler)
{
$name = $this->getAttribute('name');
if ($this->hasAttribute('twig_callable')) {
+8 -2
View File
@@ -79,7 +79,10 @@ class IncludeNode extends Node implements NodeOutputInterface, CoercesChildrenTo
}
}
protected function addGetTemplate(Compiler $compiler/* , string $template = '' */): void
/**
* @return void
*/
protected function addGetTemplate(Compiler $compiler/* , string $template = '' */)
{
$compiler
->raw('$this->load(')
@@ -90,7 +93,10 @@ class IncludeNode extends Node implements NodeOutputInterface, CoercesChildrenTo
;
}
protected function addTemplateArguments(Compiler $compiler): void
/**
* @return void
*/
protected function addTemplateArguments(Compiler $compiler)
{
if (!$this->hasNode('variables')) {
$compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]');
+4 -1
View File
@@ -117,7 +117,10 @@ class Node implements \Countable, \IteratorAggregate
}
}
public function compile(Compiler $compiler): void
/**
* @return void
*/
public function compile(Compiler $compiler)
{
foreach ($this->nodes as $node) {
$compiler->subcompile($node);
+4 -1
View File
@@ -35,7 +35,10 @@ class TypesNode extends Node
parent::__construct($nodes, ['mapping' => $types], $lineno);
}
public function compile(Compiler $compiler): void
/**
* @return void
*/
public function compile(Compiler $compiler)
{
// Don't compile anything.
}
+4 -1
View File
@@ -335,7 +335,10 @@ class Parser
return \count($this->traits) > 0;
}
public function embedTemplate(ModuleNode $template): void
/**
* @return void
*/
public function embedTemplate(ModuleNode $template)
{
$template->setIndex(++$this->lastEmbedIndex);
+10 -3
View File
@@ -123,9 +123,11 @@ abstract class IntegrationTestCase extends TestCase
* The annotation feeds PHPUnit < 10; the attribute feeds PHPUnit >= 10 and must point to a static provider, as PHPUnit >= 11 rejects non-static ones.
*
* @dataProvider getTests
*
* @return void
*/
#[DataProvider('provideTests')]
public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = ''): void
public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '')
{
$this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation);
}
@@ -134,9 +136,11 @@ abstract class IntegrationTestCase extends TestCase
* @dataProvider getLegacyTests
*
* @group legacy
*
* @return void
*/
#[DataProvider('provideLegacyTests'), Group('legacy')]
public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = ''): void
public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '')
{
$this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation);
}
@@ -223,7 +227,10 @@ abstract class IntegrationTestCase extends TestCase
return $this->getTests('testLegacyIntegration', true);
}
protected function doIntegrationTest($file, $message, $condition, $templateSources, $exception, $outputs, $deprecation = ''): void
/**
* @return void
*/
protected function doIntegrationTest($file, $message, $condition, $templateSources, $exception, $outputs, $deprecation = '')
{
if (!$outputs) {
// dummy test added by assembleTests() when there is no (legacy) test to run
+7 -2
View File
@@ -49,14 +49,19 @@ abstract class NodeTestCase extends TestCase
*
* @dataProvider getTests
* @dataProvider provideTests
*
* @return void
*/
#[DataProvider('provideTests')]
public function testCompile($node, $source, $environment = null, $isPattern = false): void
public function testCompile($node, $source, $environment = null, $isPattern = false)
{
$this->assertNodeCompilation($source, $node, $environment, $isPattern);
}
public function assertNodeCompilation($source, Node $node, ?Environment $environment = null, $isPattern = false): void
/**
* @return void
*/
public function assertNodeCompilation($source, Node $node, ?Environment $environment = null, $isPattern = false)
{
$compiler = $this->getCompiler($environment);
$compiler->compile($node);