From d17c1a97343f40e195d433cfbfac23277b14d190 Mon Sep 17 00:00:00 2001 From: Ener-Getick Date: Sat, 5 Mar 2016 13:39:12 +0100 Subject: [PATCH] [DependencyInjection] Sort the CompilerPass by priority --- .../DependencyInjection/CHANGELOG.md | 5 + .../DependencyInjection/Compiler/Compiler.php | 16 ++- .../Compiler/PassConfig.php | 102 ++++++++++++------ .../DependencyInjection/ContainerBuilder.php | 16 ++- .../Tests/Compiler/PassConfigTest.php | 34 ++++++ .../Tests/ContainerBuilderTest.php | 11 +- 6 files changed, 138 insertions(+), 46 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Compiler/PassConfigTest.php diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 09395b70fe..c2f2b52b8e 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +3.2.0 +----- + + * allowed to prioritize compiler passes by introducing a third argument to `PassConfig::addPass()`, to `Compiler::addPass` and to `ContainerBuilder::addCompilerPass()` + 3.0.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php b/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php index 1f6304ee82..5d83a6a8f5 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php @@ -65,12 +65,20 @@ class Compiler /** * Adds a pass to the PassConfig. * - * @param CompilerPassInterface $pass A compiler pass - * @param string $type The type of the pass + * @param CompilerPassInterface $pass A compiler pass + * @param string $type The type of the pass + * @param int $priority Used to sort the passes */ - public function addPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION) + public function addPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/**, $priority = 0*/) { - $this->passConfig->addPass($pass, $type); + // For BC + if (func_num_args() >= 3) { + $priority = func_get_arg(2); + } else { + $priority = 0; + } + + $this->passConfig->addPass($pass, $type, $priority); } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index ffb8c01f0f..7eae023533 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -39,7 +39,7 @@ class PassConfig { $this->mergePass = new MergeExtensionConfigurationPass(); - $this->optimizationPasses = array( + $this->optimizationPasses = array(array( new ExtensionCompilerPass(), new ResolveDefinitionTemplatesPass(), new DecoratorServicePass(), @@ -51,9 +51,9 @@ class PassConfig new AnalyzeServiceReferencesPass(true), new CheckCircularReferencesPass(), new CheckReferenceValidityPass(), - ); + )); - $this->removingPasses = array( + $this->removingPasses = array(array( new RemovePrivateAliasesPass(), new ReplaceAliasByActualDefinitionPass(), new RemoveAbstractDefinitionsPass(), @@ -64,98 +64,111 @@ class PassConfig new RemoveUnusedDefinitionsPass(), )), new CheckExceptionOnInvalidReferenceBehaviorPass(), - ); + )); } /** * Returns all passes in order to be processed. * - * @return array An array of all passes to process + * @return CompilerPassInterface[] */ public function getPasses() { return array_merge( array($this->mergePass), - $this->beforeOptimizationPasses, - $this->optimizationPasses, - $this->beforeRemovingPasses, - $this->removingPasses, - $this->afterRemovingPasses + $this->getBeforeOptimizationPasses(), + $this->getOptimizationPasses(), + $this->getBeforeRemovingPasses(), + $this->getRemovingPasses(), + $this->getAfterRemovingPasses() ); } /** * Adds a pass. * - * @param CompilerPassInterface $pass A Compiler pass - * @param string $type The pass type + * @param CompilerPassInterface $pass A Compiler pass + * @param string $type The pass type + * @param int $priority Used to sort the passes * * @throws InvalidArgumentException when a pass type doesn't exist */ - public function addPass(CompilerPassInterface $pass, $type = self::TYPE_BEFORE_OPTIMIZATION) + public function addPass(CompilerPassInterface $pass, $type = self::TYPE_BEFORE_OPTIMIZATION/*, $priority = 0*/) { + // For BC + if (func_num_args() >= 3) { + $priority = func_get_arg(2); + } else { + $priority = 0; + } + $property = $type.'Passes'; if (!isset($this->$property)) { throw new InvalidArgumentException(sprintf('Invalid type "%s".', $type)); } - $this->{$property}[] = $pass; + $passes = &$this->$property; + + if (!isset($passes[$priority])) { + $passes[$priority] = array(); + } + $passes[$priority][] = $pass; } /** * Gets all passes for the AfterRemoving pass. * - * @return array An array of passes + * @return CompilerPassInterface[] */ public function getAfterRemovingPasses() { - return $this->afterRemovingPasses; + return $this->sortPasses($this->afterRemovingPasses); } /** * Gets all passes for the BeforeOptimization pass. * - * @return array An array of passes + * @return CompilerPassInterface[] */ public function getBeforeOptimizationPasses() { - return $this->beforeOptimizationPasses; + return $this->sortPasses($this->beforeOptimizationPasses); } /** * Gets all passes for the BeforeRemoving pass. * - * @return array An array of passes + * @return CompilerPassInterface[] */ public function getBeforeRemovingPasses() { - return $this->beforeRemovingPasses; + return $this->sortPasses($this->beforeRemovingPasses); } /** * Gets all passes for the Optimization pass. * - * @return array An array of passes + * @return CompilerPassInterface[] */ public function getOptimizationPasses() { - return $this->optimizationPasses; + return $this->sortPasses($this->optimizationPasses); } /** * Gets all passes for the Removing pass. * - * @return array An array of passes + * @return CompilerPassInterface[] */ public function getRemovingPasses() { - return $this->removingPasses; + return $this->sortPasses($this->removingPasses); } /** * Gets all passes for the Merge pass. * - * @return array An array of passes + * @return CompilerPassInterface */ public function getMergePass() { @@ -175,50 +188,69 @@ class PassConfig /** * Sets the AfterRemoving passes. * - * @param array $passes An array of passes + * @param CompilerPassInterface[] $passes */ public function setAfterRemovingPasses(array $passes) { - $this->afterRemovingPasses = $passes; + $this->afterRemovingPasses = array($passes); } /** * Sets the BeforeOptimization passes. * - * @param array $passes An array of passes + * @param CompilerPassInterface[] $passes */ public function setBeforeOptimizationPasses(array $passes) { - $this->beforeOptimizationPasses = $passes; + $this->beforeOptimizationPasses = array($passes); } /** * Sets the BeforeRemoving passes. * - * @param array $passes An array of passes + * @param CompilerPassInterface[] $passes */ public function setBeforeRemovingPasses(array $passes) { - $this->beforeRemovingPasses = $passes; + $this->beforeRemovingPasses = array($passes); } /** * Sets the Optimization passes. * - * @param array $passes An array of passes + * @param CompilerPassInterface[] $passes */ public function setOptimizationPasses(array $passes) { - $this->optimizationPasses = $passes; + $this->optimizationPasses = array($passes); } /** * Sets the Removing passes. * - * @param array $passes An array of passes + * @param CompilerPassInterface[] $passes */ public function setRemovingPasses(array $passes) { - $this->removingPasses = $passes; + $this->removingPasses = array($passes); + } + + /** + * Sort passes by priority. + * + * @param array $passes CompilerPassInterface instances with their priority as key. + * + * @return CompilerPassInterface[] + */ + private function sortPasses(array $passes) + { + if (0 === count($passes)) { + return array(); + } + + krsort($passes); + + // Flatten the array + return call_user_func_array('array_merge', $passes); } } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 7a9703432b..35426e5f95 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -298,14 +298,22 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Adds a compiler pass. * - * @param CompilerPassInterface $pass A compiler pass - * @param string $type The type of compiler pass + * @param CompilerPassInterface $pass A compiler pass + * @param string $type The type of compiler pass + * @param int $priority Used to sort the passes * * @return ContainerBuilder The current instance */ - public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION) + public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/**, $priority = 0*/) { - $this->getCompiler()->addPass($pass, $type); + // For BC + if (func_num_args() >= 3) { + $priority = func_get_arg(2); + } else { + $priority = 0; + } + + $this->getCompiler()->addPass($pass, $type, $priority); $this->addObjectResource($pass); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/PassConfigTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/PassConfigTest.php new file mode 100644 index 0000000000..90659f205e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/PassConfigTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * @author Guilhem N + */ +class PassConfigTest extends \PHPUnit_Framework_TestCase +{ + public function testPassOrdering() + { + $config = new PassConfig(); + + $pass1 = $this->getMock(CompilerPassInterface::class); + $config->addPass($pass1, PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); + + $pass2 = $this->getMock(CompilerPassInterface::class); + $config->addPass($pass2, PassConfig::TYPE_BEFORE_OPTIMIZATION, 30); + + $this->assertSame(array($pass2, $pass1), $config->getBeforeOptimizationPasses()); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 6c66c45c28..069a45d168 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -17,6 +17,7 @@ require_once __DIR__.'/Fixtures/includes/ProjectExtension.php'; use Symfony\Bridge\PhpUnit\ErrorAssert; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; @@ -284,10 +285,14 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase { $builder = new ContainerBuilder(); $builder->setResourceTracking(false); - $builderCompilerPasses = $builder->getCompiler()->getPassConfig()->getPasses(); - $builder->addCompilerPass($this->getMock('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface')); + $defaultPasses = $builder->getCompiler()->getPassConfig()->getPasses(); + $builder->addCompilerPass($pass1 = $this->getMock('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface'), PassConfig::TYPE_BEFORE_OPTIMIZATION, -5); + $builder->addCompilerPass($pass2 = $this->getMock('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface'), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); - $this->assertCount(count($builder->getCompiler()->getPassConfig()->getPasses()) - 1, $builderCompilerPasses); + $passes = $builder->getCompiler()->getPassConfig()->getPasses(); + $this->assertCount(count($passes) - 2, $defaultPasses); + // Pass 1 is executed later + $this->assertTrue(array_search($pass1, $passes, true) > array_search($pass2, $passes, true)); } public function testCreateService()