Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

ProxyManager Bridge

  • Loading branch information...
commit 78e3710de8fbc1bd09e290d0f94c758afb9fbb1b 1 parent c8f95b5
@Ocramius Ocramius authored fabpot committed
Showing with 1,445 additions and 29 deletions.
  1. +2 −1  composer.json
  2. +4 −0 src/Symfony/Bridge/ProxyManager/.gitignore
  3. +7 −0 src/Symfony/Bridge/ProxyManager/CHANGELOG.md
  4. +19 −0 src/Symfony/Bridge/ProxyManager/LICENSE
  5. +62 −0 src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php
  6. +116 −0 src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php
  7. +13 −0 src/Symfony/Bridge/ProxyManager/README.md
  8. +56 −0 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php
  9. +72 −0 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php
  10. +36 −0 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php
  11. +199 −0 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php
  12. +27 −0 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt
  13. +62 −0 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php
  14. +106 −0 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php
  15. +35 −0 src/Symfony/Bridge/ProxyManager/composer.json
  16. +31 −0 src/Symfony/Bridge/ProxyManager/phpunit.xml.dist
  17. +107 −16 src/Symfony/Component/DependencyInjection/ContainerBuilder.php
  18. +26 −0 src/Symfony/Component/DependencyInjection/Definition.php
  19. +76 −8 src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
  20. +3 −0  src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
  21. +4 −0 src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
  22. +35 −0 src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php
  23. +33 −0 src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/RealServiceInstantiator.php
  24. +51 −0 src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php
  25. +47 −0 src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/NullDumper.php
  26. +1 −1  src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
  27. +4 −0 src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
  28. +1 −0  src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
  29. +106 −0 src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
  30. +12 −0 src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php
  31. +1 −1  src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml
  32. +1 −0  src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml
  33. +37 −0 src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php
  34. +35 −0 src/Symfony/Component/DependencyInjection/Tests/LazyProxy/PhpDumper/NullDumperTest.php
  35. +1 −0  src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php
  36. +1 −0  src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
  37. +2 −1  src/Symfony/Component/DependencyInjection/composer.json
  38. +14 −1 src/Symfony/Component/HttpKernel/Kernel.php
View
3  composer.json
@@ -66,7 +66,8 @@
"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.1,<0.4-dev"
},
"autoload": {
"psr-0": { "Symfony\\": "src/" },
View
4 src/Symfony/Bridge/ProxyManager/.gitignore
@@ -0,0 +1,4 @@
+vendor/
+composer.lock
+phpunit.xml
+
View
7 src/Symfony/Bridge/ProxyManager/CHANGELOG.md
@@ -0,0 +1,7 @@
+CHANGELOG
+=========
+
+2.3.0
+-----
+
+ * First introduction of `Symfony\Bridge\ProxyManager`
View
19 src/Symfony/Bridge/ProxyManager/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2013 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
View
62 src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php
@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator;
+
+use ProxyManager\Configuration;
+use ProxyManager\Factory\LazyLoadingValueHolderFactory;
+use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
+use ProxyManager\Proxy\LazyLoadingInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
+
+/**
+ * Runtime lazy loading proxy generator
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ */
+class RuntimeInstantiator implements InstantiatorInterface
+{
+ /**
+ * @var \ProxyManager\Factory\LazyLoadingValueHolderFactory
+ */
+ private $factory;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $config = new Configuration();
+
+ $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
+
+ $this->factory = new LazyLoadingValueHolderFactory($config);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator)
+ {
+ return $this->factory->createProxy(
+ $definition->getClass(),
+ function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
+ $proxy->setProxyInitializer(null);
+
+ $wrappedInstance = call_user_func($realInstantiator);
+
+ return true;
+ }
+ );
+ }
+}
View
116 src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php
@@ -0,0 +1,116 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper;
+
+use ProxyManager\Generator\ClassGenerator;
+use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy;
+use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator;
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface;
+
+/**
+ * Generates dumped php code of proxies via reflection
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ */
+class ProxyDumper implements DumperInterface
+{
+ /**
+ * @var \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator
+ */
+ private $proxyGenerator;
+
+ /**
+ * @var \ProxyManager\GeneratorStrategy\BaseGeneratorStrategy
+ */
+ private $classGenerator;
+
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->proxyGenerator = new LazyLoadingValueHolderGenerator();
+ $this->classGenerator = new BaseGeneratorStrategy();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function isProxyCandidate(Definition $definition)
+ {
+ return $definition->isLazy()
+ && ($class = $definition->getClass())
+ && class_exists($class);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getProxyFactoryCode(Definition $definition, $id)
+ {
+ $instantiation = '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';
+ $proxyClass = $this->getProxyClassName($definition);
+
+ return <<<EOF
+ if (\$lazyLoad) {
+ \$container = \$this;
+
+ $instantiation new $proxyClass(
+ function (& \$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$container) {
+ \$proxy->setProxyInitializer(null);
+
+ \$wrappedInstance = \$container->$methodName(false);
+
+ return true;
+ }
+ );
+ }
+
+
+EOF;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getProxyCode(Definition $definition)
+ {
+ $generatedClass = new ClassGenerator($this->getProxyClassName($definition));
+
+ $this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass);
+
+ return $this->classGenerator->generate($generatedClass);
+ }
+
+ /**
+ * Produces the proxy class name for the given definition
+ *
+ * @param Definition $definition
+ *
+ * @return string
+ */
+ private function getProxyClassName(Definition $definition)
+ {
+ return str_replace('\\', '', $definition->getClass()) . '_' . spl_object_hash($definition);
+ }
+}
View
13 src/Symfony/Bridge/ProxyManager/README.md
@@ -0,0 +1,13 @@
+ProxyManager Bridge
+===================
+
+Provides integration for [ProxyManager](https://github.com/Ocramius/ProxyManager) with various Symfony2 components.
+
+Resources
+---------
+
+You can run the unit tests with the following command:
+
+ $ cd path/to/Symfony/Bridge/ProxyManager/
+ $ composer.phar install --dev
+ $ phpunit
View
56 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php
@@ -0,0 +1,56 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests;
+
+require_once __DIR__ . '/Fixtures/includes/foo.php';
+
+use ProxyManager\Configuration;
+use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Integration tests for {@see \Symfony\Component\DependencyInjection\ContainerBuilder} combined
+ * with the ProxyManager bridge
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ */
+class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService
+ */
+ public function testCreateProxyServiceWithRuntimeInstantiator()
+ {
+ $builder = new ContainerBuilder();
+
+ $builder->setProxyInstantiator(new RuntimeInstantiator());
+
+ $builder->register('foo1', 'ProxyManagerBridgeFooClass')->setFile(__DIR__.'/Fixtures/includes/foo.php');
+ $builder->getDefinition('foo1')->setLazy(true);
+
+ /* @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('\ProxyManagerBridgeFooClass', $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('\ProxyManagerBridgeFooClass', $foo1->getWrappedValueHolderValue());
+ $this->assertNotInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1->getWrappedValueHolderValue());
+ }
+}
View
72 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests\Dumper;
+
+use ProxyManager\Configuration;
+use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
+
+/**
+ * Integration tests for {@see \Symfony\Component\DependencyInjection\Dumper\PhpDumper} combined
+ * with the ProxyManager bridge
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ */
+class PhpDumperTest extends \PHPUnit_Framework_TestCase
+{
+ public function testDumpContainerWithProxyService()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('foo', 'stdClass');
+ $container->getDefinition('foo')->setLazy(true);
+ $container->compile();
+
+ $dumper = new PhpDumper($container);
+
+ $dumper->setProxyDumper(new ProxyDumper());
+
+ $dumpedString = $dumper->dump();
+
+ $this->assertStringMatchesFormatFile(
+ __DIR__ . '/../Fixtures/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 __DIR__ . '/../Fixtures/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'));
+ }
+}
View
36 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php
@@ -0,0 +1,36 @@
+<?php
+
+class ProxyManagerBridgeFooClass
+{
+ public $foo, $moo;
+
+ public $bar = null, $initialized = false, $configured = false, $called = false, $arguments = array();
+
+ public function __construct($arguments = array())
+ {
+ $this->arguments = $arguments;
+ }
+
+ public static function getInstance($arguments = array())
+ {
+ $obj = new self($arguments);
+ $obj->called = true;
+
+ return $obj;
+ }
+
+ public function initialize()
+ {
+ $this->initialized = true;
+ }
+
+ public function configure()
+ {
+ $this->configured = true;
+ }
+
+ public function setBar($value = null)
+ {
+ $this->bar = $value;
+ }
+}
View
199 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php
@@ -0,0 +1,199 @@
+
+<?php
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\DependencyInjection\Exception\InactiveScopeException;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\LogicException;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\Parameter;
+use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
+
+/**
+ * ProjectServiceContainer
+ *
+ * This class has been auto-generated
+ * by the Symfony Dependency Injection Component.
+ */
+class LazyServiceProjectServiceContainer extends Container
+{
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->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.
+ */
+ public 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;
+ }
+
+
+}
View
27 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt
@@ -0,0 +1,27 @@
+<?php
+
+use %a
+class ProjectServiceContainer extends Container
+{%a
+ public function getFooService($lazyLoad = true)
+ {
+ if ($lazyLoad) {
+ $container = $this;
+
+ return $this->services['foo'] = new stdClass_%s(
+ function (& $wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) {
+ $proxy->setProxyInitializer(null);
+
+ $wrappedInstance = $container->getFooService(false);
+
+ return true;
+ }
+ );
+ }
+
+ return new \stdClass();
+ }
+}
+
+class stdClass_%s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface
+{%a}%A
View
62 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php
@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests\Instantiator;
+
+use ProxyManager\Configuration;
+use ProxyManager\Proxy\LazyLoadingInterface;
+use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Definition;
+
+/**
+ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator}
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ *
+ * @covers \Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator
+ */
+class RuntimeInstantiatorTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var RuntimeInstantiator
+ */
+ protected $instantiator;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp()
+ {
+ $this->instantiator = new RuntimeInstantiator();
+ }
+
+ public function testInstantiateProxy()
+ {
+ $instance = new \stdClass();
+ $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
+ $definition = new Definition('stdClass');
+ $instantiator = function () use ($instance) {
+ return $instance;
+ };
+
+ /* @var $proxy \ProxyManager\Proxy\LazyLoadingInterface|\ProxyManager\Proxy\ValueHolderInterface */
+ $proxy = $this->instantiator->instantiateProxy($container, $definition, 'foo', $instantiator);
+
+ $this->assertInstanceOf('ProxyManager\Proxy\LazyLoadingInterface', $proxy);
+ $this->assertInstanceOf('ProxyManager\Proxy\ValueHolderInterface', $proxy);
+ $this->assertFalse($proxy->isProxyInitialized());
+
+ $proxy->initializeProxy();
+
+ $this->assertSame($instance, $proxy->getWrappedValueHolderValue());
+ }
+}
View
106 src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php
@@ -0,0 +1,106 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests\Instantiator;
+
+use ProxyManager\Configuration;
+use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Definition;
+
+/**
+ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper}
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ *
+ * @covers \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper
+ */
+class ProxyDumperTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var ProxyDumper
+ */
+ protected $dumper;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp()
+ {
+ $this->dumper = new ProxyDumper();
+ }
+
+ /**
+ * @dataProvider getProxyCandidates
+ *
+ * @param Definition $definition
+ * @param bool $expected
+ */
+ public function testIsProxyCandidate(Definition $definition, $expected)
+ {
+ $this->assertSame($expected, $this->dumper->isProxyCandidate($definition));
+ }
+
+ public function testGetProxyCode()
+ {
+ $definition = new Definition(__CLASS__);
+
+ $definition->setLazy(true);
+
+ $code = $this->dumper->getProxyCode($definition);
+
+ $this->assertStringMatchesFormat(
+ '%Aclass SymfonyBridgeProxyManagerLazyProxyTestsInstantiatorProxyDumperTest%aextends%w'
+ . '\Symfony\Bridge\ProxyManager\LazyProxy\Tests\Instantiator%a',
+ $code
+ );
+ }
+
+ public function testGetProxyFactoryCode()
+ {
+ $definition = new Definition(__CLASS__);
+
+ $definition->setLazy(true);
+
+ $code = $this->dumper->getProxyFactoryCode($definition, 'foo');
+
+ $this->assertStringMatchesFormat(
+ '%wif ($lazyLoad) {%w$container = $this;%wreturn $this->services[\'foo\'] = new '
+ . 'SymfonyBridgeProxyManagerLazyProxyTestsInstantiatorProxyDumperTest_%s(%wfunction '
+ . '(& $wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) {'
+ . '%w$proxy->setProxyInitializer(null);%w$wrappedInstance = $container->getFooService(false);'
+ . '%wreturn true;%w}%w);%w}%w',
+ $code
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function getProxyCandidates()
+ {
+ $definitions = array(
+ array(new Definition(__CLASS__), true),
+ array(new Definition('stdClass'), true),
+ array(new Definition('foo' . uniqid()), false),
+ array(new Definition(), false),
+ );
+
+ array_map(
+ function ($definition) {
+ $definition[0]->setLazy(true);
+ },
+ $definitions
+ );
+
+ return $definitions;
+ }
+}
View
35 src/Symfony/Bridge/ProxyManager/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "symfony/proxy-manager-bridge",
+ "type": "symfony-bridge",
+ "description": "Symfony ProxyManager Bridge",
+ "keywords": [],
+ "homepage": "http://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/dependency-injection": ">=2.3-dev,<2.4-dev",
+ "ocramius/proxy-manager": ">=0.3.1,<0.4-dev"
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Bridge\\ProxyManager\\": ""
+ }
+ },
+ "target-dir": "Symfony/Bridge/ProxyManager",
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev"
+ }
+ }
+}
View
31 src/Symfony/Bridge/ProxyManager/phpunit.xml.dist
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit
+ backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="vendor/autoload.php"
+>
+ <testsuites>
+ <testsuite name="Symfony ProxyManager Bridge Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./Resources</directory>
+ <directory>./Tests</directory>
+ <directory>./vendor</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
View
123 src/Symfony/Component/DependencyInjection/ContainerBuilder.php
@@ -22,6 +22,8 @@
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Resource\ResourceInterface;
+use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
+use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
/**
* ContainerBuilder is a DI container that provides an API to easily describe services.
@@ -72,6 +74,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
private $trackResources = true;
/**
+ * @var InstantiatorInterface|null
+ */
+ private $proxyInstantiator;
+
+ /**
* Sets the track resources flag.
*
* If you are not using the loaders and therefore don't want
@@ -95,6 +102,16 @@ public function isTrackingResources()
}
/**
+ * Sets the instantiator to be used when fetching proxies.
+ *
+ * @param InstantiatorInterface $proxyInstantiator
+ */
+ public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator)
+ {
+ $this->proxyInstantiator = $proxyInstantiator;
+ }
+
+ /**
* Registers an extension.
*
* @param ExtensionInterface $extension An extension instance
@@ -223,14 +240,29 @@ public function setResources(array $resources)
*/
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
+ */
+ 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;
}
@@ -417,8 +449,10 @@ public function has($id)
*
* @return object The associated service
*
- * @throws InvalidArgumentException if the service is not defined
- * @throws LogicException if the service has a circular reference to itself
+ * @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
*
@@ -584,6 +618,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);
@@ -865,6 +905,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
*
@@ -872,13 +913,32 @@ 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
*/
- private function createService(Definition $definition, $id)
+ public function createService(Definition $definition, $id, $tryProxy = true)
{
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 && $definition->isLazy()) {
+ $container = $this;
+
+ $proxy = $this
+ ->getProxyInstantiator()
+ ->instantiateProxy(
+ $container,
+ $definition,
+ $id, function () use ($definition, $id, $container) {
+ return $container->createService($definition, $id, false);
+ }
+ );
+ $this->shareService($definition, $proxy, $id);
+
+ return $proxy;
+ }
+
$parameterBag = $this->getParameterBag();
if (null !== $definition->getFile()) {
@@ -903,16 +963,9 @@ private function createService(Definition $definition, $id)
$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 ($tryProxy || !$definition->isLazy()) {
+ // share only if proxying failed, or if not a proxy
+ $this->shareService($definition, $service, $id);
}
foreach ($definition->getMethodCalls() as $call) {
@@ -1020,6 +1073,20 @@ public static function getServiceConditionals($value)
}
/**
+ * Retrieves the currently set proxy instantiator or instantiates one.
+ *
+ * @return InstantiatorInterface
+ */
+ private function getProxyInstantiator()
+ {
+ if (!$this->proxyInstantiator) {
+ $this->proxyInstantiator = new RealServiceInstantiator();
+ }
+
+ return $this->proxyInstantiator;
+ }
+
+ /**
* Synchronizes a service change.
*
* This method updates all services that depend on the given
@@ -1057,4 +1124,28 @@ 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 InactiveScopeException
+ */
+ 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;
+ }
+ }
+ }
}
View
26 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();
}
@@ -600,6 +602,30 @@ public function isSynchronized()
}
/**
+ * Sets the lazy flag of this service.
+ *
+ * @param Boolean $lazy
+ *
+ * @return Definition The current instance
+ */
+ public function setLazy($lazy)
+ {
+ $this->lazy = (Boolean) $lazy;
+
+ return $this;
+ }
+
+ /**
+ * Whether this service is lazy.
+ *
+ * @return Boolean
+ */
+ public function isLazy()
+ {
+ return $this->lazy;
+ }
+
+ /**
* Sets whether this definition is synthetic, that is not constructed by the
* container, but dynamically injected.
*
View
84 src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -21,6 +21,8 @@
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
+use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
+use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
/**
* PhpDumper dumps a service container as a PHP class.
@@ -51,6 +53,11 @@ class PhpDumper extends Dumper
private $reservedVariables = array('instance', 'class');
/**
+ * @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface
+ */
+ private $proxyDumper;
+
+ /**
* {@inheritDoc}
*
* @api
@@ -63,6 +70,16 @@ public function __construct(ContainerBuilder $container)
}
/**
+ * Sets the dumper to be used when dumping proxies in the generated container.
+ *
+ * @param ProxyDumper $proxyDumper
+ */
+ public function setProxyDumper(ProxyDumper $proxyDumper)
+ {
+ $this->proxyDumper = $proxyDumper;
+ }
+
+ /**
* Dumps the service container as a PHP class.
*
* Available options:
@@ -94,13 +111,28 @@ public function dump(array $options = array())
$code .=
$this->addServices().
$this->addDefaultParametersMethod().
- $this->endClass()
+ $this->endClass().
+ $this->addProxyClasses()
;
return $code;
}
/**
+ * Retrieves the currently set proxy dumper or instantiates one.
+ *
+ * @return ProxyDumper
+ */
+ private function getProxyDumper()
+ {
+ if (!$this->proxyDumper) {
+ $this->proxyDumper = new NullDumper();
+ }
+
+ return $this->proxyDumper;
+ }
+
+ /**
* Generates Service local temp variables.
*
* @param string $cId
@@ -150,6 +182,27 @@ private function addServiceLocalTempVariables($cId, $definition)
}
/**
+ * Generates code for the proxies to be attached after the container class
+ *
+ * @return string
+ */
+ private function addProxyClasses()
+ {
+ /* @var $proxyDefinitions Definition[] */
+ $definitions = array_filter(
+ $this->container->getDefinitions(),
+ array($this->getProxyDumper(), 'isProxyCandidate')
+ );
+ $code = '';
+
+ foreach ($definitions as $definition) {
+ $code .= "\n" . $this->getProxyDumper()->getProxyCode($definition);
+ }
+
+ return $code;
+ }
+
+ /**
* Generates the require_once statement for service includes.
*
* @param string $id The service id
@@ -280,12 +333,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);
+ $simple = $this->isSimpleInstance($id, $definition);
+ $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
+ $instantiation = '';
- $instantiation = '';
- if (ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) {
+ if (!$isProxyCandidate && ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) {
$instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance');
- } elseif (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';
@@ -483,18 +537,32 @@ private function addService($id, $definition)
EOF;
}
- $code = <<<EOF
+ if ($definition->isLazy()) {
+ $lazyInitialization = '$lazyLoad = true';
+ $lazyInitializationDoc = "\n * @param boolean \$lazyLoad whether to try lazy-loading the"
+ . " service with a proxy\n *";
+ } else {
+ $lazyInitialization = '';
+ $lazyInitializationDoc = '';
+ }
+
+ // with proxies, for 5.3.3 compatibility, the getter must be public to be accessible to the initializer
+ $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
+ $visibility = $isProxyCandidate ? 'public' : 'protected';
+ $code = <<<EOF
/**
* Gets the '$id' service.$doc
- *
+ *$lazyInitializationDoc
* $return
*/
- protected function get{$name}Service()
+ {$visibility} function get{$name}Service($lazyInitialization)
{
EOF;
+ $code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id) : '';
+
if (!in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) {
$code .= <<<EOF
if (!isset(\$this->scopedServices['$scope'])) {
View
3  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) {
View
4 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());
}
View
35 src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/InstantiatorInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Definition;
+
+/**
+ * Lazy proxy instantiator, capable of instantiating a proxy given a container, the
+ * service definitions and a callback that produces the real service instance.
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ */
+interface InstantiatorInterface
+{
+ /**
+ * @param ContainerInterface $container the container from which the service is being requested
+ * @param Definition $definition the definitions of the requested service
+ * @param string $id identifier of the requested service
+ * @param callable $realInstantiator zero-argument callback that is capable of producing the real
+ * service instance
+ *
+ * @return object
+ */
+ public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator);
+}
View
33 src/Symfony/Component/DependencyInjection/LazyProxy/Instantiator/RealServiceInstantiator.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Definition;
+
+/**
+ * {@inheritDoc}
+ *
+ * Noop proxy instantiator - simply produces the real service instead of a proxy instance.
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ */
+class RealServiceInstantiator implements InstantiatorInterface
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator)
+ {
+ return call_user_func($realInstantiator);
+ }
+}
View
51 src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/DumperInterface.php
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Definition;
+
+/**
+ * Lazy proxy dumper capable of generating the instantiation logic php code for proxied services.
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ */
+interface DumperInterface
+{
+ /**
+ * Inspects whether the given definitions should produce proxy instantiation logic in the dumped container.
+ *
+ * @param Definition $definition
+ *
+ * @return bool
+ */
+ public function isProxyCandidate(Definition $definition);
+
+ /**
+ * Generates the code to be used to instantiate a proxy in the dumped factory code.
+ *
+ * @param Definition $definition
+ * @param string $id service identifier
+ *
+ * @return string
+ */
+ public function getProxyFactoryCode(Definition $definition, $id);
+
+ /**
+ * Generates the code for the lazy proxy.
+ *
+ * @param Definition $definition
+ *
+ * @return string
+ */
+ public function getProxyCode(Definition $definition);
+}
View
47 src/Symfony/Component/DependencyInjection/LazyProxy/PhpDumper/NullDumper.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Definition;
+
+/**
+ * Null dumper, negates any proxy code generation for any given service definition.
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ */
+class NullDumper implements DumperInterface
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function isProxyCandidate(Definition $definition)
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getProxyFactoryCode(Definition $definition, $id)
+ {
+ return '';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getProxyCode(Definition $definition)
+ {
+ return '';
+ }
+}
View
2  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));
View
4 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']);
}
View
1  src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
@@ -87,6 +87,7 @@
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="synthetic" type="boolean" />
<xsd:attribute name="synchronized" type="boolean" />
+ <xsd:attribute name="lazy" type="boolean" />
<xsd:attribute name="abstract" type="boolean" />
<xsd:attribute name="factory-class" type="xsd:string" />
<xsd:attribute name="factory-method" type="xsd:string" />
View
106 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;
@@ -266,6 +267,22 @@ public function testCreateService()
/**
* @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService
*/
+ public function testCreateProxyWithRealServiceInstantiator()
+ {
+ $builder = new ContainerBuilder();
+
+ $builder->register('foo1', 'FooClass')->setFile(__DIR__.'/Fixtures/includes/foo.php');
+ $builder->getDefinition('foo1')->setLazy(true);
+
+ $foo1 = $builder->get('foo1');
+
+ $this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved on multiple subsequent calls');
+ $this->assertSame('FooClass', get_class($foo1));
+ }
+
+ /**
+ * @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService
+ */
public function testCreateServiceClass()
{
$builder = new ContainerBuilder();
@@ -466,6 +483,95 @@ public function testFindDefinition()
}
/**
+ * @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::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
*/
View
12 src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php
@@ -165,6 +165,18 @@ public function testSetIsSynchronized()
}
/**
+ * @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
*/
View
2  src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml
@@ -46,6 +46,6 @@
<service id="alias_for_foo" alias="foo" />
<service id="another_alias_for_foo" alias="foo" public="false" />
<service id="factory_service" factory-method="getInstance" factory-service="baz_factory" />
- <service id="request" class="Request" synthetic="true" synchronized="true"/>
+ <service id="request" class="Request" synthetic="true" synchronized="true" lazy="true"/>
</services>
</container>
View
1  src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml
@@ -28,3 +28,4 @@ services:
class: Request
synthetic: true
synchronized: true
+ lazy: true
View
37 src/Symfony/Component/DependencyInjection/Tests/LazyProxy/Instantiator/RealServiceInstantiatorTest.php
@@ -0,0 +1,37 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Tests\LazyProxy\Instantiator;
+
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
+
+/**
+ * Tests for {@see \Symfony\Component\DependencyInjection\Instantiator\RealServiceInstantiator}
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ *
+ * @covers \Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator
+ */
+class RealServiceInstantiatorTest extends \PHPUnit_Framework_TestCase
+{
+ public function testInstantiateProxy()
+ {
+ $instantiator = new RealServiceInstantiator();
+ $instance = new \stdClass();
+ $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
+ $callback = function () use ($instance) {
+ return $instance;
+ };
+
+ $this->assertSame($instance, $instantiator->instantiateProxy($container, new Definition(), 'foo', $callback));
+ }
+}
View
35 src/Symfony/Component/DependencyInjection/Tests/LazyProxy/PhpDumper/NullDumperTest.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Tests\LazyProxy\PhpDumper;
+
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
+
+/**
+ * Tests for {@see \Symfony\Component\DependencyInjection\PhpDumper\NullDumper}
+ *
+ * @author Marco Pivetta <ocramius@gmail.com>
+ *
+ * @covers \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper
+ */
+class NullDumperTest extends \PHPUnit_Framework_TestCase
+{
+ public function testNullDumper()
+ {
+ $dumper = new NullDumper();
+ $definition = new Definition('stdClass');
+
+ $this->assertFalse($dumper->isProxyCandidate($definition));
+ $this->assertSame('', $dumper->getProxyFactoryCode($definition, 'foo'));
+ $this->assertSame('', $dumper->getProxyCode($definition));
+ }
+}
View
1  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 <service> elements');
View
1  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');
View
3  src/Symfony/Component/DependencyInjection/composer.json
@@ -24,7 +24,8 @@
},
"suggest": {
"symfony/yaml": "2.2.*",
- "symfony/config": "2.2.*"
+ "symfony/config": "2.2.*",
+ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them"
},
"autoload": {
"psr-0": { "Symfony\\Component\\DependencyInjection\\": "" }
View
15 src/Symfony/Component/HttpKernel/Kernel.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\HttpKernel;
+use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
+use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
@@ -684,7 +686,13 @@ protected function prepareContainer(ContainerBuilder $container)
*/
protected function getContainerBuilder()
{
- return new ContainerBuilder(new ParameterBag($this->getKernelParameters()));
+ $container = new ContainerBuilder(new ParameterBag($this->getKernelParameters()));
+
+ if (class_exists('Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator')) {
+ $container->setProxyInstantiator(new RuntimeInstantiator());
+ }
+
+ return $container;
}
/**
@@ -699,6 +707,11 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container
{
// cache the container
$dumper = new PhpDumper($container);
+
+ if (class_exists('Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator')) {
+ $dumper->setProxyDumper(new ProxyDumper());
+ }
+
$content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass));
if (!$this->debug) {
$content = self::stripComments($content);

5 comments on commit 78e3710

@lsmith77
Collaborator

@fabpot I guess this needs to be added to some script for the subtree split to work?

@lsmith77
Collaborator

merci!

@WouterJ

@fabpot and a Composer package should be created

@fabpot
Owner

Done

Please sign in to comment.
Something went wrong with that request. Please try again.