Optimize ReplaceAliasByActualDefinitionPass

Previous implementation passed over every alias and every definition, for every
alias (n*(n+m) runtime). New implementation passes once over all aliases and
once over all definitions (n+m).

Also removing needless "restart" logic --- it has no real effect in either case.
This commit is contained in:
Ariel J. Birnbaum 2016-03-22 14:30:07 +01:00
parent 56d3c264a9
commit ab8dc0c9d3
1 changed files with 64 additions and 71 deletions

View File

@ -25,7 +25,6 @@ class ReplaceAliasByActualDefinitionPass implements CompilerPassInterface
{ {
private $compiler; private $compiler;
private $formatter; private $formatter;
private $sourceId;
/** /**
* Process the Container to replace aliases with service definitions. * Process the Container to replace aliases with service definitions.
@ -36,105 +35,99 @@ class ReplaceAliasByActualDefinitionPass implements CompilerPassInterface
*/ */
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
// Setup
$this->compiler = $container->getCompiler(); $this->compiler = $container->getCompiler();
$this->formatter = $this->compiler->getLoggingFormatter(); $this->formatter = $this->compiler->getLoggingFormatter();
// First collect all alias targets that need to be replaced
foreach ($container->getAliases() as $id => $alias) { $seenAliasTargets = array();
$aliasId = (string) $alias; $replacements = array();
foreach ($container->getAliases() as $definitionId => $target) {
if ('service_container' === $aliasId) { $targetId = (string) $target;
// Special case: leave this target alone
if ('service_container' === $targetId) {
continue; continue;
} }
// Check if target needs to be replaces
try { if (isset($replacements[$targetId])) {
$definition = $container->getDefinition($aliasId); $container->setAlias($definitionId, $replacements[$targetId]);
} catch (InvalidArgumentException $e) { }
throw new InvalidArgumentException(sprintf('Unable to replace alias "%s" with actual definition "%s".', $id, $alias), null, $e); // No neeed to process the same target twice
if (isset($seenAliasTargets[$targetId])) {
continue;
}
// Process new target
$seenAliasTargets[$targetId] = true;
try {
$definition = $container->getDefinition($targetId);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('Unable to replace alias "%s" with actual definition "%s".', $definitionId, $targetId), null, $e);
} }
if ($definition->isPublic()) { if ($definition->isPublic()) {
continue; continue;
} }
// Remove private definition and schedule for replacement
$definition->setPublic(true); $definition->setPublic(true);
$container->setDefinition($id, $definition); $container->setDefinition($definitionId, $definition);
$container->removeDefinition($aliasId); $container->removeDefinition($targetId);
$replacements[$targetId] = $definitionId;
$this->updateReferences($container, $aliasId, $id); }
// Now replace target instances in all definitions
// we have to restart the process due to concurrent modification of foreach ($container->getDefinitions() as $definitionId => $definition) {
// the container $definition->setArguments($this->updateArgumentReferences($replacements, $definitionId, $definition->getArguments()));
$this->process($container); $definition->setMethodCalls($this->updateArgumentReferences($replacements, $definitionId, $definition->getMethodCalls()));
$definition->setProperties($this->updateArgumentReferences($replacements, $definitionId, $definition->getProperties()));
break; $definition->setFactoryService($this->updateFactoryReferenceId($replacements, $definition->getFactoryService()));
} }
} }
/** /**
* Updates references to remove aliases. * Recursively updates references in an array.
* *
* @param ContainerBuilder $container The container * @param array $replacements Table of aliases to replace
* @param string $currentId The alias identifier being replaced * @param string $definitionId Identifier of this definition
* @param string $newId The id of the service the alias points to * @param array $arguments Where to replace the aliases
*/
private function updateReferences($container, $currentId, $newId)
{
foreach ($container->getAliases() as $id => $alias) {
if ($currentId === (string) $alias) {
$container->setAlias($id, $newId);
}
}
foreach ($container->getDefinitions() as $id => $definition) {
$this->sourceId = $id;
$definition->setArguments(
$this->updateArgumentReferences($definition->getArguments(), $currentId, $newId)
);
$definition->setMethodCalls(
$this->updateArgumentReferences($definition->getMethodCalls(), $currentId, $newId)
);
$definition->setProperties(
$this->updateArgumentReferences($definition->getProperties(), $currentId, $newId)
);
$definition->setFactoryService($this->updateFactoryServiceReference($definition->getFactoryService(), $currentId, $newId));
}
}
/**
* Updates argument references.
*
* @param array $arguments An array of Arguments
* @param string $currentId The alias identifier
* @param string $newId The identifier the alias points to
* *
* @return array * @return array
*/ */
private function updateArgumentReferences(array $arguments, $currentId, $newId) private function updateArgumentReferences(array $replacements, $definitionId, array $arguments)
{ {
foreach ($arguments as $k => $argument) { foreach ($arguments as $k => $argument) {
// Handle recursion step
if (is_array($argument)) { if (is_array($argument)) {
$arguments[$k] = $this->updateArgumentReferences($argument, $currentId, $newId); $arguments[$k] = $this->updateArgumentReferences($replacements, $definitionId, $argument);
} elseif ($argument instanceof Reference) { continue;
if ($currentId === (string) $argument) {
$arguments[$k] = new Reference($newId, $argument->getInvalidBehavior());
$this->compiler->addLogMessage($this->formatter->formatUpdateReference($this, $this->sourceId, $currentId, $newId));
}
} }
// Skip arguments that don't need replacement
if (!$argument instanceof Reference) {
continue;
}
$referenceId = (string) $argument;
if (!isset($replacements[$referenceId])) {
continue;
}
// Perform the replacement
$newId = $replacements[$referenceId];
$arguments[$k] = new Reference($newId, $argument->getInvalidBehavior());
$this->compiler->addLogMessage($this->formatter->formatUpdateReference($this, $definitionId, $referenceId, $newId));
} }
return $arguments; return $arguments;
} }
private function updateFactoryServiceReference($factoryService, $currentId, $newId) /**
* Returns the updated reference for the factory service.
*
* @param array $replacements Table of aliases to replace
* @param string|null $referenceId Factory service reference identifier
*
* @return string|null
*/
private function updateFactoryReferenceId(array $replacements, $referenceId)
{ {
if (null === $factoryService) { if (null === $referenceId) {
return; return;
} }
return $currentId === $factoryService ? $newId : $factoryService; return isset($replacements[$referenceId]) ? $replacements[$referenceId] : $referenceId;
} }
} }