From f503ad8026457d91a503c86f6113589be1183db4 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 30 Mar 2013 00:21:12 +0100 Subject: [PATCH 01/21] First implementation of lazy services via proxy manager --- composer.json | 1 + .../DependencyInjection/ContainerBuilder.php | 19 +++++++++++- .../DependencyInjection/Definition.php | 30 +++++++++++++++++++ .../DependencyInjection/Dumper/XmlDumper.php | 3 ++ .../DependencyInjection/Dumper/YamlDumper.php | 4 +++ .../Loader/XmlFileLoader.php | 2 +- .../Loader/YamlFileLoader.php | 4 +++ .../schema/dic/services/services-1.0.xsd | 1 + .../Tests/ContainerBuilderTest.php | 24 +++++++++++++++ .../Tests/DefinitionTest.php | 12 ++++++++ .../Tests/Fixtures/xml/services6.xml | 2 +- .../Tests/Fixtures/yaml/services6.yml | 1 + .../Tests/Loader/XmlFileLoaderTest.php | 1 + .../Tests/Loader/YamlFileLoaderTest.php | 1 + 14 files changed, 102 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 504b48d92c76a..a102f4705c448 100644 --- a/composer.json +++ b/composer.json @@ -61,6 +61,7 @@ "symfony/yaml": "self.version" }, "require-dev": { + "ocramius/proxy-manager": "0.2.*", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.2", "doctrine/orm": "~2.2,>=2.2.3", diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 5d8d0d1a10f15..9ce9a18595635 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -11,6 +11,9 @@ namespace Symfony\Component\DependencyInjection; +use ProxyManager\Configuration; +use ProxyManager\Factory\LazyLoadingValueHolderFactory; +use ProxyManager\Proxy\LazyLoadingInterface; use Symfony\Component\DependencyInjection\Compiler\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -873,8 +876,22 @@ public function findDefinition($id) * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable */ - private function createService(Definition $definition, $id) + public function createService(Definition $definition, $id, $tryProxy = true) { + if ($tryProxy && ($className = $definition->getClass()) && $definition->isLazy()) { + $factory = new LazyLoadingValueHolderFactory(new Configuration()); + $container = $this; + + return $factory->createProxy( + $className, + function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($container, $definition, $id) { + $proxy->setProxyInitializer(null); + + $wrappedInstance = $container->createService($definition, $id, false); + } + ); + } + if ($definition->isSynthetic()) { throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id)); } diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index 9d52426121874..803dc1640d785 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -37,6 +37,7 @@ class Definition private $synthetic; private $abstract; private $synchronized; + private $lazy; protected $arguments; @@ -58,6 +59,7 @@ public function __construct($class = null, array $arguments = array()) $this->public = true; $this->synthetic = false; $this->synchronized = false; + $this->lazy = false; $this->abstract = false; $this->properties = array(); } @@ -599,6 +601,34 @@ public function isSynchronized() return $this->synchronized; } + /** + * Sets the lazy flag of this service. + * + * @param Boolean $lazy + * + * @return Definition The current instance + * + * @api + */ + public function setLazy($lazy) + { + $this->lazy = (Boolean) $lazy; + + return $this; + } + + /** + * Whether this service is lazy. + * + * @return Boolean + * + * @api + */ + public function isLazy() + { + return $this->lazy; + } + /** * Sets whether this definition is synthetic, that is not constructed by the * container, but dynamically injected. diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php index da2e2c4cd80be..a5ceb2c68d3c3 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php @@ -133,6 +133,9 @@ private function addService($definition, $id, \DOMElement $parent) if ($definition->isSynchronized()) { $service->setAttribute('synchronized', 'true'); } + if ($definition->isLazy()) { + $service->setAttribute('lazy', 'true'); + } foreach ($definition->getTags() as $name => $tags) { foreach ($tags as $attributes) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index bc10c4a7b4e17..0059f0d0d7388 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -106,6 +106,10 @@ private function addService($id, $definition) $code .= sprintf(" factory_class: %s\n", $definition->getFactoryClass()); } + if ($definition->isLazy()) { + $code .= sprintf(" lazy: true\n"); + } + if ($definition->getFactoryMethod()) { $code .= sprintf(" factory_method: %s\n", $definition->getFactoryMethod()); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 85898d3d3fe86..9f25ab7683cd2 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -148,7 +148,7 @@ private function parseDefinition($id, $service, $file) $definition = new Definition(); } - foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'synchronized', 'abstract') as $key) { + foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'synchronized', 'lazy', 'abstract') as $key) { if (isset($service[$key])) { $method = 'set'.str_replace('-', '', $key); $definition->$method((string) $service->getAttributeAsPhp($key)); diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index b95280dfcfe5f..cf68a33756747 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -157,6 +157,10 @@ private function parseDefinition($id, $service, $file) $definition->setSynchronized($service['synchronized']); } + if (isset($service['lazy'])) { + $definition->setLazy($service['lazy']); + } + if (isset($service['public'])) { $definition->setPublic($service['public']); } 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 4d9addcd971f8..f1c2003c62258 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 @@ -87,6 +87,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 7d2cb278871cc..1e22d3e778843 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -263,6 +263,30 @@ public function testCreateService() $this->assertInstanceOf('\FooClass', $builder->get('foo2'), '->createService() replaces parameters in the file provided by the service definition'); } + /** + * @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService + */ + public function testCreateServiceWithDelegateFactory() + { + $builder = new ContainerBuilder(); + + $builder->register('foo1', 'FooClass')->setFile(__DIR__.'/Fixtures/includes/foo.php'); + $builder->getDefinition('foo1')->setLazy(true); + + /* @var $foo1 \ProxyManager\Proxy\LazyLoadingInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $foo1 = $builder->get('foo1'); + + $this->assertInstanceOf('\FooClass', $foo1); + $this->assertInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1); + $this->assertFalse($foo1->isProxyInitialized()); + + $foo1->initializeProxy(); + + $this->assertTrue($foo1->isProxyInitialized()); + $this->assertInstanceOf('\FooClass', $foo1->getWrappedValueHolderValue()); + $this->assertNotInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1->getWrappedValueHolderValue()); + } + /** * @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php index d9a4282efefbc..d41c6a8e2b2ae 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php @@ -164,6 +164,18 @@ public function testSetIsSynchronized() $this->assertTrue($def->isSynchronized(), '->isSynchronized() returns true if the service is synchronized.'); } + /** + * @covers Symfony\Component\DependencyInjection\Definition::setLazy + * @covers Symfony\Component\DependencyInjection\Definition::isLazy + */ + public function testSetIsLazy() + { + $def = new Definition('stdClass'); + $this->assertFalse($def->isLazy(), '->isLazy() returns false by default'); + $this->assertSame($def, $def->setLazy(true), '->setLazy() implements a fluent interface'); + $this->assertTrue($def->isLazy(), '->isLazy() returns true if the service is lazy.'); + } + /** * @covers Symfony\Component\DependencyInjection\Definition::setAbstract * @covers Symfony\Component\DependencyInjection\Definition::isAbstract diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml index 4d2aa3d79ae24..abd9fbc1529b1 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml @@ -46,6 +46,6 @@ - + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml index 820c364a06556..7ba9453bdd6dd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml @@ -28,3 +28,4 @@ services: class: Request synthetic: true synchronized: true + lazy: true diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index b355f0ac215c3..d8138f947541a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -187,6 +187,7 @@ public function testLoadServices() $this->assertTrue($services['request']->isSynthetic(), '->load() parses the synthetic flag'); $this->assertTrue($services['request']->isSynchronized(), '->load() parses the synchronized flag'); + $this->assertTrue($services['request']->isLazy(), '->load() parses the lazy flag'); $aliases = $container->getAliases(); $this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses elements'); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 290d6628acf5b..e452e5d221d19 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -130,6 +130,7 @@ public function testLoadServices() $this->assertTrue($services['request']->isSynthetic(), '->load() parses the synthetic flag'); $this->assertTrue($services['request']->isSynchronized(), '->load() parses the synchronized flag'); + $this->assertTrue($services['request']->isLazy(), '->load() parses the lazy flag'); $aliases = $container->getAliases(); $this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses aliases'); From 67aef456ba5c7d4a2e8ba6b1170d9f6e1cfde25e Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 30 Mar 2013 01:23:57 +0100 Subject: [PATCH 02/21] Adding basic logic to generate proxy instantiation into a php dumped container --- .../DependencyInjection/ContainerBuilder.php | 2 + .../DependencyInjection/Dumper/PhpDumper.php | 62 ++++++++++++++++- .../Tests/Dumper/PhpDumperTest.php | 14 ++++ .../Tests/Fixtures/php/lazy_service.php | 66 +++++++++++++++++++ 4 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 9ce9a18595635..4e3edcb352f69 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -888,6 +888,8 @@ function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($container, $def $proxy->setProxyInitializer(null); $wrappedInstance = $container->createService($definition, $id, false); + + return true; } ); } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 294de23a7773d..88a627fef4de0 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -149,6 +149,51 @@ private function addServiceLocalTempVariables($cId, $definition) return $code; } + /** + * Generates the logic required for proxy lazy loading + * + * @param string $id The service id + * @param Definition $definition + * + * @return string + */ + private function addProxyLoading($id, Definition $definition) + { + if (!($definition->isLazy() && $definition->getClass())) { + return ''; + } + + $class = $this->dumpValue($definition->getClass()); + + if (0 === strpos($class, "'") && !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + return ''; + } + + // @todo this should happen directly through the factory class, but we have to ensure that the proxy + // @todo class is generated during the dump process + $methodName = 'get' . Container::camelize($id) . 'Service'; + + return <<createProxy( + $class, + function (& \$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$container) { + \$proxy->setProxyInitializer(null); + + \$wrappedInstance = \$container->$methodName(false); + + return true; + } + ); + } + + +EOF; + } + /** * Generates the require_once statement for service includes. * @@ -483,18 +528,29 @@ private function addService($id, $definition) EOF; } - $code = <<isLazy()) { + $lazyInitialization = '$lazyLoad = true'; + $lazyInitializationDoc = "\n * @param boolean \$lazyLoad whether to try lazy-loading the" + . " service with a proxy\n *"; + } else { + $lazyInitialization = ''; + $lazyInitializationDoc = ''; + } + + $code = <<addProxyLoading($id, $definition); + if (!in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) { $code .= <<scopedServices['$scope'])) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 72d587ff070fc..767635aebc03d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -52,6 +52,20 @@ public function testDumpFrozenContainerWithNoParameter() $this->assertNotRegexp("/function getDefaultParameters\(/", $dumpedString, '->dump() does not add getDefaultParameters() method definition.'); } + public function testDumpContainerWithProxyService() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass'); + $container->getDefinition('foo')->setLazy(true); + + $container->compile(); + + $dumper = new PhpDumper($container); + + $dumpedString = $dumper->dump(); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_service.php', $dumpedString, '->dump() does generate proxy lazy loading logic.'); + } + public function testDumpOptimizationString() { $definition = new Definition(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php new file mode 100644 index 0000000000000..9f4eafd0c0fa3 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php @@ -0,0 +1,66 @@ +services = + $this->scopedServices = + $this->scopeStacks = array(); + + $this->set('service_container', $this); + + $this->scopes = array(); + $this->scopeChildren = array(); + } + + /** + * Gets the 'foo' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @param boolean $lazyLoad whether to try lazy-loading the service with a proxy + * + * @return stdClass A stdClass instance. + */ + protected function getFooService($lazyLoad = true) + { + if ($lazyLoad) { + $factory = new \ProxyManager\Factory\LazyLoadingValueHolderFactory(new \ProxyManager\Configuration()); + $container = $this; + + return $factory->createProxy( + 'stdClass', + function (& $wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { + $proxy->setProxyInitializer(null); + + $wrappedInstance = $container->getFooService(false); + + return true; + } + ); + } + + return $this->services['foo'] = new \stdClass(); + } +} From f4a19c7d0500805c527066da10b7d916242e346e Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 30 Mar 2013 02:54:45 +0100 Subject: [PATCH 03/21] Compiling proxies into the generated DIC file --- .../DependencyInjection/Dumper/PhpDumper.php | 48 ++++- .../Tests/Dumper/PhpDumperTest.php | 2 +- .../Tests/Fixtures/php/lazy_service.php | 66 ------ .../Tests/Fixtures/php/lazy_service.txt | 190 ++++++++++++++++++ 4 files changed, 235 insertions(+), 71 deletions(-) delete mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 88a627fef4de0..a418b0a92e5a8 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -11,6 +11,10 @@ namespace Symfony\Component\DependencyInjection\Dumper; +use CG\Core\DefaultGeneratorStrategy; +use CG\Generator\PhpClass; +use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator; +use ReflectionClass; use Symfony\Component\DependencyInjection\Variable; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -94,7 +98,8 @@ public function dump(array $options = array()) $code .= $this->addServices(). $this->addDefaultParametersMethod(). - $this->endClass() + $this->endClass(). + $this->addProxyClasses() ; return $code; @@ -172,14 +177,13 @@ private function addProxyLoading($id, Definition $definition) // @todo this should happen directly through the factory class, but we have to ensure that the proxy // @todo class is generated during the dump process $methodName = 'get' . Container::camelize($id) . 'Service'; + $proxyClass = str_replace('\\', '', $definition->getClass()) . '_' . md5(spl_object_hash($definition)); return <<createProxy( - $class, + return new $proxyClass( function (& \$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$container) { \$proxy->setProxyInitializer(null); @@ -194,6 +198,42 @@ function (& \$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) EOF; } + /** + * Generates code for the proxy classes to be attached after the container class + * + * @return string + */ + private function addProxyClasses() + { + $definitions = $this->container->getDefinitions(); + + ksort($definitions); + + $proxyDefinitions = array_filter( + $this->container->getDefinitions(), + function (Definition $definition) { + return $definition->isLazy() && $definition->getClass(); + } + ); + + $proxyGenerator = new LazyLoadingValueHolderGenerator(); + $classGenerator = new DefaultGeneratorStrategy(); + $code = ''; + + /* @var $proxyDefinitions Definition[] */ + foreach ($proxyDefinitions as $definition) { + $phpClass = new PhpClass( + str_replace('\\', '', $definition->getClass()) . '_' . md5(spl_object_hash($definition)) + ); + + $proxyGenerator->generate(new ReflectionClass($definition->getClass()), $phpClass); + + $code .= "\n" . $classGenerator->generate($phpClass); + } + + return $code; + } + /** * Generates the require_once statement for service includes. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 767635aebc03d..fe1580dfc464f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -63,7 +63,7 @@ public function testDumpContainerWithProxyService() $dumper = new PhpDumper($container); $dumpedString = $dumper->dump(); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_service.php', $dumpedString, '->dump() does generate proxy lazy loading logic.'); + $this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/lazy_service.txt', $dumpedString, '->dump() does generate proxy lazy loading logic.'); } public function testDumpOptimizationString() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php deleted file mode 100644 index 9f4eafd0c0fa3..0000000000000 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php +++ /dev/null @@ -1,66 +0,0 @@ -services = - $this->scopedServices = - $this->scopeStacks = array(); - - $this->set('service_container', $this); - - $this->scopes = array(); - $this->scopeChildren = array(); - } - - /** - * Gets the 'foo' service. - * - * This service is shared. - * This method always returns the same instance of the service. - * - * @param boolean $lazyLoad whether to try lazy-loading the service with a proxy - * - * @return stdClass A stdClass instance. - */ - protected function getFooService($lazyLoad = true) - { - if ($lazyLoad) { - $factory = new \ProxyManager\Factory\LazyLoadingValueHolderFactory(new \ProxyManager\Configuration()); - $container = $this; - - return $factory->createProxy( - 'stdClass', - function (& $wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { - $proxy->setProxyInitializer(null); - - $wrappedInstance = $container->getFooService(false); - - return true; - } - ); - } - - return $this->services['foo'] = new \stdClass(); - } -} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt new file mode 100644 index 0000000000000..1a27b5ae0f3cd --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt @@ -0,0 +1,190 @@ +services = + $this->scopedServices = + $this->scopeStacks = array(); + + $this->set('service_container', $this); + + $this->scopes = array(); + $this->scopeChildren = array(); + } + + /** + * Gets the 'foo' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @param boolean $lazyLoad whether to try lazy-loading the service with a proxy + * + * @return stdClass A stdClass instance. + */ + protected function getFooService($lazyLoad = true) + { + if ($lazyLoad) { + $container = $this; + + return new stdClass_%s( + function (& $wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { + $proxy->setProxyInitializer(null); + + $wrappedInstance = $container->getFooService(false); + + return true; + } + ); + } + + return $this->services['foo'] = new \stdClass(); + } +} + +class %s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface +{ + /** + * @var \Closure|null initializer responsible for generating the wrapped object + */ + private $%s; + /** + * @var \Closure|null initializer responsible for generating the wrapped object + */ + private $%s; + + /** + * {@inheritDoc} + */ + public function setProxyInitializer(\Closure $initializer = NULL) + { + $this->%s = $initializer; + } + + /** + * {@inheritDoc} + */ + public function isProxyInitialized() + { + return null !== $this->%s; + } + + /** + * {@inheritDoc} + */ + public function initializeProxy() + { + return $this->%s && $this->%s->__invoke($this->%s, $this, 'initializeProxy', array()); + } + + /** + * {@inheritDoc} + */ + public function getWrappedValueHolderValue() + { + return $this->%s; + } + + /** + * {@inheritDoc} + */ + public function getProxyInitializer() + { + return $this->%s; + } + + /** + */ + public function __wakeup() + { + } + + /** + * @param string $name + */ + public function __unset($name) + { + $this->%s && $this->%s->__invoke($this->%s, $this, '__unset', array('name' => $name)); + + unset($this->%s->$name); + } + + /** + */ + public function __sleep() + { + $this->%s && $this->%s->__invoke($this->%s, $this, '__sleep', array()); + + return array('%s'); + } + + /** + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->%s && $this->%s->__invoke($this->%s, $this, '__set', array('name' => $name, 'value' => $value)); + + $this->%s->$name = $value; + } + + /** + * @param string $name + */ + public function __isset($name) + { + $this->%s && $this->%s->__invoke($this->%s, $this, '__isset', array('name' => $name)); + + return isset($this->%s->$name); + } + + /** + * @param string $name + */ + public function __get($name) + { + $this->%s && $this->%s->__invoke($this->%s, $this, '__get', array('name' => $name)); + + return $this->%s->$name; + } + + /** + * @override constructor for lazy initialization + * @param \Closure|null $initializer + */ + public function __construct($initializer) + { + $this->%s = $initializer; + } + + /** + */ + public function __clone() + { + $this->%s && $this->%s->__invoke($this->%s, $this, '__clone', array()); + + $this->%s = clone $this->%s; + } +} \ No newline at end of file From d2760f126a8d1f39b06e0b257fbd915e1064df7c Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 30 Mar 2013 11:56:42 +0100 Subject: [PATCH 04/21] Upgrading dependency to ProxyManager 0.3.* --- composer.json | 4 +- .../DependencyInjection/Dumper/PhpDumper.php | 13 ++- .../Tests/Fixtures/php/lazy_service.txt | 106 ++++++++++-------- .../DependencyInjection/composer.json | 3 +- 4 files changed, 70 insertions(+), 56 deletions(-) diff --git a/composer.json b/composer.json index a102f4705c448..bf74f0864c187 100644 --- a/composer.json +++ b/composer.json @@ -61,13 +61,13 @@ "symfony/yaml": "self.version" }, "require-dev": { - "ocramius/proxy-manager": "0.2.*", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.2", "doctrine/orm": "~2.2,>=2.2.3", "monolog/monolog": "~1.3", "propel/propel1": "1.6.*", - "ircmaxell/password-compat": "1.0.*" + "ircmaxell/password-compat": "1.0.*", + "ocramius/proxy-manager": "0.3.*" }, "autoload": { "psr-0": { "Symfony\\": "src/" }, diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index a418b0a92e5a8..02866e3ab753e 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -11,8 +11,8 @@ namespace Symfony\Component\DependencyInjection\Dumper; -use CG\Core\DefaultGeneratorStrategy; -use CG\Generator\PhpClass; +use ProxyManager\Generator\ClassGenerator; +use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator; use ReflectionClass; use Symfony\Component\DependencyInjection\Variable; @@ -216,13 +216,18 @@ function (Definition $definition) { } ); + if (empty($proxyDefinitions)) { + // avoids hard dependency to ProxyManager + return ''; + } + $proxyGenerator = new LazyLoadingValueHolderGenerator(); - $classGenerator = new DefaultGeneratorStrategy(); + $classGenerator = new BaseGeneratorStrategy(); $code = ''; /* @var $proxyDefinitions Definition[] */ foreach ($proxyDefinitions as $definition) { - $phpClass = new PhpClass( + $phpClass = new ClassGenerator( str_replace('\\', '', $definition->getClass()) . '_' . md5(spl_object_hash($definition)) ); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt index 1a27b5ae0f3cd..43d5541451bc2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt @@ -63,61 +63,58 @@ class ProjectServiceContainer extends Container } } -class %s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface +class stdClass_%s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface { + /** * @var \Closure|null initializer responsible for generating the wrapped object */ - private $%s; + private %s = null; + /** * @var \Closure|null initializer responsible for generating the wrapped object */ - private $%s; + private %s = null; /** - * {@inheritDoc} + * @override constructor for lazy initialization + * + * @param \Closure|null $initializer */ - public function setProxyInitializer(\Closure $initializer = NULL) + public function __construct($initializer) { $this->%s = $initializer; } /** - * {@inheritDoc} + * @param string $name */ - public function isProxyInitialized() + public function __get($name) { - return null !== $this->%s; - } + $this->%s && $this->%s->__invoke($this->%s, $this, '__get', array('name' => $name)); - /** - * {@inheritDoc} - */ - public function initializeProxy() - { - return $this->%s && $this->%s->__invoke($this->%s, $this, 'initializeProxy', array()); + return $this->%s->$name; } /** - * {@inheritDoc} + * @param string $name + * @param mixed $value */ - public function getWrappedValueHolderValue() + public function __set($name, $value) { - return $this->%s; - } + $this->%s && $this->%s->__invoke($this->%s, $this, '__set', array('name' => $name, 'value' => $value)); - /** - * {@inheritDoc} - */ - public function getProxyInitializer() - { - return $this->%s; + $this->%s->$name = $value; } /** + * @param string $name */ - public function __wakeup() + public function __isset($name) { + $this->%s && $this->%s->__invoke($this->%s, $this, '__isset', array('name' => $name)); + + return isset($this->%s->$name); } /** @@ -131,6 +128,17 @@ class %s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, } /** + * + */ + public function __clone() + { + $this->%s && $this->%s->__invoke($this->%s, $this, '__clone', array()); + + $this->%s = clone $this->%s; + } + + /** + * */ public function __sleep() { @@ -140,51 +148,51 @@ class %s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, } /** - * @param string $name - * @param mixed $value + * */ - public function __set($name, $value) + public function __wakeup() { - $this->%s && $this->%s->__invoke($this->%s, $this, '__set', array('name' => $name, 'value' => $value)); - - $this->%s->$name = $value; } /** - * @param string $name + * {@inheritDoc} */ - public function __isset($name) + public function setProxyInitializer(\Closure $initializer = null) { - $this->%s && $this->%s->__invoke($this->%s, $this, '__isset', array('name' => $name)); - - return isset($this->%s->$name); + $this->%s = $initializer; } /** - * @param string $name + * {@inheritDoc} */ - public function __get($name) + public function getProxyInitializer() { - $this->%s && $this->%s->__invoke($this->%s, $this, '__get', array('name' => $name)); - - return $this->%s->$name; + return $this->%s; } /** - * @override constructor for lazy initialization - * @param \Closure|null $initializer + * {@inheritDoc} */ - public function __construct($initializer) + public function initializeProxy() { - $this->%s = $initializer; + return $this->%s && $this->%s->__invoke($this->%s, $this, 'initializeProxy', array()); } /** + * {@inheritDoc} */ - public function __clone() + public function isProxyInitialized() { - $this->%s && $this->%s->__invoke($this->%s, $this, '__clone', array()); + return null !== $this->%s; + } - $this->%s = clone $this->%s; + /** + * {@inheritDoc} + */ + public function getWrappedValueHolderValue() + { + return $this->%s; } + + } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index 887686747eda3..d817b9b9b04fd 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -20,7 +20,8 @@ }, "require-dev": { "symfony/yaml": "~2.0", - "symfony/config": ">=2.2,<2.4-dev" + "symfony/config": ">=2.2,<2.4-dev", + "ocramius/proxy-manager": "0.3.*" }, "suggest": { "symfony/yaml": "2.2.*", From 4a13f822cd93aa0a0cff75a9504bd658099b4fa8 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Mar 2013 07:26:17 +0200 Subject: [PATCH 05/21] Suggesting ProxyManager in composer.json, removing useless calls --- .../DependencyInjection/ContainerBuilder.php | 25 ++++++++++++++----- .../DependencyInjection/Dumper/PhpDumper.php | 11 +++----- .../DependencyInjection/composer.json | 5 ++-- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 4e3edcb352f69..9370f690b2b09 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -13,6 +13,7 @@ use ProxyManager\Configuration; use ProxyManager\Factory\LazyLoadingValueHolderFactory; +use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\Proxy\LazyLoadingInterface; use Symfony\Component\DependencyInjection\Compiler\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; @@ -868,6 +869,7 @@ public function findDefinition($id) * * @param Definition $definition A service definition instance * @param string $id The service identifier + * @param Boolean $tryProxy Whether to try proxying the service with a lazy proxy * * @return object The service described by the service definition * @@ -875,11 +877,26 @@ public function findDefinition($id) * @throws RuntimeException When the factory definition is incomplete * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable + * + * @internal this method is public because of PHP 5.3 limitations, do not use it explicitly in your code */ public function createService(Definition $definition, $id, $tryProxy = true) { - if ($tryProxy && ($className = $definition->getClass()) && $definition->isLazy()) { - $factory = new LazyLoadingValueHolderFactory(new Configuration()); + if ($definition->isSynthetic()) { + throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id)); + } + + if ( + $tryProxy + && ($className = $definition->getClass()) + && $definition->isLazy() + && class_exists('ProxyManager\\Factory\\LazyLoadingValueHolderFactory') + ) { + $config = new Configuration(); + + $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + + $factory = new LazyLoadingValueHolderFactory($config); $container = $this; return $factory->createProxy( @@ -894,10 +911,6 @@ function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($container, $def ); } - if ($definition->isSynthetic()) { - throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id)); - } - $parameterBag = $this->getParameterBag(); if (null !== $definition->getFile()) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 02866e3ab753e..c72f791f38893 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -171,11 +171,10 @@ private function addProxyLoading($id, Definition $definition) $class = $this->dumpValue($definition->getClass()); if (0 === strpos($class, "'") && !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + // provided class name is not valid return ''; } - // @todo this should happen directly through the factory class, but we have to ensure that the proxy - // @todo class is generated during the dump process $methodName = 'get' . Container::camelize($id) . 'Service'; $proxyClass = str_replace('\\', '', $definition->getClass()) . '_' . md5(spl_object_hash($definition)); @@ -205,10 +204,6 @@ function (& \$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) */ private function addProxyClasses() { - $definitions = $this->container->getDefinitions(); - - ksort($definitions); - $proxyDefinitions = array_filter( $this->container->getDefinitions(), function (Definition $definition) { @@ -216,8 +211,8 @@ function (Definition $definition) { } ); - if (empty($proxyDefinitions)) { - // avoids hard dependency to ProxyManager + // avoids hard dependency to ProxyManager + if (empty($proxyDefinitions) || !class_exists('ProxyManager\\GeneratorStrategy\\BaseGeneratorStrategy')) { return ''; } diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index d817b9b9b04fd..469811a075d48 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -21,11 +21,12 @@ "require-dev": { "symfony/yaml": "~2.0", "symfony/config": ">=2.2,<2.4-dev", - "ocramius/proxy-manager": "0.3.*" + "ocramius/proxy-manager": ">=0.3.1,<0.4.x-dev" }, "suggest": { "symfony/yaml": "2.2.*", - "symfony/config": "2.2.*" + "symfony/config": "2.2.*", + "ocramius/proxy-manager": "Generate service proxies to lazy load them" }, "autoload": { "psr-0": { "Symfony\\Component\\DependencyInjection\\": "" } From a6a65724d2b0f8a3598990d0f8ea9d4dcfb18c1b Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Mar 2013 08:17:48 +0200 Subject: [PATCH 06/21] Adding failing test to demonstrate that proxy initialization breaks shared services --- .../DependencyInjection/Tests/ContainerBuilderTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 1e22d3e778843..0fd3b0f4aebc2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -276,12 +276,14 @@ public function testCreateServiceWithDelegateFactory() /* @var $foo1 \ProxyManager\Proxy\LazyLoadingInterface|\ProxyManager\Proxy\ValueHolderInterface */ $foo1 = $builder->get('foo1'); + $this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved on multiple subsequent calls'); $this->assertInstanceOf('\FooClass', $foo1); $this->assertInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1); $this->assertFalse($foo1->isProxyInitialized()); $foo1->initializeProxy(); + $this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved after initialization'); $this->assertTrue($foo1->isProxyInitialized()); $this->assertInstanceOf('\FooClass', $foo1->getWrappedValueHolderValue()); $this->assertNotInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1->getWrappedValueHolderValue()); From 35fddedc501948880bc651863d10ec194245ceb1 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Mar 2013 08:18:19 +0200 Subject: [PATCH 07/21] Fixing shared service instance --- .../DependencyInjection/ContainerBuilder.php | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 9370f690b2b09..1dfe5c3bf3ad8 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -898,8 +898,7 @@ public function createService(Definition $definition, $id, $tryProxy = true) $factory = new LazyLoadingValueHolderFactory($config); $container = $this; - - return $factory->createProxy( + $proxy = $factory->createProxy( $className, function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($container, $definition, $id) { $proxy->setProxyInitializer(null); @@ -909,6 +908,10 @@ function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($container, $def return true; } ); + + $this->shareService($definition, $proxy, $id); + + return $proxy; } $parameterBag = $this->getParameterBag(); @@ -935,16 +938,8 @@ function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($container, $def $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); } - if (self::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { - if (self::SCOPE_CONTAINER !== $scope && !isset($this->scopedServices[$scope])) { - throw new InactiveScopeException($id, $scope); - } - - $this->services[$lowerId = strtolower($id)] = $service; - - if (self::SCOPE_CONTAINER !== $scope) { - $this->scopedServices[$scope][$lowerId] = $service; - } + if (!$definition->isLazy()) { + $this->shareService($definition, $service, $id); } foreach ($definition->getMethodCalls() as $call) { @@ -1089,4 +1084,19 @@ private function callMethod($service, $call) call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->resolveValue($call[1]))); } + + private function shareService(Definition $definition, $service, $id) + { + if (self::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { + if (self::SCOPE_CONTAINER !== $scope && !isset($this->scopedServices[$scope])) { + throw new InactiveScopeException($id, $scope); + } + + $this->services[$lowerId = strtolower($id)] = $service; + + if (self::SCOPE_CONTAINER !== $scope) { + $this->scopedServices[$scope][$lowerId] = $service; + } + } + } } From bec77744041aeef807538df113f126e8e11cac2b Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Mar 2013 08:27:10 +0200 Subject: [PATCH 08/21] Sharing services in the container should only happen when proxying failed --- src/Symfony/Component/DependencyInjection/ContainerBuilder.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 1dfe5c3bf3ad8..945b40ffa3f1a 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -938,7 +938,8 @@ function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($container, $def $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); } - if (!$definition->isLazy()) { + if ($tryProxy || !$definition->isLazy()) { + // share only if proxying failed, or if not a proxy $this->shareService($definition, $service, $id); } From 468e92ec04caaa3006bb85d1730d596ce5c9e6fa Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Mar 2013 09:00:42 +0200 Subject: [PATCH 09/21] Adding tests for proxy sharing within dumped containers --- .../Tests/Dumper/PhpDumperTest.php | 31 ++- .../Tests/Fixtures/php/lazy_service.php | 199 ++++++++++++++++++ ...service.txt => lazy_service_structure.txt} | 4 +- 3 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php rename src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/{lazy_service.txt => lazy_service_structure.txt} (97%) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index fe1580dfc464f..12f88ca6b19a6 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -55,15 +55,38 @@ public function testDumpFrozenContainerWithNoParameter() public function testDumpContainerWithProxyService() { $container = new ContainerBuilder(); + $container->register('foo', 'stdClass'); $container->getDefinition('foo')->setLazy(true); - $container->compile(); - $dumper = new PhpDumper($container); - + $dumper = new PhpDumper($container); $dumpedString = $dumper->dump(); - $this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/lazy_service.txt', $dumpedString, '->dump() does generate proxy lazy loading logic.'); + + $this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/lazy_service_structure.txt', $dumpedString, '->dump() does generate proxy lazy loading logic.'); + } + + /** + * Verifies that the generated container retrieves the same proxy instance on multiple subsequent requests + */ + public function testDumpContainerWithProxyServiceWillShareProxies() + { + require_once self::$fixturesPath.'/php/lazy_service.php'; + + $container = new \LazyServiceProjectServiceContainer(); + + /* @var $proxy \stdClass_c1d194250ee2e2b7d2eab8b8212368a8 */ + $proxy = $container->get('foo'); + + $this->assertInstanceOf('stdClass_c1d194250ee2e2b7d2eab8b8212368a8', $proxy); + $this->assertSame($proxy, $container->get('foo')); + + $this->assertFalse($proxy->isProxyInitialized()); + + $proxy->initializeProxy(); + + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertSame($proxy, $container->get('foo')); } public function testDumpOptimizationString() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php new file mode 100644 index 0000000000000..cc69dd8247aa3 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.php @@ -0,0 +1,199 @@ + +services = + $this->scopedServices = + $this->scopeStacks = array(); + + $this->set('service_container', $this); + + $this->scopes = array(); + $this->scopeChildren = array(); + } + + /** + * Gets the 'foo' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @param boolean $lazyLoad whether to try lazy-loading the service with a proxy + * + * @return stdClass A stdClass instance. + */ + protected function getFooService($lazyLoad = true) + { + if ($lazyLoad) { + $container = $this; + + return $this->services['foo'] = new stdClass_c1d194250ee2e2b7d2eab8b8212368a8( + function (& $wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { + $proxy->setProxyInitializer(null); + + $wrappedInstance = $container->getFooService(false); + + return true; + } + ); + } + + return new \stdClass(); + } +} + +class stdClass_c1d194250ee2e2b7d2eab8b8212368a8 extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface +{ + + /** + * @var \Closure|null initializer responsible for generating the wrapped object + */ + private $valueHolder5157dd96e88c0 = null; + + /** + * @var \Closure|null initializer responsible for generating the wrapped object + */ + private $initializer5157dd96e8924 = null; + + /** + * @override constructor for lazy initialization + * + * @param \Closure|null $initializer + */ + public function __construct($initializer) + { + $this->initializer5157dd96e8924 = $initializer; + } + + /** + * @param string $name + */ + public function __get($name) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__get', array('name' => $name)); + + return $this->valueHolder5157dd96e88c0->$name; + } + + /** + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__set', array('name' => $name, 'value' => $value)); + + $this->valueHolder5157dd96e88c0->$name = $value; + } + + /** + * @param string $name + */ + public function __isset($name) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__isset', array('name' => $name)); + + return isset($this->valueHolder5157dd96e88c0->$name); + } + + /** + * @param string $name + */ + public function __unset($name) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__unset', array('name' => $name)); + + unset($this->valueHolder5157dd96e88c0->$name); + } + + /** + * + */ + public function __clone() + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__clone', array()); + + $this->valueHolder5157dd96e88c0 = clone $this->valueHolder5157dd96e88c0; + } + + /** + * + */ + public function __sleep() + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__sleep', array()); + + return array('valueHolder5157dd96e88c0'); + } + + /** + * + */ + public function __wakeup() + { + } + + /** + * {@inheritDoc} + */ + public function setProxyInitializer(\Closure $initializer = null) + { + $this->initializer5157dd96e8924 = $initializer; + } + + /** + * {@inheritDoc} + */ + public function getProxyInitializer() + { + return $this->initializer5157dd96e8924; + } + + /** + * {@inheritDoc} + */ + public function initializeProxy() + { + return $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, 'initializeProxy', array()); + } + + /** + * {@inheritDoc} + */ + public function isProxyInitialized() + { + return null !== $this->valueHolder5157dd96e88c0; + } + + /** + * {@inheritDoc} + */ + public function getWrappedValueHolderValue() + { + return $this->valueHolder5157dd96e88c0; + } + + +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt similarity index 97% rename from src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt rename to src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt index 43d5541451bc2..9247ec9308a84 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt @@ -48,7 +48,7 @@ class ProjectServiceContainer extends Container if ($lazyLoad) { $container = $this; - return new stdClass_%s( + return $this->services['foo'] = new stdClass_%s( function (& $wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { $proxy->setProxyInitializer(null); @@ -59,7 +59,7 @@ class ProjectServiceContainer extends Container ); } - return $this->services['foo'] = new \stdClass(); + return new \stdClass(); } } From 4ecd5adfdc2557c5f26e1f17cc116b722642b9d0 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Mar 2013 09:01:01 +0200 Subject: [PATCH 10/21] Fixing shared proxies into the container --- .../DependencyInjection/Dumper/PhpDumper.php | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index c72f791f38893..dd001c05454ef 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -164,15 +164,16 @@ private function addServiceLocalTempVariables($cId, $definition) */ private function addProxyLoading($id, Definition $definition) { - if (!($definition->isLazy() && $definition->getClass())) { + if (!$this->isProxyCandidate($definition)) { return ''; } - $class = $this->dumpValue($definition->getClass()); + $instantiation = 'return'; - if (0 === strpos($class, "'") && !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { - // provided class name is not valid - return ''; + if (ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) { + $instantiation .= " \$this->services['$id'] ="; + } elseif (ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { + $instantiation .= " \$this->services['$id'] = \$this->scopedServices['$scope']['$id'] ="; } $methodName = 'get' . Container::camelize($id) . 'Service'; @@ -182,7 +183,7 @@ private function addProxyLoading($id, Definition $definition) if (\$lazyLoad) { \$container = \$this; - return new $proxyClass( + $instantiation new $proxyClass( function (& \$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$container) { \$proxy->setProxyInitializer(null); @@ -222,13 +223,13 @@ function (Definition $definition) { /* @var $proxyDefinitions Definition[] */ foreach ($proxyDefinitions as $definition) { - $phpClass = new ClassGenerator( + $generatedClass = new ClassGenerator( str_replace('\\', '', $definition->getClass()) . '_' . md5(spl_object_hash($definition)) ); - $proxyGenerator->generate(new ReflectionClass($definition->getClass()), $phpClass); + $proxyGenerator->generate(new ReflectionClass($definition->getClass()), $generatedClass); - $code .= "\n" . $classGenerator->generate($phpClass); + $code .= "\n" . $classGenerator->generate($generatedClass); } return $code; @@ -368,9 +369,10 @@ private function addServiceInstance($id, $definition) $simple = $this->isSimpleInstance($id, $definition); $instantiation = ''; - if (ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) { + + if (!$this->isProxyCandidate($definition) && ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) { $instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance'); - } elseif (ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { + } elseif (!$this->isProxyCandidate($definition) && ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { $instantiation = "\$this->services['$id'] = \$this->scopedServices['$scope']['$id'] = ".($simple ? '' : '$instance'); } elseif (!$simple) { $instantiation = '$instance'; @@ -1274,6 +1276,24 @@ private function getServiceCall($id, Reference $reference = null) } } + /** + * Tells if the given definitions are to be used for proxying + * + * @param Definition $definition + * + * @return bool + */ + private function isProxyCandidate(Definition $definition) + { + if (!($definition->isLazy() && $definition->getClass())) { + return false; + } + + $class = $this->dumpValue($definition->getClass()); + + return (boolean) preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class); + } + /** * Returns the next name to use * From 11a1da905ffbc405752bf76ff405ab688cb30595 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Mar 2013 09:06:27 +0200 Subject: [PATCH 11/21] Bumping required version of ProxyManager --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bf74f0864c187..1e3fdae52142f 100644 --- a/composer.json +++ b/composer.json @@ -67,7 +67,7 @@ "monolog/monolog": "~1.3", "propel/propel1": "1.6.*", "ircmaxell/password-compat": "1.0.*", - "ocramius/proxy-manager": "0.3.*" + "ocramius/proxy-manager": ">=0.3.1,<0.4-dev" }, "autoload": { "psr-0": { "Symfony\\": "src/" }, From 5870fedf77cfe086e40f99b49c48ca129f1f6292 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Mar 2013 09:07:07 +0200 Subject: [PATCH 12/21] Docblock for ContainerBuilder#shareService --- .../Component/DependencyInjection/ContainerBuilder.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 945b40ffa3f1a..50f5e4f13db12 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -1086,6 +1086,15 @@ private function callMethod($service, $call) call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->resolveValue($call[1]))); } + /** + * Shares a given service in the container + * + * @param Definition $definition + * @param mixed $service + * @param string $id + * + * @throws Exception\RuntimeException + */ private function shareService(Definition $definition, $service, $id) { if (self::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { From 695e3c5becf3657e60514235fd334b3860fd6731 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sun, 31 Mar 2013 10:52:31 +0200 Subject: [PATCH 13/21] Adding `ContainerBuilder#addClassResource` --- .../DependencyInjection/ContainerBuilder.php | 26 ++++++-- .../Tests/ContainerBuilderTest.php | 60 +++++++++++++++++++ 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 50f5e4f13db12..d70498137dfa0 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -15,6 +15,7 @@ use ProxyManager\Factory\LazyLoadingValueHolderFactory; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\Proxy\LazyLoadingInterface; +use ReflectionClass; use Symfony\Component\DependencyInjection\Compiler\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -226,15 +227,32 @@ public function setResources(array $resources) * @api */ public function addObjectResource($object) + { + if ($this->trackResources) { + $this->addClassResource(new ReflectionClass($object)); + } + + return $this; + } + + /** + * Adds the given class hierarchy as resources. + * + * @param \ReflectionClass $class + * + * @return ContainerBuilder The current instance + * + * @api + */ + public function addClassResource(ReflectionClass $class) { if (!$this->trackResources) { return $this; } - $parent = new \ReflectionObject($object); do { - $this->addResource(new FileResource($parent->getFileName())); - } while ($parent = $parent->getParentClass()); + $this->addResource(new FileResource($class->getFileName())); + } while ($class = $class->getParentClass()); return $this; } @@ -933,7 +951,7 @@ function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($container, $def $service = call_user_func_array(array($factory, $definition->getFactoryMethod()), $arguments); } else { - $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass())); + $r = new ReflectionClass($parameterBag->resolveValue($definition->getClass())); $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 0fd3b0f4aebc2..6fe92afce3db9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -491,6 +491,66 @@ public function testFindDefinition() $this->assertEquals($definition, $container->findDefinition('foobar'), '->findDefinition() returns a Definition'); } + /** + * @covers Symfony\Component\DependencyInjection\ContainerBuilder::addObjectResource + */ + public function testAddObjectResource() + { + if (!class_exists('Symfony\Component\Config\Resource\FileResource')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + $container = new ContainerBuilder(); + + $container->setResourceTracking(false); + $container->addObjectResource(new \BarClass()); + + $this->assertEmpty($container->getResources(), 'No resources get registered without resource tracking'); + + $container->setResourceTracking(true); + $container->addObjectResource(new \BarClass()); + + $resources = $container->getResources(); + + $this->assertCount(1, $resources, '1 resource was registered'); + + /* @var $resource \Symfony\Component\Config\Resource\FileResource */ + $resource = end($resources); + + $this->assertInstanceOf('Symfony\Component\Config\Resource\FileResource', $resource); + $this->assertSame(realpath(__DIR__.'/Fixtures/includes/classes.php'), realpath($resource->getResource())); + } + + /** + * @covers Symfony\Component\DependencyInjection\ContainerBuilder::addClassResource + */ + public function testAddClassResource() + { + if (!class_exists('Symfony\Component\Config\Resource\FileResource')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + $container = new ContainerBuilder(); + + $container->setResourceTracking(false); + $container->addClassResource(new \ReflectionClass('BarClass')); + + $this->assertEmpty($container->getResources(), 'No resources get registered without resource tracking'); + + $container->setResourceTracking(true); + $container->addClassResource(new \ReflectionClass('BarClass')); + + $resources = $container->getResources(); + + $this->assertCount(1, $resources, '1 resource was registered'); + + /* @var $resource \Symfony\Component\Config\Resource\FileResource */ + $resource = end($resources); + + $this->assertInstanceOf('Symfony\Component\Config\Resource\FileResource', $resource); + $this->assertSame(realpath(__DIR__.'/Fixtures/includes/classes.php'), realpath($resource->getResource())); + } + /** * @covers Symfony\Component\DependencyInjection\ContainerBuilder::getResources * @covers Symfony\Component\DependencyInjection\ContainerBuilder::addResource From c5a5af099fadae7c59426b74c9a405637cf7da75 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 1 Apr 2013 13:03:54 +0200 Subject: [PATCH 14/21] Adding test to check that class resources are registered for lazy services --- .../Tests/ContainerBuilderTest.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 6fe92afce3db9..b1b0a040f5776 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -14,6 +14,7 @@ require_once __DIR__.'/Fixtures/includes/classes.php'; require_once __DIR__.'/Fixtures/includes/ProjectExtension.php'; +use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -551,6 +552,35 @@ public function testAddClassResource() $this->assertSame(realpath(__DIR__.'/Fixtures/includes/classes.php'), realpath($resource->getResource())); } + /** + * @covers Symfony\Component\DependencyInjection\ContainerBuilder::compile + */ + public function testCompilesClassDefinitionsOfLazyServices() + { + if (!class_exists('Symfony\Component\Config\Resource\FileResource')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + $container = new ContainerBuilder(); + + $this->assertEmpty($container->getResources(), 'No resources get registered without resource tracking'); + + $container->register('foo', 'BarClass'); + $container->getDefinition('foo')->setLazy(true); + + $container->compile(); + + $classesPath = realpath(__DIR__.'/Fixtures/includes/classes.php'); + $matchingResources = array_filter( + $container->getResources(), + function (ResourceInterface $resource) use ($classesPath) { + return $resource instanceof FileResource && $classesPath === realpath($resource->getResource()); + } + ); + + $this->assertNotEmpty($matchingResources); + } + /** * @covers Symfony\Component\DependencyInjection\ContainerBuilder::getResources * @covers Symfony\Component\DependencyInjection\ContainerBuilder::addResource From 29899ecb3c0971dfac65d1fdaa722e7a65d4ac76 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 1 Apr 2013 13:04:25 +0200 Subject: [PATCH 15/21] Fixing tests, registering class resources for lazy services --- .../Component/DependencyInjection/ContainerBuilder.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index d70498137dfa0..1bd112170ca25 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -606,6 +606,12 @@ public function compile() foreach ($this->compiler->getPassConfig()->getPasses() as $pass) { $this->addObjectResource($pass); } + + foreach ($this->definitions as $definition) { + if ($definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) { + $this->addClassResource(new ReflectionClass($class)); + } + } } $this->compiler->compile($this); From b5d029866979a9da479394a8a7466ddf3183ba69 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Tue, 2 Apr 2013 20:43:26 +0200 Subject: [PATCH 16/21] Reverting import of global namespace classes --- .../Component/DependencyInjection/ContainerBuilder.php | 9 ++++----- .../Component/DependencyInjection/Dumper/PhpDumper.php | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 1bd112170ca25..1ccbf34e55d66 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -15,7 +15,6 @@ use ProxyManager\Factory\LazyLoadingValueHolderFactory; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\Proxy\LazyLoadingInterface; -use ReflectionClass; use Symfony\Component\DependencyInjection\Compiler\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -229,7 +228,7 @@ public function setResources(array $resources) public function addObjectResource($object) { if ($this->trackResources) { - $this->addClassResource(new ReflectionClass($object)); + $this->addClassResource(new \ReflectionClass($object)); } return $this; @@ -244,7 +243,7 @@ public function addObjectResource($object) * * @api */ - public function addClassResource(ReflectionClass $class) + public function addClassResource(\ReflectionClass $class) { if (!$this->trackResources) { return $this; @@ -609,7 +608,7 @@ public function compile() foreach ($this->definitions as $definition) { if ($definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) { - $this->addClassResource(new ReflectionClass($class)); + $this->addClassResource(new \ReflectionClass($class)); } } } @@ -957,7 +956,7 @@ function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($container, $def $service = call_user_func_array(array($factory, $definition->getFactoryMethod()), $arguments); } else { - $r = new ReflectionClass($parameterBag->resolveValue($definition->getClass())); + $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass())); $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index dd001c05454ef..18523dc0f3560 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -14,7 +14,6 @@ use ProxyManager\Generator\ClassGenerator; use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator; -use ReflectionClass; use Symfony\Component\DependencyInjection\Variable; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -227,7 +226,7 @@ function (Definition $definition) { str_replace('\\', '', $definition->getClass()) . '_' . md5(spl_object_hash($definition)) ); - $proxyGenerator->generate(new ReflectionClass($definition->getClass()), $generatedClass); + $proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass); $code .= "\n" . $classGenerator->generate($generatedClass); } From cda390b92a4a1740ce4124b5081a6f8d4d95a274 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 25 Apr 2013 21:50:41 +0200 Subject: [PATCH 17/21] Lazier checks on the proxy structure (avoiding whitespace-based test failures --- .../Fixtures/php/lazy_service_structure.txt | 133 ++++-------------- 1 file changed, 30 insertions(+), 103 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt index 9247ec9308a84..a0bd62e8dbb2a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt @@ -64,135 +64,62 @@ class ProjectServiceContainer extends Container } class stdClass_%s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface -{ - - /** - * @var \Closure|null initializer responsible for generating the wrapped object - */ +{%a + /**%a*/ private %s = null; - /** - * @var \Closure|null initializer responsible for generating the wrapped object - */ + /**%a*/ private %s = null; - /** - * @override constructor for lazy initialization - * - * @param \Closure|null $initializer - */ + /**%a*/ public function __construct($initializer) - { - $this->%s = $initializer; - } + {%a} - /** - * @param string $name - */ + /**%a*/ public function __get($name) - { - $this->%s && $this->%s->__invoke($this->%s, $this, '__get', array('name' => $name)); + {%a} - return $this->%s->$name; - } - - /** - * @param string $name - * @param mixed $value - */ + /**%a*/ public function __set($name, $value) - { - $this->%s && $this->%s->__invoke($this->%s, $this, '__set', array('name' => $name, 'value' => $value)); - - $this->%s->$name = $value; - } + {%a} - /** - * @param string $name - */ + /**%a*/ public function __isset($name) - { - $this->%s && $this->%s->__invoke($this->%s, $this, '__isset', array('name' => $name)); - - return isset($this->%s->$name); - } + {%a} - /** - * @param string $name - */ + /**%a*/ public function __unset($name) - { - $this->%s && $this->%s->__invoke($this->%s, $this, '__unset', array('name' => $name)); - - unset($this->%s->$name); - } + {%a} - /** - * - */ + /**%a*/ public function __clone() - { - $this->%s && $this->%s->__invoke($this->%s, $this, '__clone', array()); + {%a} - $this->%s = clone $this->%s; - } - - /** - * - */ + /**%a*/ public function __sleep() - { - $this->%s && $this->%s->__invoke($this->%s, $this, '__sleep', array()); - - return array('%s'); - } + {%a} - /** - * - */ + /**%a*/ public function __wakeup() - { - } + {%a} - /** - * {@inheritDoc} - */ + /**%a*/ public function setProxyInitializer(\Closure $initializer = null) - { - $this->%s = $initializer; - } + {%a} - /** - * {@inheritDoc} - */ + /**%a*/ public function getProxyInitializer() - { - return $this->%s; - } + {%a} - /** - * {@inheritDoc} - */ + /**%a*/ public function initializeProxy() - { - return $this->%s && $this->%s->__invoke($this->%s, $this, 'initializeProxy', array()); - } + {%a} - /** - * {@inheritDoc} - */ + /**%a*/ public function isProxyInitialized() - { - return null !== $this->%s; - } + {%a} - /** - * {@inheritDoc} - */ + /**%a*/ public function getWrappedValueHolderValue() - { - return $this->%s; - } - - -} \ No newline at end of file + {%a} +%a} \ No newline at end of file From 1eb4cf700e003329aaf8b747a30439b2bbd7e8fb Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 25 Apr 2013 22:06:27 +0200 Subject: [PATCH 18/21] Getters for proxied services are public for 5.3.3 compatibility --- .../DependencyInjection/ContainerBuilder.php | 11 ++++++----- .../DependencyInjection/Dumper/PhpDumper.php | 9 ++++++--- .../Tests/Fixtures/php/lazy_service.php | 2 +- .../Tests/Fixtures/php/lazy_service_structure.txt | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 1ccbf34e55d66..030551a955782 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -434,13 +434,14 @@ public function has($id) * Gets a service. * * @param string $id The service identifier - * @param integer $invalidBehavior The behavior when the service does not exist + * @param int $invalidBehavior The behavior when the service does not exist * + * @throws InvalidArgumentException + * @throws InactiveScopeException + * @throws LogicException + * @throws \Exception * @return object The associated service * - * @throws InvalidArgumentException if the service is not defined - * @throws LogicException if the service has a circular reference to itself - * * @see Reference * * @api @@ -1116,7 +1117,7 @@ private function callMethod($service, $call) * @param mixed $service * @param string $id * - * @throws Exception\RuntimeException + * @throws InactiveScopeException */ private function shareService(Definition $definition, $service, $id) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 18523dc0f3560..64b7b7af2f22f 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -578,19 +578,22 @@ private function addService($id, $definition) $lazyInitializationDoc = ''; } - $code = <<isProxyCandidate($definition); + $visibility = $isProxyCandidate ? 'public' : 'protected'; + $code = <<addProxyLoading($id, $definition); + $code .= $isProxyCandidate ? $this->addProxyLoading($id, $definition) : ''; if (!in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) { $code .= << Date: Thu, 25 Apr 2013 23:33:15 +0200 Subject: [PATCH 19/21] Enforcing soft dependency to ProxyManager --- .../DependencyInjection/Dumper/PhpDumper.php | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 64b7b7af2f22f..b502bdc6f99d8 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -176,7 +176,7 @@ private function addProxyLoading($id, Definition $definition) } $methodName = 'get' . Container::camelize($id) . 'Service'; - $proxyClass = str_replace('\\', '', $definition->getClass()) . '_' . md5(spl_object_hash($definition)); + $proxyClass = str_replace('\\', '', $definition->getClass()) . '_' . spl_object_hash($definition); return <<container->getDefinitions(), - function (Definition $definition) { - return $definition->isLazy() && $definition->getClass(); + $proxyDefinitions = array(); + + foreach ($this->container->getDefinitions() as $definition) { + if ($this->isProxyCandidate($definition)) { + $proxyDefinitions[] = $definition; } - ); + } // avoids hard dependency to ProxyManager - if (empty($proxyDefinitions) || !class_exists('ProxyManager\\GeneratorStrategy\\BaseGeneratorStrategy')) { + if (empty($proxyDefinitions)) { return ''; } @@ -365,13 +366,13 @@ private function addServiceInstance($id, $definition) throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id)); } - $simple = $this->isSimpleInstance($id, $definition); - - $instantiation = ''; + $simple = $this->isSimpleInstance($id, $definition); + $isProxyCandidate = $this->isProxyCandidate($definition); + $instantiation = ''; - if (!$this->isProxyCandidate($definition) && ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) { + if (!$isProxyCandidate && ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) { $instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance'); - } elseif (!$this->isProxyCandidate($definition) && ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { + } elseif (!$isProxyCandidate && ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) { $instantiation = "\$this->services['$id'] = \$this->scopedServices['$scope']['$id'] = ".($simple ? '' : '$instance'); } elseif (!$simple) { $instantiation = '$instance'; @@ -1279,7 +1280,8 @@ private function getServiceCall($id, Reference $reference = null) } /** - * Tells if the given definitions are to be used for proxying + * Tells if the given definitions are to be used for proxying, and if proxying is possible, + * since ProxyManager may not be available * * @param Definition $definition * @@ -1287,7 +1289,11 @@ private function getServiceCall($id, Reference $reference = null) */ private function isProxyCandidate(Definition $definition) { - if (!($definition->isLazy() && $definition->getClass())) { + if (!( + $definition->isLazy() + && $definition->getClass() + && class_exists('ProxyManager\\Factory\\LazyLoadingValueHolderFactory') + )) { return false; } From 1e24767d1a4824da39935864bcad4e7e87b31461 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Thu, 25 Apr 2013 23:38:19 +0200 Subject: [PATCH 20/21] Reverting documentation changes, adding exception types description in `@throws` --- .../DependencyInjection/ContainerBuilder.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 030551a955782..654c42b302b3f 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -434,14 +434,15 @@ public function has($id) * Gets a service. * * @param string $id The service identifier - * @param int $invalidBehavior The behavior when the service does not exist + * @param integer $invalidBehavior The behavior when the service does not exist * - * @throws InvalidArgumentException - * @throws InactiveScopeException - * @throws LogicException - * @throws \Exception * @return object The associated service * + * @throws InvalidArgumentException when no definitions are available + * @throws InactiveScopeException when the current scope is not active + * @throws LogicException when a circular dependency is detected + * @throws \Exception + * * @see Reference * * @api From 450635a22654e2ba4f93e346e56677f767278e62 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Sat, 27 Apr 2013 21:30:48 +0200 Subject: [PATCH 21/21] Lazier checks on the proxy structure --- .../Fixtures/php/lazy_service_structure.txt | 60 +------------------ 1 file changed, 1 insertion(+), 59 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt index a9210c6d35eaf..1eaf8cad606c3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/lazy_service_structure.txt @@ -64,62 +64,4 @@ class ProjectServiceContainer extends Container } class stdClass_%s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface -{%a - /**%a*/ - private %s = null; - - /**%a*/ - private %s = null; - - /**%a*/ - public function __construct($initializer) - {%a} - - /**%a*/ - public function __get($name) - {%a} - - /**%a*/ - public function __set($name, $value) - {%a} - - /**%a*/ - public function __isset($name) - {%a} - - /**%a*/ - public function __unset($name) - {%a} - - /**%a*/ - public function __clone() - {%a} - - /**%a*/ - public function __sleep() - {%a} - - /**%a*/ - public function __wakeup() - {%a} - - /**%a*/ - public function setProxyInitializer(\Closure $initializer = null) - {%a} - - /**%a*/ - public function getProxyInitializer() - {%a} - - /**%a*/ - public function initializeProxy() - {%a} - - /**%a*/ - public function isProxyInitialized() - {%a} - - /**%a*/ - public function getWrappedValueHolderValue() - {%a} -%a} \ No newline at end of file +{%a} \ No newline at end of file