From 286536feb57c0887452b2db6556b4bf5bc7310be Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sat, 20 Aug 2016 09:02:39 +0000 Subject: [PATCH 1/5] poc randomizing private services --- .../Compiler/PassConfig.php | 4 + .../RandomizePrivateServiceIdentifiers.php | 90 +++++++++++++++++++ .../DependencyInjection/Container.php | 23 +++-- .../DependencyInjection/ContainerBuilder.php | 21 ++++- .../DependencyInjection/Definition.php | 12 +++ .../DependencyInjection/Dumper/PhpDumper.php | 8 +- .../Tests/ContainerTest.php | 29 ++++-- .../Tests/Fixtures/containers/container9.php | 9 ++ .../Tests/Fixtures/graphviz/services9.dot | 5 ++ .../Tests/Fixtures/php/services9.php | 61 +++++++++++-- .../Tests/Fixtures/php/services9_compiled.php | 54 +++++++++++ .../Tests/Fixtures/xml/services9.xml | 7 ++ .../Tests/Fixtures/yaml/services9.yml | 9 ++ .../Tests/Loader/XmlFileLoaderTest.php | 2 +- 14 files changed, 307 insertions(+), 27 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index f33e9fa0450ce..a29556e861543 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -65,6 +65,10 @@ public function __construct() )), new CheckExceptionOnInvalidReferenceBehaviorPass(), )); + + $this->afterRemovingPasses = array(array( + new RandomizePrivateServiceIdentifiers(), + )); } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php new file mode 100644 index 0000000000000..63f391705f4db --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php @@ -0,0 +1,90 @@ + + * + * 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\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Randomizes private service identifiers, effectively making them unaccessable from outside. + * + * @author Roland Franssen + */ +class RandomizePrivateServiceIdentifiers implements CompilerPassInterface +{ + private $idMap; + + public function process(ContainerBuilder $container) + { + // build id map + $this->idMap = array(); + foreach ($container->getDefinitions() as $id => $definition) { + if (!$definition->isPublic()) { + $this->idMap[$id] = md5(uniqid($id)); + } + } + + // update private definitions + alias map + $aliases = $container->getAliases(); + foreach ($this->idMap as $id => $randId) { + $definition = $container->getDefinition($id); + Definition::markAsPrivateOrigin($id, $definition); + $container->setDefinition($randId, $definition); + $container->removeDefinition($id); + foreach ($aliases as $aliasId => $aliasedId) { + if ((string) $aliasedId === $id) { + $aliases[$aliasId] = new Alias($randId, $aliasedId->isPublic()); + } + } + } + $container->setAliases($aliases); + + // update referencing definitions + foreach ($container->getDefinitions() as $id => $definition) { + $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 (null !== ($decorated = $definition->getDecoratedService()) && isset($this->idMap[$decorated[0]])) { + $definition->setDecoratedService($this->idMap[$decorated[0]], $decorated[1], $decorated[2]); + } + } + } + + private function processArguments(array $arguments) + { + foreach ($arguments as $k => $argument) { + if (is_array($argument)) { + $arguments[$k] = $this->processArguments($argument); + } elseif ($argument instanceof Reference && isset($this->idMap[$id = (string) $argument])) { + $arguments[$k] = new Reference($this->idMap[$id], $argument->getInvalidBehavior()); + } + } + + return $arguments; + } + + private function processFactory($factory) + { + if (null === $factory || !is_array($factory) || !$factory[0] instanceof Reference) { + return $factory; + } + if (isset($this->idMap[$id = (string) $factory[0]])) { + $factory[0] = new Reference($this->idMap[$id], $factory[0]->getInvalidBehavior()); + } + + return $factory; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 0d7e9dafef483..3fccd54ce5d96 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -65,9 +65,9 @@ class Container implements ResettableContainerInterface protected $services = array(); protected $methodMap = array(); - protected $privates = array(); protected $aliases = array(); protected $loading = array(); + protected $privateOriginIds = array(); private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_'); @@ -178,7 +178,7 @@ public function set($id, $service) unset($this->services[$id]); } - if (isset($this->privates[$id])) { + if (false !== $randomizedId = array_search($id, $this->privateOriginIds, true)) { if (null === $service) { @trigger_error(sprintf('Unsetting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); } else { @@ -207,8 +207,9 @@ public function has($id) if (--$i && $id !== $lcId = strtolower($id)) { $id = $lcId; } else { - if (isset($this->privates[$id])) { + if (false !== $randomizedId = array_search($id, $this->privateOriginIds, true)) { @trigger_error(sprintf('Checking for the existence of the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); + $id = $randomizedId; } return method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service'); @@ -263,6 +264,16 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE } elseif (method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) { // $method is set to the right value, proceed } else { + if (false !== $randomizedId = array_search($id, $this->privateOriginIds, true)) { + @trigger_error(sprintf('Requesting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); + + if ($randomizedId === $id) { + throw new \LogicException(sprintf('Cannot reference a private origin service "%s" with the same id.', $id)); + } + + return $this->get($randomizedId); + } + if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { if (!$id) { throw new ServiceNotFoundException($id); @@ -281,9 +292,6 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE return; } - if (isset($this->privates[$id])) { - @trigger_error(sprintf('Requesting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); - } $this->loading[$id] = true; @@ -341,7 +349,8 @@ public function getServiceIds() $ids = array(); foreach (get_class_methods($this) as $method) { if (preg_match('/^get(.+)Service$/', $method, $match)) { - $ids[] = self::underscore($match[1]); + $id = self::underscore($match[1]); + $ids[] = isset($this->privateOriginIds[$id]) ? $this->privateOriginIds[$id] : $id; } } $ids[] = 'service_container'; diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index cb3789165891a..08117af0deb9e 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -355,7 +355,7 @@ public function set($id, $service) throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a frozen container is not allowed.', $id)); } - unset($this->definitions[$id], $this->aliasDefinitions[$id]); + unset($this->definitions[$id], $this->aliasDefinitions[$id], $this->privateOriginIds[$id]); parent::set($id, $service); } @@ -367,7 +367,9 @@ public function set($id, $service) */ public function removeDefinition($id) { - unset($this->definitions[strtolower($id)]); + $id = strtolower($id); + + unset($this->definitions[$id], $this->privateOriginIds[$id]); } /** @@ -541,14 +543,25 @@ public function compile() $compiler->compile($this); $this->compiled = true; + $renameDefinitions = array(); foreach ($this->definitions as $id => $definition) { if (!$definition->isPublic()) { - $this->privates[$id] = true; + if (null === $privateOriginId = $definition->getPrivateOriginId()) { + Definition::markAsPrivateOrigin($id, $definition); + $privateOriginId = $id; + $id = md5(uniqid($id)); + $renameDefinitions[$privateOriginId] = $id; + } + $this->privateOriginIds[$id] = $privateOriginId; } if ($this->trackResources && $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) { $this->addClassResource(new \ReflectionClass($class)); } } + foreach ($renameDefinitions as $oldId => $newId) { + $this->definitions[$newId] = $this->definitions[$oldId]; + unset($this->definitions[$oldId]); + } $this->extensionConfigs = array(); @@ -735,7 +748,7 @@ public function setDefinition($id, Definition $definition) $id = strtolower($id); - unset($this->aliasDefinitions[$id]); + unset($this->aliasDefinitions[$id], $this->privateOriginIds[$id]); return $this->definitions[$id] = $definition; } diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index b697f02708e1f..66d5f30f14423 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -38,6 +38,7 @@ class Definition private $decoratedService; private $autowired = false; private $autowiringTypes = array(); + private $privateOriginId; protected $arguments; @@ -51,6 +52,17 @@ public function __construct($class = null, array $arguments = array()) $this->arguments = $arguments; } + public static function markAsPrivateOrigin($id, Definition $origin) + { + $origin->public = false; + $origin->privateOriginId = $id; + } + + final public function getPrivateOriginId() + { + return !$this->isPublic() ? $this->privateOriginId : null; + } + /** * Sets a factory. * diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 210b13eb84601..2e66837382383 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -24,6 +24,7 @@ use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; use Symfony\Component\DependencyInjection\ExpressionLanguage; +use Symfony\Component\DependencyInjection\ExpressionLanguageProvider; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\HttpKernel\Kernel; @@ -839,6 +840,7 @@ public function __construct() $code .= "\n \$this->services = array();\n"; $code .= $this->addMethodMap(); + $code .= $this->addPrivateServices(); $code .= $this->addAliases(); $code .= <<<'EOF' @@ -903,8 +905,8 @@ private function addPrivateServices() $code = ''; ksort($definitions); foreach ($definitions as $id => $definition) { - if (!$definition->isPublic()) { - $code .= ' '.var_export($id, true)." => true,\n"; + if (null !== $privateOriginId = $definition->getPrivateOriginId()) { + $code .= ' '.var_export($id, true).' => '.var_export($privateOriginId, true).",\n"; } } @@ -912,7 +914,7 @@ private function addPrivateServices() return ''; } - $out = " \$this->privates = array(\n"; + $out = " \$this->privateOriginIds = array(\n"; $out .= $code; $out .= " );\n"; diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php index 28101c29fa984..fa2c1fce938aa 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php @@ -126,7 +126,7 @@ public function testGetServiceIds() $sc = new ProjectServiceContainer(); $sc->set('foo', $obj = new \stdClass()); - $this->assertEquals(array('internal', 'bar', 'foo_bar', 'foo.baz', 'circular', 'throw_exception', 'throws_exception_on_service_configuration', 'service_container', 'foo'), $sc->getServiceIds(), '->getServiceIds() returns defined service ids by getXXXService() methods, followed by service ids defined by set()'); + $this->assertEquals(array('internal', 'sharing_internal', 'bar', 'foo_bar', 'foo.baz', 'circular', 'throw_exception', 'throws_exception_on_service_configuration', 'service_container', 'foo'), $sc->getServiceIds(), '->getServiceIds() returns defined service ids by getXXXService() methods, followed by service ids defined by set()'); } public function testSet() @@ -308,6 +308,15 @@ public function testThatCloningIsNotSupported() $this->assertTrue($clone->isPrivate()); } + public function testSharedPrivateService() + { + $c = new ProjectServiceContainer(); + $expected = new \stdClass(); + $expected->internal = new \stdClass(); + $this->assertTrue($c->has('sharing_internal')); + $this->assertEquals($expected, $c->get('sharing_internal'), '->get() returns a public service referencing a private service'); + } + /** * @group legacy * @requires function Symfony\Bridge\PhpUnit\ErrorAssert::assertDeprecationsAreTriggered @@ -379,6 +388,7 @@ class ProjectServiceContainer extends Container public $__foo_bar; public $__foo_baz; public $__internal; + public $__sharing_internal; public function __construct() { @@ -387,14 +397,23 @@ public function __construct() $this->__bar = new \stdClass(); $this->__foo_bar = new \stdClass(); $this->__foo_baz = new \stdClass(); - $this->__internal = new \stdClass(); - $this->privates = array('internal' => true); + $this->__rand1_internal = new \stdClass(); + $this->__sharing_internal = new \stdClass(); + $this->privateOriginIds = array('rand1_internal' => 'internal'); $this->aliases = array('alias' => 'bar'); } - protected function getInternalService() + protected function getRand1InternalService() { - return $this->__internal; + return $this->__rand1_internal; + } + + protected function getSharingInternalService() + { + $instance = $this->__sharing_internal; + $instance->internal = $this->get('rand1_internal'); // simulates dumped behavior + + return $instance; } protected function getBarService() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php index ba25dc3c99361..9f76165e45cff 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php @@ -129,5 +129,14 @@ ->register('factory_service_simple', 'Bar') ->setFactory(array(new Reference('factory_simple'), 'getInstance')) ; +$container + ->register('shared_private', 'stdClass') + ->setPublic(false); +$container + ->register('shared_private_dep1', 'stdClass') + ->setProperty('dep', new Reference('shared_private')); +$container + ->register('shared_private_dep2', 'stdClass') + ->setProperty('dep', new Reference('shared_private')); return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot index 3b24ef8ffbca3..f0e7d137aee4b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot @@ -26,6 +26,9 @@ digraph sc { node_service_from_static_method [label="service_from_static_method\nBar\\FooClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_factory_simple [label="factory_simple\nSimpleFactoryClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_factory_service_simple [label="factory_service_simple\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_shared_private [label="shared_private\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_shared_private_dep1 [label="shared_private_dep1\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_shared_private_dep2 [label="shared_private_dep2\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; node_service_container [label="service_container\nSymfony\\Component\\DependencyInjection\\ContainerBuilder\n", shape=record, fillcolor="#9999ff", style="filled"]; node_foo2 [label="foo2\n\n", shape=record, fillcolor="#ff9999", style="filled"]; node_foo3 [label="foo3\n\n", shape=record, fillcolor="#ff9999", style="filled"]; @@ -43,4 +46,6 @@ digraph sc { node_inlined -> node_baz [label="setBaz()" style="dashed"]; node_baz -> node_foo_with_inline [label="setFoo()" style="dashed"]; node_configurator_service -> node_baz [label="setFoo()" style="dashed"]; + node_shared_private_dep1 -> node_shared_private [label="" style="dashed"]; + node_shared_private_dep2 -> node_shared_private [label="" style="dashed"]; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php index f77ae5dea1cdb..4648bb45d099c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php @@ -48,13 +48,9 @@ public function __construct() 'new_factory_service' => 'getNewFactoryServiceService', 'request' => 'getRequestService', 'service_from_static_method' => 'getServiceFromStaticMethodService', - ); - $this->privates = array( - 'configurator_service' => true, - 'configurator_service_simple' => true, - 'factory_simple' => true, - 'inlined' => true, - 'new_factory' => true, + 'shared_private' => 'getSharedPrivateService', + 'shared_private_dep1' => 'getSharedPrivateDep1Service', + 'shared_private_dep2' => 'getSharedPrivateDep2Service', ); $this->aliases = array( 'alias_for_alias' => 'foo', @@ -354,6 +350,40 @@ protected function getServiceFromStaticMethodService() return $this->services['service_from_static_method'] = \Bar\FooClass::getInstance(); } + /** + * Gets the 'shared_private_dep1' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \stdClass A stdClass instance + */ + protected function getSharedPrivateDep1Service() + { + $this->services['shared_private_dep1'] = $instance = new \stdClass(); + + $instance->dep = $this->get('shared_private'); + + return $instance; + } + + /** + * Gets the 'shared_private_dep2' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \stdClass A stdClass instance + */ + protected function getSharedPrivateDep2Service() + { + $this->services['shared_private_dep2'] = $instance = new \stdClass(); + + $instance->dep = $this->get('shared_private'); + + return $instance; + } + /** * Gets the 'configurator_service' service. * @@ -452,6 +482,23 @@ protected function getNewFactoryService() return $instance; } + /** + * Gets the 'shared_private' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * This service is private. + * If you want to be able to request this service from the container directly, + * make it public, otherwise you might end up with broken code. + * + * @return \stdClass A stdClass instance + */ + protected function getSharedPrivateService() + { + return $this->services['shared_private'] = new \stdClass(); + } + /** * Gets the default parameters. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index bcbd4a67b6b9d..8f9b96c99e0e4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -44,6 +44,9 @@ public function __construct() 'new_factory_service' => 'getNewFactoryServiceService', 'request' => 'getRequestService', 'service_from_static_method' => 'getServiceFromStaticMethodService', + 'shared_private' => 'getSharedPrivateService', + 'shared_private_dep1' => 'getSharedPrivateDep1Service', + 'shared_private_dep2' => 'getSharedPrivateDep2Service', ); $this->aliases = array( 'alias_for_alias' => 'foo', @@ -342,6 +345,57 @@ protected function getServiceFromStaticMethodService() return $this->services['service_from_static_method'] = \Bar\FooClass::getInstance(); } + /** + * Gets the 'shared_private_dep1' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \stdClass A stdClass instance + */ + protected function getSharedPrivateDep1Service() + { + $this->services['shared_private_dep1'] = $instance = new \stdClass(); + + $instance->dep = $this->get('shared_private'); + + return $instance; + } + + /** + * Gets the 'shared_private_dep2' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \stdClass A stdClass instance + */ + protected function getSharedPrivateDep2Service() + { + $this->services['shared_private_dep2'] = $instance = new \stdClass(); + + $instance->dep = $this->get('shared_private'); + + return $instance; + } + + /** + * Gets the 'shared_private' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * This service is private. + * If you want to be able to request this service from the container directly, + * make it public, otherwise you might end up with broken code. + * + * @return \stdClass A stdClass instance + */ + protected function getSharedPrivateService() + { + return $this->services['shared_private'] = new \stdClass(); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml index 9ad09ad5918dd..3b7cca3736bc3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml @@ -115,6 +115,13 @@ + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml index 44a174b6edb5b..6a615d480d5f0 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml @@ -107,5 +107,14 @@ services: factory_service_simple: class: Bar factory: ['@factory_simple', getInstance] + shared_private: + class: stdClass + public: false + shared_private_dep1: + class: stdClass + properties: { dep: '@shared_private' } + shared_private_dep2: + class: stdClass + properties: { dep: '@shared_private' } alias_for_foo: '@foo' alias_for_alias: '@foo' diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index a351c62ebc881..119e17c43d580 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -220,7 +220,7 @@ public function testLoadAnonymousServices() $services = $container->getDefinitions(); $fooArgs = $services['foo']->getArguments(); $barArgs = $services['bar']->getArguments(); - $this->assertSame($fooArgs[0], $barArgs[0]); + $this->assertEquals($fooArgs[0], $barArgs[0]); } public function testLoadServices() From bc3d636af8e9b3979da197b19d1062d400a7db7d Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sat, 20 Aug 2016 20:08:58 +0000 Subject: [PATCH 2/5] fixed tests --- .../RandomizePrivateServiceIdentifiers.php | 9 +++++++-- .../DependencyInjection/Dumper/PhpDumper.php | 1 - .../Tests/Dumper/PhpDumperTest.php | 8 ++++++++ .../Tests/Fixtures/php/services9_compiled.php | 15 +++++++++------ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php index 63f391705f4db..455e36a3583e3 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php @@ -12,7 +12,6 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; @@ -25,6 +24,7 @@ class RandomizePrivateServiceIdentifiers implements CompilerPassInterface { private $idMap; + private $randomizer; public function process(ContainerBuilder $container) { @@ -32,7 +32,7 @@ public function process(ContainerBuilder $container) $this->idMap = array(); foreach ($container->getDefinitions() as $id => $definition) { if (!$definition->isPublic()) { - $this->idMap[$id] = md5(uniqid($id)); + $this->idMap[$id] = $this->randomizer ? (string) call_user_func($this->randomizer, $id) : md5(uniqid($id)); } } @@ -63,6 +63,11 @@ public function process(ContainerBuilder $container) } } + public function setRandomizer(callable $randomizer = null) + { + $this->randomizer = $randomizer; + } + private function processArguments(array $arguments) { foreach ($arguments as $k => $argument) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 2e66837382383..5d6a48af9777a 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -24,7 +24,6 @@ use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; use Symfony\Component\DependencyInjection\ExpressionLanguage; -use Symfony\Component\DependencyInjection\ExpressionLanguageProvider; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\HttpKernel\Kernel; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index d155a9ac763e2..09f69bb44c396 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper; +use Symfony\Component\DependencyInjection\Compiler\RandomizePrivateServiceIdentifiers; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -122,6 +123,13 @@ public function testAddService() // with compilation $container = include self::$fixturesPath.'/containers/container9.php'; + foreach ($container->getCompiler()->getPassConfig()->getPasses() as $pass) { + if ($pass instanceof RandomizePrivateServiceIdentifiers) { + $pass->setRandomizer(function ($id) { + return 'shared_private' === $id ? 'semirandom_'.$id : md5(uniqid($id)); + }); + } + } $container->compile(); $dumper = new PhpDumper($container); $this->assertEquals(str_replace('%path%', str_replace('\\', '\\\\', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), file_get_contents(self::$fixturesPath.'/php/services9_compiled.php')), $dumper->dump(), '->dump() dumps services'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index 8f9b96c99e0e4..d1e73ba60954b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -43,11 +43,14 @@ public function __construct() 'method_call1' => 'getMethodCall1Service', 'new_factory_service' => 'getNewFactoryServiceService', 'request' => 'getRequestService', + 'semirandom_shared_private' => 'getSemirandomSharedPrivateService', 'service_from_static_method' => 'getServiceFromStaticMethodService', - 'shared_private' => 'getSharedPrivateService', 'shared_private_dep1' => 'getSharedPrivateDep1Service', 'shared_private_dep2' => 'getSharedPrivateDep2Service', ); + $this->privateOriginIds = array( + 'semirandom_shared_private' => 'shared_private', + ); $this->aliases = array( 'alias_for_alias' => 'foo', 'alias_for_foo' => 'foo', @@ -357,7 +360,7 @@ protected function getSharedPrivateDep1Service() { $this->services['shared_private_dep1'] = $instance = new \stdClass(); - $instance->dep = $this->get('shared_private'); + $instance->dep = $this->get('semirandom_shared_private'); return $instance; } @@ -374,13 +377,13 @@ protected function getSharedPrivateDep2Service() { $this->services['shared_private_dep2'] = $instance = new \stdClass(); - $instance->dep = $this->get('shared_private'); + $instance->dep = $this->get('semirandom_shared_private'); return $instance; } /** - * Gets the 'shared_private' service. + * Gets the 'semirandom_shared_private' service. * * This service is shared. * This method always returns the same instance of the service. @@ -391,9 +394,9 @@ protected function getSharedPrivateDep2Service() * * @return \stdClass A stdClass instance */ - protected function getSharedPrivateService() + protected function getSemirandomSharedPrivateService() { - return $this->services['shared_private'] = new \stdClass(); + return $this->services['semirandom_shared_private'] = new \stdClass(); } /** From 1313bc1093312e786256bbb8287543370e04b835 Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sun, 21 Aug 2016 08:34:24 +0000 Subject: [PATCH 3/5] refactored --- .../RandomizePrivateServiceIdentifiers.php | 30 ++++++------ .../DependencyInjection/Container.php | 49 ++++++++++++------- .../DependencyInjection/ContainerBuilder.php | 27 +++++----- .../DependencyInjection/Definition.php | 11 ++--- .../DependencyInjection/Dumper/PhpDumper.php | 36 +++++++++++--- .../Tests/ContainerTest.php | 18 +++++-- .../Tests/Fixtures/php/services9.php | 20 ++++++++ .../Tests/Fixtures/php/services9_compiled.php | 10 +++- 8 files changed, 133 insertions(+), 68 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php index 455e36a3583e3..1c63f6ca36779 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php @@ -13,7 +13,6 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Alias; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; /** @@ -28,28 +27,21 @@ class RandomizePrivateServiceIdentifiers implements CompilerPassInterface public function process(ContainerBuilder $container) { - // build id map + // mark randomized definitions + build id map $this->idMap = array(); foreach ($container->getDefinitions() as $id => $definition) { if (!$definition->isPublic()) { - $this->idMap[$id] = $this->randomizer ? (string) call_user_func($this->randomizer, $id) : md5(uniqid($id)); + $this->idMap[$id] = $this->randomizer ? (string) call_user_func($this->randomizer, $id) : hash('sha256', mt_rand().$id); + $definition->setOriginId($id); } } - // update private definitions + alias map + // rename definitions $aliases = $container->getAliases(); - foreach ($this->idMap as $id => $randId) { - $definition = $container->getDefinition($id); - Definition::markAsPrivateOrigin($id, $definition); - $container->setDefinition($randId, $definition); - $container->removeDefinition($id); - foreach ($aliases as $aliasId => $aliasedId) { - if ((string) $aliasedId === $id) { - $aliases[$aliasId] = new Alias($randId, $aliasedId->isPublic()); - } - } + foreach ($this->idMap as $oldId => $newId) { + $container->setDefinition($newId, $container->getDefinition($oldId)); + $container->removeDefinition($oldId); } - $container->setAliases($aliases); // update referencing definitions foreach ($container->getDefinitions() as $id => $definition) { @@ -61,6 +53,14 @@ public function process(ContainerBuilder $container) $definition->setDecoratedService($this->idMap[$decorated[0]], $decorated[1], $decorated[2]); } } + + // update alias map + $aliases = $container->getAliases(); + foreach ($container->getAliases() as $oldId => $alias) { + if(isset($this->idMap[$oldId]) && $oldId === (string) $alias) { + $container->setAlias(new Alias($this->idMap[$oldId], $alias->isPublic()), $this->idMap[$oldId]); + } + } } public function setRandomizer(callable $randomizer = null) diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 3fccd54ce5d96..7bfa86f9a00d5 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -67,7 +67,7 @@ class Container implements ResettableContainerInterface protected $methodMap = array(); protected $aliases = array(); protected $loading = array(); - protected $privateOriginIds = array(); + protected $serviceMetadata = array(); private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_'); @@ -172,19 +172,22 @@ public function set($id, $service) unset($this->aliases[$id]); } - $this->services[$id] = $service; - - if (null === $service) { - unset($this->services[$id]); - } - - if (false !== $randomizedId = array_search($id, $this->privateOriginIds, true)) { + if ($this->isPrivateService($id)) { if (null === $service) { @trigger_error(sprintf('Unsetting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); } else { @trigger_error(sprintf('Setting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0. A new public service will be created instead.', $id), E_USER_DEPRECATED); } } + + if (null === $service) { + if (null !== $originId = $this->getServiceOriginId($id)) { + unset($this->services[$originId], $this->serviceMetadata[$originId]); + } + unset($this->services[$id], $this->serviceMetadata[$id]); + } else { + $this->services[$id] = $service; + } } /** @@ -207,9 +210,9 @@ public function has($id) if (--$i && $id !== $lcId = strtolower($id)) { $id = $lcId; } else { - if (false !== $randomizedId = array_search($id, $this->privateOriginIds, true)) { + if ($this->isPrivateService($id)) { @trigger_error(sprintf('Checking for the existence of the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); - $id = $randomizedId; + $id = $this->getServiceOriginId($id) ?: $id; } return method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service'); @@ -264,14 +267,13 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE } elseif (method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) { // $method is set to the right value, proceed } else { - if (false !== $randomizedId = array_search($id, $this->privateOriginIds, true)) { + if ($this->isPrivateService($id) && null === $this->getServiceOriginId($id)) { @trigger_error(sprintf('Requesting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); - - if ($randomizedId === $id) { - throw new \LogicException(sprintf('Cannot reference a private origin service "%s" with the same id.', $id)); + foreach ($this->serviceMetadata as $serviceId => $metadata) { + if (isset($metadata['origin_id']) && $id === $metadata['origin_id']) { + return $this->get($serviceId, $invalidBehavior); + } } - - return $this->get($randomizedId); } if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { @@ -350,7 +352,10 @@ public function getServiceIds() foreach (get_class_methods($this) as $method) { if (preg_match('/^get(.+)Service$/', $method, $match)) { $id = self::underscore($match[1]); - $ids[] = isset($this->privateOriginIds[$id]) ? $this->privateOriginIds[$id] : $id; + if ($this->isPrivateService($id)) { + $id = $this->getServiceOriginId($id) ?: $id; + } + $ids[] = $id; } } $ids[] = 'service_container'; @@ -382,6 +387,16 @@ public static function underscore($id) return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), str_replace('_', '.', $id))); } + final protected function isPrivateService($id) + { + return isset($this->serviceMetadata[$id]['private']) && true === $this->serviceMetadata[$id]['private']; + } + + final protected function getServiceOriginId($id) + { + return isset($this->serviceMetadata[$id]['origin_id']) ? $this->serviceMetadata[$id]['origin_id'] : null; + } + private function __clone() { } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 08117af0deb9e..5c422da3503b8 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -355,7 +355,7 @@ public function set($id, $service) throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a frozen container is not allowed.', $id)); } - unset($this->definitions[$id], $this->aliasDefinitions[$id], $this->privateOriginIds[$id]); + unset($this->definitions[$id], $this->aliasDefinitions[$id]); parent::set($id, $service); } @@ -367,9 +367,7 @@ public function set($id, $service) */ public function removeDefinition($id) { - $id = strtolower($id); - - unset($this->definitions[$id], $this->privateOriginIds[$id]); + unset($this->definitions[strtolower($id)]); } /** @@ -543,25 +541,22 @@ public function compile() $compiler->compile($this); $this->compiled = true; - $renameDefinitions = array(); + $this->serviceMetadata = array(); + foreach ($this->definitions as $id => $definition) { if (!$definition->isPublic()) { - if (null === $privateOriginId = $definition->getPrivateOriginId()) { - Definition::markAsPrivateOrigin($id, $definition); - $privateOriginId = $id; - $id = md5(uniqid($id)); - $renameDefinitions[$privateOriginId] = $id; + $this->serviceMetadata[$id]['private'] = true; + } + if (null !== $originId = $definition->getOriginId()) { + $this->serviceMetadata[$id]['origin_id'] = $originId; + if (!$definition->isPublic()) { + $this->serviceMetadata[$originId]['private'] = true; } - $this->privateOriginIds[$id] = $privateOriginId; } if ($this->trackResources && $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) { $this->addClassResource(new \ReflectionClass($class)); } } - foreach ($renameDefinitions as $oldId => $newId) { - $this->definitions[$newId] = $this->definitions[$oldId]; - unset($this->definitions[$oldId]); - } $this->extensionConfigs = array(); @@ -748,7 +743,7 @@ public function setDefinition($id, Definition $definition) $id = strtolower($id); - unset($this->aliasDefinitions[$id], $this->privateOriginIds[$id]); + unset($this->aliasDefinitions[$id]); return $this->definitions[$id] = $definition; } diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index 66d5f30f14423..e9edb7c91b030 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -38,7 +38,7 @@ class Definition private $decoratedService; private $autowired = false; private $autowiringTypes = array(); - private $privateOriginId; + private $originId; protected $arguments; @@ -52,15 +52,14 @@ public function __construct($class = null, array $arguments = array()) $this->arguments = $arguments; } - public static function markAsPrivateOrigin($id, Definition $origin) + public function setOriginId($id) { - $origin->public = false; - $origin->privateOriginId = $id; + $this->originId = $id; } - final public function getPrivateOriginId() + public function getOriginId() { - return !$this->isPublic() ? $this->privateOriginId : null; + return $this->originId; } /** diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 5d6a48af9777a..5b8058e6d8f8e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -804,7 +804,7 @@ public function __construct() EOF; $code .= $this->addMethodMap(); - $code .= $this->addPrivateServices(); + $code .= $this->addServiceMetadata(); $code .= $this->addAliases(); $code .= <<<'EOF' @@ -839,7 +839,7 @@ public function __construct() $code .= "\n \$this->services = array();\n"; $code .= $this->addMethodMap(); - $code .= $this->addPrivateServices(); + $code .= $this->addServiceMetadata(); $code .= $this->addAliases(); $code .= <<<'EOF' @@ -891,11 +891,11 @@ private function addMethodMap() } /** - * Adds the privates property definition. + * Adds the service metadata property definition. * * @return string */ - private function addPrivateServices() + private function addServiceMetadata() { if (!$definitions = $this->container->getDefinitions()) { return ''; @@ -904,8 +904,30 @@ private function addPrivateServices() $code = ''; ksort($definitions); foreach ($definitions as $id => $definition) { - if (null !== $privateOriginId = $definition->getPrivateOriginId()) { - $code .= ' '.var_export($id, true).' => '.var_export($privateOriginId, true).",\n"; + $metadata = array(); + if (!$definition->isPublic()) { + $metadata['private'] = true; + } + if (null !== $originId = $definition->getOriginId()) { + $metadata['origin_id'] = $originId; + } + if ($metadata) { + $code .= ' '.var_export($id, true)." => array(\n"; + foreach ($metadata as $k => $v) { + $code .= ' '.var_export($k, true).' => '.var_export($v, true).",\n"; + } + $code .= " ),\n"; + } + if (null === $originId) { + continue; + } + unset($metadata['origin_id']); + if ($metadata) { + $code .= ' '.var_export($originId, true)." => array(\n"; + foreach ($metadata as $k => $v) { + $code .= ' '.var_export($k, true).' => '.var_export($v, true).",\n"; + } + $code .= " ),\n"; } } @@ -913,7 +935,7 @@ private function addPrivateServices() return ''; } - $out = " \$this->privateOriginIds = array(\n"; + $out = " \$this->serviceMetadata = array(\n"; $out .= $code; $out .= " );\n"; diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php index fa2c1fce938aa..89a1cda6cf129 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php @@ -397,21 +397,29 @@ public function __construct() $this->__bar = new \stdClass(); $this->__foo_bar = new \stdClass(); $this->__foo_baz = new \stdClass(); - $this->__rand1_internal = new \stdClass(); + $this->__internal = new \stdClass(); $this->__sharing_internal = new \stdClass(); - $this->privateOriginIds = array('rand1_internal' => 'internal'); $this->aliases = array('alias' => 'bar'); + $this->serviceMetadata = array( + 'semirandom_internal' => array( + 'private' => true, + 'origin_id' => 'internal', + ), + 'internal' => array( + 'private' => true, + ), + ); } - protected function getRand1InternalService() + protected function getSemirandomInternalService() { - return $this->__rand1_internal; + return $this->__internal; } protected function getSharingInternalService() { $instance = $this->__sharing_internal; - $instance->internal = $this->get('rand1_internal'); // simulates dumped behavior + $instance->internal = $this->get('semirandom_internal'); // simulates dumped behavior return $instance; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php index 4648bb45d099c..bfb024add2870 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php @@ -52,6 +52,26 @@ public function __construct() 'shared_private_dep1' => 'getSharedPrivateDep1Service', 'shared_private_dep2' => 'getSharedPrivateDep2Service', ); + $this->serviceMetadata = array( + 'configurator_service' => array( + 'private' => true, + ), + 'configurator_service_simple' => array( + 'private' => true, + ), + 'factory_simple' => array( + 'private' => true, + ), + 'inlined' => array( + 'private' => true, + ), + 'new_factory' => array( + 'private' => true, + ), + 'shared_private' => array( + 'private' => true, + ), + ); $this->aliases = array( 'alias_for_alias' => 'foo', 'alias_for_foo' => 'foo', diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index d1e73ba60954b..5da9ca3a62d17 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -48,8 +48,14 @@ public function __construct() 'shared_private_dep1' => 'getSharedPrivateDep1Service', 'shared_private_dep2' => 'getSharedPrivateDep2Service', ); - $this->privateOriginIds = array( - 'semirandom_shared_private' => 'shared_private', + $this->serviceMetadata = array( + 'semirandom_shared_private' => array( + 'private' => true, + 'origin_id' => 'shared_private', + ), + 'shared_private' => array( + 'private' => true, + ), ); $this->aliases = array( 'alias_for_alias' => 'foo', From 2cc36db448b3c071d3251f1d36fb6a7e70759cd6 Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sun, 21 Aug 2016 13:12:40 +0000 Subject: [PATCH 4/5] try public randomized definitions --- .../RandomizePrivateServiceIdentifiers.php | 1 + .../DependencyInjection/Container.php | 39 ++++++------------ .../DependencyInjection/ContainerBuilder.php | 10 +---- .../DependencyInjection/Dumper/PhpDumper.php | 32 +++------------ .../Tests/ContainerTest.php | 10 +---- .../Tests/Fixtures/php/services9.php | 20 ---------- .../Tests/Fixtures/php/services9_compiled.php | 40 +++++++------------ 7 files changed, 37 insertions(+), 115 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php index 1c63f6ca36779..f89995dd6b023 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php @@ -33,6 +33,7 @@ public function process(ContainerBuilder $container) if (!$definition->isPublic()) { $this->idMap[$id] = $this->randomizer ? (string) call_user_func($this->randomizer, $id) : hash('sha256', mt_rand().$id); $definition->setOriginId($id); + $definition->setPublic(true); } } diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 7bfa86f9a00d5..2385f4d77f222 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -67,7 +67,7 @@ class Container implements ResettableContainerInterface protected $methodMap = array(); protected $aliases = array(); protected $loading = array(); - protected $serviceMetadata = array(); + protected $privateOriginIds = array(); // for BC private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_'); @@ -172,19 +172,18 @@ public function set($id, $service) unset($this->aliases[$id]); } - if ($this->isPrivateService($id)) { + if (isset($this->privateOriginIds[$id])) { if (null === $service) { @trigger_error(sprintf('Unsetting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); + unset($this->privateOriginIds[$id]); } else { @trigger_error(sprintf('Setting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0. A new public service will be created instead.', $id), E_USER_DEPRECATED); + $id = $this->privateOriginIds[$id]; } } if (null === $service) { - if (null !== $originId = $this->getServiceOriginId($id)) { - unset($this->services[$originId], $this->serviceMetadata[$originId]); - } - unset($this->services[$id], $this->serviceMetadata[$id]); + unset($this->services[$id]); } else { $this->services[$id] = $service; } @@ -210,9 +209,9 @@ public function has($id) if (--$i && $id !== $lcId = strtolower($id)) { $id = $lcId; } else { - if ($this->isPrivateService($id)) { + if (isset($this->privateOriginIds[$id])) { @trigger_error(sprintf('Checking for the existence of the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); - $id = $this->getServiceOriginId($id) ?: $id; + $id = $this->privateOriginIds[$id]; } return method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service'); @@ -267,13 +266,10 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE } elseif (method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) { // $method is set to the right value, proceed } else { - if ($this->isPrivateService($id) && null === $this->getServiceOriginId($id)) { + if (isset($this->privateOriginIds[$id])) { @trigger_error(sprintf('Requesting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); - foreach ($this->serviceMetadata as $serviceId => $metadata) { - if (isset($metadata['origin_id']) && $id === $metadata['origin_id']) { - return $this->get($serviceId, $invalidBehavior); - } - } + + return $this->get($this->privateOriginIds[$id]); } if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { @@ -349,11 +345,12 @@ public function reset() public function getServiceIds() { $ids = array(); + $reversedPrivateOriginIds = array_flip($this->privateOriginIds); foreach (get_class_methods($this) as $method) { if (preg_match('/^get(.+)Service$/', $method, $match)) { $id = self::underscore($match[1]); - if ($this->isPrivateService($id)) { - $id = $this->getServiceOriginId($id) ?: $id; + if (isset($reversedPrivateOriginIds[$id])) { + $id = $reversedPrivateOriginIds[$id]; } $ids[] = $id; } @@ -387,16 +384,6 @@ public static function underscore($id) return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), str_replace('_', '.', $id))); } - final protected function isPrivateService($id) - { - return isset($this->serviceMetadata[$id]['private']) && true === $this->serviceMetadata[$id]['private']; - } - - final protected function getServiceOriginId($id) - { - return isset($this->serviceMetadata[$id]['origin_id']) ? $this->serviceMetadata[$id]['origin_id'] : null; - } - private function __clone() { } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 5c422da3503b8..17e20b6955863 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -541,17 +541,11 @@ public function compile() $compiler->compile($this); $this->compiled = true; - $this->serviceMetadata = array(); + $this->privateOriginIds = array(); foreach ($this->definitions as $id => $definition) { - if (!$definition->isPublic()) { - $this->serviceMetadata[$id]['private'] = true; - } if (null !== $originId = $definition->getOriginId()) { - $this->serviceMetadata[$id]['origin_id'] = $originId; - if (!$definition->isPublic()) { - $this->serviceMetadata[$originId]['private'] = true; - } + $this->privateOriginIds[$originId] = $id; } if ($this->trackResources && $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) { $this->addClassResource(new \ReflectionClass($class)); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 5b8058e6d8f8e..3c607a91b3999 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -804,7 +804,7 @@ public function __construct() EOF; $code .= $this->addMethodMap(); - $code .= $this->addServiceMetadata(); + $code .= $this->addPrivateOriginIdServices(); $code .= $this->addAliases(); $code .= <<<'EOF' @@ -839,7 +839,7 @@ public function __construct() $code .= "\n \$this->services = array();\n"; $code .= $this->addMethodMap(); - $code .= $this->addServiceMetadata(); + $code .= $this->addPrivateOriginIdServices(); $code .= $this->addAliases(); $code .= <<<'EOF' @@ -895,7 +895,7 @@ private function addMethodMap() * * @return string */ - private function addServiceMetadata() + private function addPrivateOriginIdServices() { if (!$definitions = $this->container->getDefinitions()) { return ''; @@ -904,30 +904,8 @@ private function addServiceMetadata() $code = ''; ksort($definitions); foreach ($definitions as $id => $definition) { - $metadata = array(); - if (!$definition->isPublic()) { - $metadata['private'] = true; - } if (null !== $originId = $definition->getOriginId()) { - $metadata['origin_id'] = $originId; - } - if ($metadata) { - $code .= ' '.var_export($id, true)." => array(\n"; - foreach ($metadata as $k => $v) { - $code .= ' '.var_export($k, true).' => '.var_export($v, true).",\n"; - } - $code .= " ),\n"; - } - if (null === $originId) { - continue; - } - unset($metadata['origin_id']); - if ($metadata) { - $code .= ' '.var_export($originId, true)." => array(\n"; - foreach ($metadata as $k => $v) { - $code .= ' '.var_export($k, true).' => '.var_export($v, true).",\n"; - } - $code .= " ),\n"; + $code .= ' '.var_export($originId, true).' => '.var_export($id, true).",\n"; } } @@ -935,7 +913,7 @@ private function addServiceMetadata() return ''; } - $out = " \$this->serviceMetadata = array(\n"; + $out = " \$this->privateOriginIds = array(\n"; $out .= $code; $out .= " );\n"; diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php index 89a1cda6cf129..2392bcb833d2b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php @@ -400,15 +400,7 @@ public function __construct() $this->__internal = new \stdClass(); $this->__sharing_internal = new \stdClass(); $this->aliases = array('alias' => 'bar'); - $this->serviceMetadata = array( - 'semirandom_internal' => array( - 'private' => true, - 'origin_id' => 'internal', - ), - 'internal' => array( - 'private' => true, - ), - ); + $this->privateOriginIds = array('internal' => 'semirandom_internal'); } protected function getSemirandomInternalService() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php index bfb024add2870..4648bb45d099c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php @@ -52,26 +52,6 @@ public function __construct() 'shared_private_dep1' => 'getSharedPrivateDep1Service', 'shared_private_dep2' => 'getSharedPrivateDep2Service', ); - $this->serviceMetadata = array( - 'configurator_service' => array( - 'private' => true, - ), - 'configurator_service_simple' => array( - 'private' => true, - ), - 'factory_simple' => array( - 'private' => true, - ), - 'inlined' => array( - 'private' => true, - ), - 'new_factory' => array( - 'private' => true, - ), - 'shared_private' => array( - 'private' => true, - ), - ); $this->aliases = array( 'alias_for_alias' => 'foo', 'alias_for_foo' => 'foo', diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index 5da9ca3a62d17..733cb6812e86a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -48,14 +48,8 @@ public function __construct() 'shared_private_dep1' => 'getSharedPrivateDep1Service', 'shared_private_dep2' => 'getSharedPrivateDep2Service', ); - $this->serviceMetadata = array( - 'semirandom_shared_private' => array( - 'private' => true, - 'origin_id' => 'shared_private', - ), - 'shared_private' => array( - 'private' => true, - ), + $this->privateOriginIds = array( + 'shared_private' => 'semirandom_shared_private', ); $this->aliases = array( 'alias_for_alias' => 'foo', @@ -341,6 +335,19 @@ protected function getRequestService() throw new RuntimeException('You have requested a synthetic service ("request"). The DIC does not know how to construct this service.'); } + /** + * Gets the 'semirandom_shared_private' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @return \stdClass A stdClass instance + */ + protected function getSemirandomSharedPrivateService() + { + return $this->services['semirandom_shared_private'] = new \stdClass(); + } + /** * Gets the 'service_from_static_method' service. * @@ -388,23 +395,6 @@ protected function getSharedPrivateDep2Service() return $instance; } - /** - * Gets the 'semirandom_shared_private' service. - * - * This service is shared. - * This method always returns the same instance of the service. - * - * This service is private. - * If you want to be able to request this service from the container directly, - * make it public, otherwise you might end up with broken code. - * - * @return \stdClass A stdClass instance - */ - protected function getSemirandomSharedPrivateService() - { - return $this->services['semirandom_shared_private'] = new \stdClass(); - } - /** * {@inheritdoc} */ From e62470efbf74aa7bd7c382bfcc912d13ca3aadf0 Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sun, 21 Aug 2016 14:49:58 +0000 Subject: [PATCH 5/5] guarantee BC --- .../RandomizePrivateServiceIdentifiers.php | 8 +++++-- .../DependencyInjection/Container.php | 22 +++++++++---------- .../DependencyInjection/ContainerBuilder.php | 5 ----- .../DependencyInjection/Definition.php | 11 ---------- .../DependencyInjection/Dumper/PhpDumper.php | 21 ++++++++++-------- .../Tests/ContainerTest.php | 2 +- .../Tests/Fixtures/php/services9_compiled.php | 2 +- 7 files changed, 31 insertions(+), 40 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php index f89995dd6b023..d472a66a62936 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RandomizePrivateServiceIdentifiers.php @@ -27,12 +27,11 @@ class RandomizePrivateServiceIdentifiers implements CompilerPassInterface public function process(ContainerBuilder $container) { - // mark randomized definitions + build id map + // update private definitions + build id map $this->idMap = array(); foreach ($container->getDefinitions() as $id => $definition) { if (!$definition->isPublic()) { $this->idMap[$id] = $this->randomizer ? (string) call_user_func($this->randomizer, $id) : hash('sha256', mt_rand().$id); - $definition->setOriginId($id); $definition->setPublic(true); } } @@ -62,6 +61,11 @@ public function process(ContainerBuilder $container) $container->setAlias(new Alias($this->idMap[$oldId], $alias->isPublic()), $this->idMap[$oldId]); } } + + // for BC + $reflectionProperty = new \ReflectionProperty(ContainerBuilder::class, 'privates'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($container, $this->idMap); } public function setRandomizer(callable $randomizer = null) diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 2385f4d77f222..345bf0f162f10 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -65,9 +65,9 @@ class Container implements ResettableContainerInterface protected $services = array(); protected $methodMap = array(); + protected $privates = array(); // for BC protected $aliases = array(); protected $loading = array(); - protected $privateOriginIds = array(); // for BC private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_'); @@ -172,13 +172,13 @@ public function set($id, $service) unset($this->aliases[$id]); } - if (isset($this->privateOriginIds[$id])) { + if (isset($this->privates[$id])) { if (null === $service) { @trigger_error(sprintf('Unsetting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); - unset($this->privateOriginIds[$id]); + unset($this->privates[$id]); } else { @trigger_error(sprintf('Setting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0. A new public service will be created instead.', $id), E_USER_DEPRECATED); - $id = $this->privateOriginIds[$id]; + $id = $this->privates[$id]; } } @@ -209,9 +209,9 @@ public function has($id) if (--$i && $id !== $lcId = strtolower($id)) { $id = $lcId; } else { - if (isset($this->privateOriginIds[$id])) { + if (isset($this->privates[$id])) { @trigger_error(sprintf('Checking for the existence of the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); - $id = $this->privateOriginIds[$id]; + $id = $this->privates[$id]; } return method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service'); @@ -266,10 +266,10 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE } elseif (method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) { // $method is set to the right value, proceed } else { - if (isset($this->privateOriginIds[$id])) { + if (isset($this->privates[$id])) { @trigger_error(sprintf('Requesting the "%s" private service is deprecated since Symfony 3.2 and won\'t be supported anymore in Symfony 4.0.', $id), E_USER_DEPRECATED); - return $this->get($this->privateOriginIds[$id]); + return $this->get($this->privates[$id]); } if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { @@ -345,12 +345,12 @@ public function reset() public function getServiceIds() { $ids = array(); - $reversedPrivateOriginIds = array_flip($this->privateOriginIds); + $reversedPrivates = array_flip($this->privates); foreach (get_class_methods($this) as $method) { if (preg_match('/^get(.+)Service$/', $method, $match)) { $id = self::underscore($match[1]); - if (isset($reversedPrivateOriginIds[$id])) { - $id = $reversedPrivateOriginIds[$id]; + if (isset($reversedPrivates[$id])) { + $id = $reversedPrivates[$id]; } $ids[] = $id; } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 17e20b6955863..13b049aa9cd77 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -541,12 +541,7 @@ public function compile() $compiler->compile($this); $this->compiled = true; - $this->privateOriginIds = array(); - foreach ($this->definitions as $id => $definition) { - if (null !== $originId = $definition->getOriginId()) { - $this->privateOriginIds[$originId] = $id; - } if ($this->trackResources && $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) { $this->addClassResource(new \ReflectionClass($class)); } diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index e9edb7c91b030..b697f02708e1f 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -38,7 +38,6 @@ class Definition private $decoratedService; private $autowired = false; private $autowiringTypes = array(); - private $originId; protected $arguments; @@ -52,16 +51,6 @@ public function __construct($class = null, array $arguments = array()) $this->arguments = $arguments; } - public function setOriginId($id) - { - $this->originId = $id; - } - - public function getOriginId() - { - return $this->originId; - } - /** * Sets a factory. * diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 3c607a91b3999..428259faf010c 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -804,7 +804,7 @@ public function __construct() EOF; $code .= $this->addMethodMap(); - $code .= $this->addPrivateOriginIdServices(); + $code .= $this->addPrivateServices(); $code .= $this->addAliases(); $code .= <<<'EOF' @@ -839,7 +839,7 @@ public function __construct() $code .= "\n \$this->services = array();\n"; $code .= $this->addMethodMap(); - $code .= $this->addPrivateOriginIdServices(); + $code .= $this->addPrivateServices(); $code .= $this->addAliases(); $code .= <<<'EOF' @@ -891,29 +891,32 @@ private function addMethodMap() } /** - * Adds the service metadata property definition. + * Adds the privates property definition. + * + * This method is intented for BC only. * * @return string */ - private function addPrivateOriginIdServices() + private function addPrivateServices() { if (!$definitions = $this->container->getDefinitions()) { return ''; } + $reflectionProperty = new \ReflectionProperty(ContainerBuilder::class, 'privates'); + $reflectionProperty->setAccessible(true); + $code = ''; ksort($definitions); - foreach ($definitions as $id => $definition) { - if (null !== $originId = $definition->getOriginId()) { - $code .= ' '.var_export($originId, true).' => '.var_export($id, true).",\n"; - } + foreach ($reflectionProperty->getValue($this->container) as $originId => $id) { + $code .= ' '.var_export($originId, true).' => '.var_export($id, true).",\n"; } if (empty($code)) { return ''; } - $out = " \$this->privateOriginIds = array(\n"; + $out = " \$this->privates = array(\n"; $out .= $code; $out .= " );\n"; diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php index 2392bcb833d2b..93741de90aec5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php @@ -400,7 +400,7 @@ public function __construct() $this->__internal = new \stdClass(); $this->__sharing_internal = new \stdClass(); $this->aliases = array('alias' => 'bar'); - $this->privateOriginIds = array('internal' => 'semirandom_internal'); + $this->privates = array('internal' => 'semirandom_internal'); } protected function getSemirandomInternalService() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index 733cb6812e86a..15a8ac248a465 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -48,7 +48,7 @@ public function __construct() 'shared_private_dep1' => 'getSharedPrivateDep1Service', 'shared_private_dep2' => 'getSharedPrivateDep2Service', ); - $this->privateOriginIds = array( + $this->privates = array( 'shared_private' => 'semirandom_shared_private', ); $this->aliases = array(