diff --git a/src/Symfony/Component/DependencyInjection/ChildDefinition.php b/src/Symfony/Component/DependencyInjection/ChildDefinition.php index bafcd3b7b87bf..b75124f61977f 100644 --- a/src/Symfony/Component/DependencyInjection/ChildDefinition.php +++ b/src/Symfony/Component/DependencyInjection/ChildDefinition.php @@ -23,15 +23,12 @@ class ChildDefinition extends Definition { private $parent; private $inheritTags = false; - private $changes = array(); /** * @param string $parent The id of Definition instance to decorate */ public function __construct($parent) { - parent::__construct(); - $this->parent = $parent; } @@ -46,13 +43,17 @@ public function getParent() } /** - * Returns all changes tracked for the Definition object. + * Sets the Definition being decorated. + * + * @param string $parent * - * @return array An array of changes for this Definition + * @return $this */ - public function getChanges() + public function setParent($parent) { - return $this->changes; + $this->parent = $parent; + + return $this; } /** @@ -79,116 +80,6 @@ public function getInheritTags() return $this->inheritTags; } - /** - * {@inheritdoc} - */ - public function setClass($class) - { - $this->changes['class'] = true; - - return parent::setClass($class); - } - - /** - * {@inheritdoc} - */ - public function setFactory($callable) - { - $this->changes['factory'] = true; - - return parent::setFactory($callable); - } - - /** - * {@inheritdoc} - */ - public function setConfigurator($callable) - { - $this->changes['configurator'] = true; - - return parent::setConfigurator($callable); - } - - /** - * {@inheritdoc} - */ - public function setFile($file) - { - $this->changes['file'] = true; - - return parent::setFile($file); - } - - /** - * {@inheritdoc} - */ - public function setShared($boolean) - { - $this->changes['shared'] = true; - - return parent::setShared($boolean); - } - - /** - * {@inheritdoc} - */ - public function setPublic($boolean) - { - $this->changes['public'] = true; - - return parent::setPublic($boolean); - } - - /** - * {@inheritdoc} - */ - public function setLazy($boolean) - { - $this->changes['lazy'] = true; - - return parent::setLazy($boolean); - } - - /** - * {@inheritdoc} - */ - public function setAbstract($boolean) - { - $this->changes['abstract'] = true; - - return parent::setAbstract($boolean); - } - - /** - * {@inheritdoc} - */ - public function setDecoratedService($id, $renamedId = null, $priority = 0) - { - $this->changes['decorated_service'] = true; - - return parent::setDecoratedService($id, $renamedId, $priority); - } - - /** - * {@inheritdoc} - */ - public function setDeprecated($boolean = true, $template = null) - { - $this->changes['deprecated'] = true; - - return parent::setDeprecated($boolean, $template); - } - - /** - * {@inheritdoc} - */ - public function setAutowired($autowired) - { - $this->changes['autowired'] = true; - - return parent::setAutowired($autowired); - } - /** * Gets an argument to pass to the service constructor/factory method. * diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index 16d0d741ae086..8ded4ba6d1cdf 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -65,11 +65,12 @@ protected function processValue($value, $isRoot = false) $value->setProperties($this->processValue($value->getProperties())); $value->setMethodCalls($this->processValue($value->getMethodCalls())); - if ($v = $value->getFactory()) { - $value->setFactory($this->processValue($v)); + $changes = $value->getChanges(); + if (isset($changes['factory'])) { + $value->setFactory($this->processValue($value->getFactory())); } - if ($v = $value->getConfigurator()) { - $value->setConfigurator($this->processValue($v)); + if (isset($changes['configurator'])) { + $value->setConfigurator($this->processValue($value->getConfigurator())); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 47402b8ad8a90..b05012a3f12b2 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -42,7 +42,8 @@ public function __construct() $this->beforeOptimizationPasses = array( 100 => array( $resolveClassPass = new ResolveClassPass(), - new ResolveDefinitionInheritancePass(), + new ResolveInstanceofConditionalsPass(), + new ResolveTagsInheritancePass(), ), ); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionInheritancePass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionInheritancePass.php deleted file mode 100644 index f06b19e33b44f..0000000000000 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionInheritancePass.php +++ /dev/null @@ -1,99 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\ChildDefinition; -use Symfony\Component\DependencyInjection\Definition; - -/** - * Applies tags and instanceof inheritance to definitions. - * - * @author Nicolas Grekas - */ -class ResolveDefinitionInheritancePass extends AbstractRecursivePass -{ - protected function processValue($value, $isRoot = false) - { - if (!$value instanceof Definition) { - return parent::processValue($value, $isRoot); - } - - $class = $value instanceof ChildDefinition ? $this->resolveDefinition($value) : $value->getClass(); - - if (!$class || false !== strpos($class, '%') || !$instanceof = $value->getInstanceofConditionals()) { - return parent::processValue($value, $isRoot); - } - $value->setInstanceofConditionals(array()); - - foreach ($instanceof as $interface => $definition) { - if ($interface !== $class && (!$this->container->getReflectionClass($interface) || !$this->container->getReflectionClass($class))) { - continue; - } - if ($interface === $class || is_subclass_of($class, $interface)) { - $this->mergeDefinition($value, $definition); - } - } - - return parent::processValue($value, $isRoot); - } - - /** - * Populates the class and tags from parent definitions. - */ - private function resolveDefinition(ChildDefinition $definition) - { - if (!$this->container->has($parent = $definition->getParent())) { - return; - } - - $parentDef = $this->container->findDefinition($parent); - $class = $parentDef instanceof ChildDefinition ? $this->resolveDefinition($parentDef) : $parentDef->getClass(); - $class = $definition->getClass() ?: $class; - - // append parent tags when inheriting is enabled - if ($definition->getInheritTags()) { - $definition->setInheritTags(false); - - foreach ($parentDef->getTags() as $k => $v) { - foreach ($v as $v) { - $definition->addTag($k, $v); - } - } - } - - return $class; - } - - private function mergeDefinition(Definition $def, ChildDefinition $definition) - { - $changes = $definition->getChanges(); - if (isset($changes['shared'])) { - $def->setShared($definition->isShared()); - } - if (isset($changes['abstract'])) { - $def->setAbstract($definition->isAbstract()); - } - - ResolveDefinitionTemplatesPass::mergeDefinition($def, $definition); - - // prepend instanceof tags - $tailTags = $def->getTags(); - if ($headTags = $definition->getTags()) { - $def->setTags($headTags); - foreach ($tailTags as $k => $v) { - foreach ($v as $v) { - $def->addTag($k, $v); - } - } - } - } -} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php index 7d9f7da4b8452..1b4d7b7da1163 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php @@ -84,7 +84,7 @@ private function doResolveDefinition(ChildDefinition $definition) $def = new Definition(); // merge in parent definition - // purposely ignored attributes: abstract, tags + // purposely ignored attributes: abstract, shared, tags $def->setClass($parentDef->getClass()); $def->setArguments($parentDef->getArguments()); $def->setMethodCalls($parentDef->getMethodCalls()); @@ -101,27 +101,8 @@ private function doResolveDefinition(ChildDefinition $definition) $def->setPublic($parentDef->isPublic()); $def->setLazy($parentDef->isLazy()); $def->setAutowired($parentDef->isAutowired()); + $def->setChanges($parentDef->getChanges()); - self::mergeDefinition($def, $definition); - - // merge autowiring types - foreach ($definition->getAutowiringTypes(false) as $autowiringType) { - $def->addAutowiringType($autowiringType); - } - - // these attributes are always taken from the child - $def->setAbstract($definition->isAbstract()); - $def->setShared($definition->isShared()); - $def->setTags($definition->getTags()); - - return $def; - } - - /** - * @internal - */ - public static function mergeDefinition(Definition $def, ChildDefinition $definition) - { // overwrite with values specified in the decorator $changes = $definition->getChanges(); if (isset($changes['class'])) { @@ -148,6 +129,9 @@ public static function mergeDefinition(Definition $def, ChildDefinition $definit if (isset($changes['autowired'])) { $def->setAutowired($definition->isAutowired()); } + if (isset($changes['shared'])) { + $def->setShared($definition->isShared()); + } if (isset($changes['decorated_service'])) { $decoratedService = $definition->getDecoratedService(); if (null === $decoratedService) { @@ -182,5 +166,16 @@ public static function mergeDefinition(Definition $def, ChildDefinition $definit if ($calls = $definition->getMethodCalls()) { $def->setMethodCalls(array_merge($def->getMethodCalls(), $calls)); } + + // merge autowiring types + foreach ($definition->getAutowiringTypes(false) as $autowiringType) { + $def->addAutowiringType($autowiringType); + } + + // these attributes are always taken from the child + $def->setAbstract($definition->isAbstract()); + $def->setTags($definition->getTags()); + + return $def; } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php new file mode 100644 index 0000000000000..940d113c0b9b4 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInstanceofConditionalsPass.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Applies instanceof conditionals to definitions. + * + * @author Nicolas Grekas + */ +class ResolveInstanceofConditionalsPass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $didProcess = false; + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition instanceof ChildDefinition) { + // don't apply "instanceof" to children: it will be applied to their parent + continue; + } + if ($definition !== $processedDefinition = $this->processDefinition($container, $id, $definition)) { + $didProcess = true; + $container->setDefinition($id, $processedDefinition); + } + } + if ($didProcess) { + $container->register('abstract.'.__CLASS__, '')->setAbstract(true); + } + } + + private function processDefinition(ContainerBuilder $container, $id, Definition $definition) + { + if (!$instanceofConditionals = $definition->getInstanceofConditionals()) { + return $definition; + } + if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) { + return $definition; + } + + $definition->setInstanceofConditionals(array()); + $instanceofParent = null; + $parent = 'abstract.'.__CLASS__; + $shared = null; + + foreach ($instanceofConditionals as $interface => $instanceofDef) { + if ($interface !== $class && (!$container->getReflectionClass($interface) || !$container->getReflectionClass($class))) { + continue; + } + if ($interface === $class || is_subclass_of($class, $interface)) { + $instanceofParent = clone $instanceofDef; + $instanceofParent->setAbstract(true)->setInheritTags(true)->setParent($parent); + $parent = 'instanceof.'.$interface.'.'.$id; + $container->setDefinition($parent, $instanceofParent); + + if (isset($instanceofParent->getChanges()['shared'])) { + $shared = $instanceofParent->isShared(); + } + } + } + + if ($instanceofParent) { + // cast Definition to ChildDefinition + $definition = serialize($definition); + $definition = substr_replace($definition, '53', 2, 2); + $definition = substr_replace($definition, 'Child', 44, 0); + $definition = unserialize($definition); + $definition->setInheritTags(true)->setParent($parent); + + if (null !== $shared && !isset($definition->getChanges()['shared'])) { + $definition->setShared($shared); + } + } + + return $definition; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php index d1d6786145bad..2444e441ee53e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php @@ -58,8 +58,13 @@ protected function processValue($value, $isRoot = false) return $this->bag->resolveValue($value); } if ($value instanceof Definition) { - $value->setClass($this->bag->resolveValue($value->getClass())); - $value->setFile($this->bag->resolveValue($value->getFile())); + $changes = $value->getChanges(); + if (isset($changes['class'])) { + $value->setClass($this->bag->resolveValue($value->getClass())); + } + if (isset($changes['file'])) { + $value->setFile($this->bag->resolveValue($value->getFile())); + } $value->setProperties($this->bag->resolveValue($value->getProperties())); $value->setMethodCalls($this->bag->resolveValue($value->getMethodCalls())); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php index 94671b80eb42f..6e79faba43f04 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php @@ -43,7 +43,9 @@ public function process(ContainerBuilder $container) $definition->setArguments($this->processArguments($definition->getArguments())); $definition->setMethodCalls($this->processArguments($definition->getMethodCalls())); $definition->setProperties($this->processArguments($definition->getProperties())); - $definition->setFactory($this->processFactory($definition->getFactory())); + if (isset($definition->getChanges()['factory'])) { + $definition->setFactory($this->processFactory($definition->getFactory())); + } } foreach ($container->getAliases() as $id => $alias) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveTagsInheritancePass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveTagsInheritancePass.php new file mode 100644 index 0000000000000..dbb0aab261650 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveTagsInheritancePass.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * Applies tags inheritance to definitions. + * + * @author Nicolas Grekas + */ +class ResolveTagsInheritancePass extends AbstractRecursivePass +{ + /** + * {@inheritdoc} + */ + protected function processValue($value, $isRoot = false) + { + if (!$value instanceof ChildDefinition || !$value->getInheritTags()) { + return parent::processValue($value, $isRoot); + } + $value->setInheritTags(false); + + if (!$this->container->has($parent = $value->getParent())) { + throw new RuntimeException(sprintf('Parent definition "%s" does not exist.', $parent)); + } + + $parentDef = $this->container->findDefinition($parent); + + if ($parentDef instanceof ChildDefinition) { + $this->processValue($parentDef); + } + + foreach ($parentDef->getTags() as $k => $v) { + foreach ($v as $v) { + $value->addTag($k, $v); + } + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index e3c79c511aa7a..74922ea423ccf 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -39,8 +39,9 @@ class Definition private $decoratedService; private $autowired = false; private $autowiringTypes = array(); + private $changes = array(); - protected $arguments; + protected $arguments = array(); /** * @param string|null $class The service class @@ -48,10 +49,34 @@ class Definition */ public function __construct($class = null, array $arguments = array()) { - $this->class = $class; + if (null !== $class) { + $this->setClass($class); + } $this->arguments = $arguments; } + /** + * Returns all changes tracked for the Definition object. + * + * @return array An array of changes for this Definition + */ + public function getChanges() + { + return $this->changes; + } + + /** + * Sets the tracked changes for the Definition object. + * + * @return $this + */ + public function setChanges(array $changes) + { + $this->changes = $changes; + + return $this; + } + /** * Sets a factory. * @@ -61,6 +86,8 @@ public function __construct($class = null, array $arguments = array()) */ public function setFactory($factory) { + $this->changes['factory'] = true; + if (is_string($factory) && strpos($factory, '::') !== false) { $factory = explode('::', $factory, 2); } @@ -97,6 +124,8 @@ public function setDecoratedService($id, $renamedId = null, $priority = 0) throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id)); } + $this->changes['decorated_service'] = true; + if (null === $id) { $this->decoratedService = null; } else { @@ -125,6 +154,8 @@ public function getDecoratedService() */ public function setClass($class) { + $this->changes['class'] = true; + $this->class = $class; return $this; @@ -448,6 +479,8 @@ public function clearTags() */ public function setFile($file) { + $this->changes['file'] = true; + $this->file = $file; return $this; @@ -472,6 +505,8 @@ public function getFile() */ public function setShared($shared) { + $this->changes['shared'] = true; + $this->shared = (bool) $shared; return $this; @@ -496,6 +531,8 @@ public function isShared() */ public function setPublic($boolean) { + $this->changes['public'] = true; + $this->public = (bool) $boolean; return $this; @@ -520,6 +557,8 @@ public function isPublic() */ public function setLazy($lazy) { + $this->changes['lazy'] = true; + $this->lazy = (bool) $lazy; return $this; @@ -612,6 +651,8 @@ public function setDeprecated($status = true, $template = null) $this->deprecationTemplate = $template; } + $this->changes['deprecated'] = true; + $this->deprecated = (bool) $status; return $this; @@ -649,6 +690,8 @@ public function getDeprecationMessage($id) */ public function setConfigurator($configurator) { + $this->changes['configurator'] = true; + if (is_string($configurator) && strpos($configurator, '::') !== false) { $configurator = explode('::', $configurator, 2); } @@ -709,6 +752,8 @@ public function isAutowired() */ public function setAutowired($autowired) { + $this->changes['autowired'] = true; + $this->autowired = (bool) $autowired; return $this; diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 798b6b1adcde4..8ea27b4e1677d 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -222,12 +222,19 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults = $defaults = array(); } else { $definition = new Definition(); + + if (isset($defaults['public'])) { + $definition->setPublic($defaults['public']); + } + if (isset($defaults['autowire'])) { + $definition->setAutowired($defaults['autowire']); + } + + $definition->setChanges(array()); } if ($publicAttr = $service->getAttribute('public')) { $definition->setPublic(XmlUtils::phpize($publicAttr)); - } elseif (isset($defaults['public'])) { - $definition->setPublic($defaults['public']); } foreach (array('class', 'shared', 'synthetic', 'lazy', 'abstract') as $key) { @@ -239,8 +246,6 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults = if ($value = $service->getAttribute('autowire')) { $definition->setAutowired(XmlUtils::phpize($value)); - } elseif (isset($defaults['autowire'])) { - $definition->setAutowired($defaults['autowire']); } if ($files = $this->getChildren($service, 'file')) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 7c7550e6c982b..52e91883eb3c8 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -81,10 +81,6 @@ class YamlFileLoader extends FileLoader 'shared' => 'shared', 'lazy' => 'lazy', 'public' => 'public', - 'abstract' => 'abstract', - 'deprecated' => 'deprecated', - 'factory' => 'factory', - 'arguments' => 'arguments', 'properties' => 'properties', 'configurator' => 'configurator', 'calls' => 'calls', @@ -366,6 +362,15 @@ private function parseDefinition($id, $service, $file, array $defaults) $defaults = array(); } else { $definition = new Definition(); + + if (isset($defaults['public'])) { + $definition->setPublic($defaults['public']); + } + if (isset($defaults['autowire'])) { + $definition->setAutowired($defaults['autowire']); + } + + $definition->setChanges(array()); } if (isset($service['class'])) { @@ -384,9 +389,8 @@ private function parseDefinition($id, $service, $file, array $defaults) $definition->setLazy($service['lazy']); } - $public = isset($service['public']) ? $service['public'] : (isset($defaults['public']) ? $defaults['public'] : null); - if (null !== $public) { - $definition->setPublic($public); + if (isset($service['public'])) { + $definition->setPublic($service['public']); } if (isset($service['abstract'])) { @@ -484,9 +488,8 @@ private function parseDefinition($id, $service, $file, array $defaults) $definition->setDecoratedService($service['decorates'], $renameId, $priority); } - $autowire = isset($service['autowire']) ? $service['autowire'] : (isset($defaults['autowire']) ? $defaults['autowire'] : null); - if (null !== $autowire) { - $definition->setAutowired($autowire); + if (isset($service['autowire'])) { + $definition->setAutowired($service['autowire']); } if (isset($service['autowiring_types'])) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index ebbcf00ef487a..79a9ad4637cf6 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -136,10 +136,7 @@ - - - @@ -148,7 +145,6 @@ - diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index db33d4b4a14dc..5598900680eeb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -12,7 +12,9 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -114,4 +116,79 @@ public function testProcessInlinesWhenThereAreMultipleReferencesButFromTheSameDe $this->assertFalse($container->hasDefinition('b')); $this->assertFalse($container->hasDefinition('c'), 'Service C was not inlined.'); } + + public function testInstanceofDefaultsAndParentDefinitionResolution() + { + $container = new ContainerBuilder(); + $container->setResourceTracking(false); + + // loading YAML with an expressive test-case in that file + $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Fixtures/yaml')); + $loader->load('services_defaults_instanceof_parent.yml'); + $container->compile(); + + // instanceof overrides defaults + $simpleService = $container->getDefinition('service_simple'); + $this->assertFalse($simpleService->isAutowired()); + $this->assertFalse($simpleService->isShared()); + + // all tags are kept + $this->assertEquals( + array( + 'foo_tag' => array(array('priority' => 100), array()), + 'bar_tag' => array(array()), + ), + $simpleService->getTags() + ); + + // calls are all kept, but service-level calls are last + $this->assertEquals( + array( + // from instanceof + array('setSunshine', array('bright')), + // from service + array('enableSummer', array(true)), + array('setSunshine', array('warm')), + ), + $simpleService->getMethodCalls() + ); + + // service override instanceof + $overrideService = $container->getDefinition('service_override_instanceof'); + $this->assertTrue($overrideService->isAutowired()); + + // children definitions get no instanceof + $childDef = $container->getDefinition('child_service'); + $this->assertEmpty($childDef->getTags()); + + $childDef2 = $container->getDefinition('child_service_with_parent_instanceof'); + // taken from instanceof applied to parent + $this->assertFalse($childDef2->isAutowired()); + // override the instanceof + $this->assertTrue($childDef2->isShared()); + // tags inherit like normal + $this->assertEquals( + array( + 'foo_tag' => array(array('priority' => 100), array()), + 'bar_tag' => array(array()), + ), + $simpleService->getTags() + ); + } +} + +class IntegrationTestStub extends IntegrationTestStubParent +{ +} + +class IntegrationTestStubParent +{ + public function enableSummer($enable) + { + // methods used in calls - added here to prevent errors for not existing + } + + public function setSunshine($type) + { + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionInheritancePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionInheritancePassTest.php deleted file mode 100644 index f521f96002fd7..0000000000000 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionInheritancePassTest.php +++ /dev/null @@ -1,170 +0,0 @@ - - * - * 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 PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\ChildDefinition; -use Symfony\Component\DependencyInjection\Compiler\ResolveDefinitionInheritancePass; -use Symfony\Component\DependencyInjection\ContainerBuilder; - -class ResolveDefinitionInheritancePassTest extends TestCase -{ - public function testProcess() - { - $container = new ContainerBuilder(); - $def = $container->register('parent', self::class)->setArguments(array('moo', 'b'))->setProperty('foo', 'moo'); - $def->setInstanceofConditionals(array( - parent::class => (new ChildDefinition('')) - ->replaceArgument(0, 'a') - ->setProperty('foo', 'bar') - ->setClass('bar'), - )); - - $this->process($container); - - $this->assertEmpty($def->getInstanceofConditionals()); - $this->assertSame($def, $container->getDefinition('parent')); - $this->assertEquals('bar', $def->getClass()); - $this->assertEquals(array('a', 'b'), $def->getArguments()); - $this->assertEquals(array('foo' => 'bar'), $def->getProperties()); - } - - public function testProcessAppendsMethodCallsAlways() - { - $container = new ContainerBuilder(); - - $def = $container - ->register('parent', self::class) - ->addMethodCall('foo', array('bar')); - - $def->setInstanceofConditionals(array( - parent::class => (new ChildDefinition('')) - ->addMethodCall('bar', array('foo')), - )); - - $this->process($container); - - $this->assertEquals(array( - array('foo', array('bar')), - array('bar', array('foo')), - ), $container->getDefinition('parent')->getMethodCalls()); - } - - public function testProcessDoesReplaceAbstract() - { - $container = new ContainerBuilder(); - - $def = $container->register('parent', 'stdClass'); - - $def->setInstanceofConditionals(array( - 'stdClass' => (new ChildDefinition(''))->setAbstract(true), - )); - - $this->process($container); - - $this->assertTrue($def->isAbstract()); - } - - public function testProcessDoesReplaceShared() - { - $container = new ContainerBuilder(); - - $def = $container->register('parent', 'stdClass'); - - $def->setInstanceofConditionals(array( - 'stdClass' => (new ChildDefinition(''))->setShared(false), - )); - - $this->process($container); - - $this->assertFalse($def->isShared()); - } - - public function testProcessHandlesMultipleInheritance() - { - $container = new ContainerBuilder(); - - $def = $container - ->register('parent', self::class) - ->setArguments(array('foo', 'bar', 'c')) - ; - - $def->setInstanceofConditionals(array( - parent::class => (new ChildDefinition(''))->replaceArgument(1, 'b'), - self::class => (new ChildDefinition(''))->replaceArgument(0, 'a'), - )); - - $this->process($container); - - $this->assertEquals(array('a', 'b', 'c'), $def->getArguments()); - } - - public function testSetLazyOnServiceHasParent() - { - $container = new ContainerBuilder(); - - $def = $container->register('parent', 'stdClass'); - - $def->setInstanceofConditionals(array( - 'stdClass' => (new ChildDefinition(''))->setLazy(true), - )); - - $this->process($container); - - $this->assertTrue($container->getDefinition('parent')->isLazy()); - } - - public function testProcessInheritTags() - { - $container = new ContainerBuilder(); - - $container->register('parent', self::class)->addTag('parent'); - - $def = $container->setDefinition('child', new ChildDefinition('parent')) - ->addTag('child') - ->setInheritTags(true) - ; - - $def->setInstanceofConditionals(array( - parent::class => (new ChildDefinition(''))->addTag('foo'), - )); - - $this->process($container); - - $t = array(array()); - $this->assertSame(array('foo' => $t, 'child' => $t, 'parent' => $t), $def->getTags()); - } - - public function testProcessResolvesAliasesAndTags() - { - $container = new ContainerBuilder(); - - $container->register('parent', self::class); - $container->setAlias('parent_alias', 'parent'); - $def = $container->setDefinition('child', new ChildDefinition('parent_alias')); - $def->setInstanceofConditionals(array( - parent::class => (new ChildDefinition(''))->addTag('foo'), - )); - - $this->process($container); - - $this->assertSame(array('foo' => array(array())), $def->getTags()); - $this->assertSame($def, $container->getDefinition('child')); - $this->assertEmpty($def->getClass()); - } - - protected function process(ContainerBuilder $container) - { - $pass = new ResolveDefinitionInheritancePass(); - $pass->process($container); - } -} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php new file mode 100644 index 0000000000000..af27ab28c118e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php @@ -0,0 +1,104 @@ + + * + * 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 PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass; +use Symfony\Component\DependencyInjection\Compiler\ResolveDefinitionTemplatesPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class ResolveInstanceofConditionalsPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $def = $container->register('foo', self::class); + $def->setInstanceofConditionals(array( + parent::class => (new ChildDefinition(''))->setProperty('foo', 'bar'), + )); + + (new ResolveInstanceofConditionalsPass())->process($container); + + $parent = 'instanceof.'.parent::class.'.foo'; + $def = $container->getDefinition('foo'); + $this->assertEmpty($def->getInstanceofConditionals()); + $this->assertInstanceof(ChildDefinition::class, $def); + $this->assertTrue($def->getInheritTags()); + $this->assertSame($parent, $def->getParent()); + $this->assertEquals(array('foo' => 'bar'), $container->getDefinition($parent)->getProperties()); + } + + public function testProcessInheritance() + { + $container = new ContainerBuilder(); + + $def = $container + ->register('parent', parent::class) + ->addMethodCall('foo', array('foo')); + $def->setInstanceofConditionals(array( + parent::class => (new ChildDefinition(''))->addMethodCall('foo', array('bar')), + )); + + $def = (new ChildDefinition('parent'))->setClass(self::class); + $def->setInstanceofConditionals(array( + parent::class => (new ChildDefinition(''))->addMethodCall('foo', array('baz')), + )); + $container->setDefinition('child', $def); + + (new ResolveInstanceofConditionalsPass())->process($container); + (new ResolveDefinitionTemplatesPass())->process($container); + + $expected = array( + array('foo', array('bar')), + array('foo', array('foo')), + ); + + $this->assertSame($expected, $container->getDefinition('parent')->getMethodCalls()); + $this->assertSame($expected, $container->getDefinition('child')->getMethodCalls()); + } + + public function testProcessDoesReplaceShared() + { + $container = new ContainerBuilder(); + + $def = $container->register('foo', 'stdClass'); + $def->setInstanceofConditionals(array( + 'stdClass' => (new ChildDefinition(''))->setShared(false), + )); + + (new ResolveInstanceofConditionalsPass())->process($container); + + $def = $container->getDefinition('foo'); + $this->assertFalse($def->isShared()); + } + + public function testProcessHandlesMultipleInheritance() + { + $container = new ContainerBuilder(); + + $def = $container->register('foo', self::class)->setShared(true); + + $def->setInstanceofConditionals(array( + parent::class => (new ChildDefinition(''))->setLazy(true)->setShared(false), + self::class => (new ChildDefinition(''))->setAutowired(true), + )); + + (new ResolveInstanceofConditionalsPass())->process($container); + (new ResolveDefinitionTemplatesPass())->process($container); + + $def = $container->getDefinition('foo'); + $this->assertTrue($def->isAutowired()); + $this->assertTrue($def->isLazy()); + $this->assertTrue($def->isShared()); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTagsInheritancePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTagsInheritancePassTest.php new file mode 100644 index 0000000000000..532bc860302a0 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveTagsInheritancePassTest.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 PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\ResolveTagsInheritancePass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class ResolveTagsInheritancePassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('grandpa', self::class)->addTag('g'); + $container->setDefinition('parent', new ChildDefinition('grandpa'))->addTag('p')->setInheritTags(true); + $container->setDefinition('child', new ChildDefinition('parent'))->setInheritTags(true); + + (new ResolveTagsInheritancePass())->process($container); + + $expected = array('p' => array(array()), 'g' => array(array())); + $this->assertSame($expected, $container->getDefinition('parent')->getTags()); + $this->assertSame($expected, $container->getDefinition('child')->getTags()); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php index 1acc7a3333631..797c8cc0feb13 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php @@ -20,6 +20,7 @@ public function testConstructor() { $def = new Definition('stdClass'); $this->assertEquals('stdClass', $def->getClass(), '__construct() takes the class name as its first argument'); + $this->assertSame(array('class' => true), $def->getChanges()); $def = new Definition('stdClass', array('foo')); $this->assertEquals(array('foo'), $def->getArguments(), '__construct() takes an optional array of arguments as its second argument'); @@ -27,13 +28,14 @@ public function testConstructor() public function testSetGetFactory() { - $def = new Definition('stdClass'); + $def = new Definition(); $this->assertSame($def, $def->setFactory('foo'), '->setFactory() implements a fluent interface'); $this->assertEquals('foo', $def->getFactory(), '->getFactory() returns the factory'); $def->setFactory('Foo::bar'); $this->assertEquals(array('Foo', 'bar'), $def->getFactory(), '->setFactory() converts string static method call to the array'); + $this->assertSame(array('factory' => true), $def->getChanges()); } public function testSetGetClass() @@ -315,6 +317,51 @@ public function testAutowired() $this->assertFalse($def->isAutowired()); } + public function testChangesNoChanges() + { + $def = new Definition(); + + $this->assertSame(array(), $def->getChanges()); + } + + public function testGetChangesWithChanges() + { + $def = new Definition('stdClass', array('fooarg')); + + $def->setAbstract(true); + $def->setAutowired(true); + $def->setConfigurator('configuration_func'); + $def->setDecoratedService(null); + $def->setDeprecated(true); + $def->setFactory('factory_func'); + $def->setFile('foo.php'); + $def->setLazy(true); + $def->setPublic(true); + $def->setShared(true); + $def->setSynthetic(true); + // changes aren't tracked for these, class or arguments + $def->setInstanceofConditionals(array()); + $def->addTag('foo_tag'); + $def->addMethodCall('methodCall'); + $def->setProperty('fooprop', true); + + $this->assertSame(array( + 'class' => true, + 'autowired' => true, + 'configurator' => true, + 'decorated_service' => true, + 'deprecated' => true, + 'factory' => true, + 'file' => true, + 'lazy' => true, + 'public' => true, + 'shared' => true, + ), $def->getChanges()); + + $def->setChanges(array()); + $this->assertSame(array(), $def->getChanges()); + } + /** * @group legacy */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/anonymous_services_in_instanceof.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/anonymous_services_in_instanceof.yml index a45a73b993349..ea0bebaf12433 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/anonymous_services_in_instanceof.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/anonymous_services_in_instanceof.yml @@ -5,7 +5,8 @@ services: autowire: true DummyInterface: - arguments: [ !service { class: Anonymous } ] + properties: + foo: !service { class: Anonymous } # Ensure next conditionals are not considered as services Bar: diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_defaults_instanceof_parent.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_defaults_instanceof_parent.yml new file mode 100644 index 0000000000000..7ff543d30deb6 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_defaults_instanceof_parent.yml @@ -0,0 +1,49 @@ +services: + _defaults: + autowire: true + + _instanceof: + Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStubParent: + # should override _defaults + autowire: false + shared: false + tags: + - { name: foo_tag } + calls: + - [setSunshine, [bright]] + + # a second instanceof that will be applied + Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub: + tags: + - { name: bar_tag } + + service_simple: + class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub + tags: + - { name: foo_tag, priority: 100 } + # calls from instanceof are kept, but this comes later + calls: + - [enableSummer, [true]] + - [setSunshine, [warm]] + + service_override_instanceof: + class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub + # override instanceof + autowire: true + + parent_service: + abstract: true + lazy: true + + # instanceof will not be applied to this + child_service: + class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub + parent: parent_service + + parent_service_with_class: + abstract: true + class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub + + child_service_with_parent_instanceof: + parent: parent_service_with_class + shared: true diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 35d5f60de5dcc..8b628b098798a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -648,6 +648,8 @@ public function testDefaults() $this->assertFalse($container->getDefinition('with_defaults')->isPublic()); $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_defaults')->getTags()); $this->assertTrue($container->getDefinition('with_defaults')->isAutowired()); + $this->assertArrayNotHasKey('public', $container->getDefinition('with_defaults')->getChanges()); + $this->assertArrayNotHasKey('autowire', $container->getDefinition('with_defaults')->getChanges()); $this->assertArrayNotHasKey('public', $container->getDefinition('no_defaults_child')->getChanges()); $this->assertArrayNotHasKey('autowire', $container->getDefinition('no_defaults_child')->getChanges()); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 8e4732f99e95e..8f628ae3e22cb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -24,7 +24,6 @@ use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy; use Symfony\Component\ExpressionLanguage\Expression; @@ -398,6 +397,8 @@ public function testDefaults() $this->assertFalse($container->getDefinition('with_defaults')->isPublic()); $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_defaults')->getTags()); $this->assertTrue($container->getDefinition('with_defaults')->isAutowired()); + $this->assertArrayNotHasKey('public', $container->getDefinition('with_defaults')->getChanges()); + $this->assertArrayNotHasKey('autowire', $container->getDefinition('with_defaults')->getChanges()); $this->assertFalse($container->getAlias('with_defaults_aliased')->isPublic()); $this->assertFalse($container->getAlias('with_defaults_aliased_short')->isPublic()); @@ -532,12 +533,12 @@ public function testAnonymousServicesInInstanceof() $this->assertCount(3, $instanceof); $this->assertArrayHasKey('DummyInterface', $instanceof); - $args = $instanceof['DummyInterface']->getArguments(); + $args = $instanceof['DummyInterface']->getProperties(); $this->assertCount(1, $args); - $this->assertInstanceOf(Reference::class, $args[0]); - $this->assertTrue($container->has((string) $args[0])); + $this->assertInstanceOf(Reference::class, $args['foo']); + $this->assertTrue($container->has((string) $args['foo'])); - $anonymous = $container->getDefinition((string) $args[0]); + $anonymous = $container->getDefinition((string) $args['foo']); $this->assertEquals('Anonymous', $anonymous->getClass()); $this->assertFalse($anonymous->isPublic()); $this->assertEmpty($anonymous->getInstanceofConditionals());