merged branch Ocramius/feature/proxy-manager-bridge (PR #7890)

This PR was squashed before being merged into the master branch (closes #7890).

Discussion
----------

ProxyManager Bridge

As of @beberlei's suggestion, I re-implemented #7527 as a new bridge to avoid possible hidden dependencies.

Everything is like #7527 except that the new namespace (and possibly package/subtree split) `Symfony\Bridge\ProxyManager` is introduced

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #6140 (supersedes) #5012 #6102 (maybe) #7527 (supersedes)
| License       | MIT (attached code) - BSD-3-Clause (transitive dependency)
| Doc PR        | Please pester me to death so I do it

This PR introduces lazy services along the lines of zendframework/zf2#4146

It introduces an **OPTIONAL** dependency to [ProxyManager](https://github.com/Ocramius/ProxyManager) and transitively to [`"zendframework/zend-code": "2.*"`](https://github.com/zendframework/zf2/tree/master/library/Zend/Code).

## Lazy services: why? A comprehensive example

For those who don't know what this is about, here's an example.

Assuming you have a service class like following:

```php
class MySuperSlowClass
{
    public function __construct()
    {
        // inject large object graph or do heavy computation
        sleep(10);
    }

    public function doFoo()
    {
        echo 'Foo!';
    }
}
```

The DIC will hang for 10 seconds when calling:

```php
$container->get('my_super_slow_class');
```

With this PR, this can be avoided, and the following call will return a proxy immediately.

```php
$container->getDefinitions('my_super_slow_class')->setLazy(true);
$service = $container->get('my_super_slow_class');
```

The 10 seconds wait time will be delayed until the object is actually used:

```php
$service->doFoo(); // wait 10 seconds, then 'Foo!'
```

A more extensive description of the functionality can be found [here](https://github.com/Ocramius/ProxyManager/blob/master/docs/lazy-loading-value-holder.md).

## When do we need it?

Lazy services can be used to optimize the dependency graph in cases like:

 * Webservice endpoints
 * Db connections
 * Objects that cause I/O in general
 * Large dependency graphs that are not always used

This could also help in reducing excessive service location usage as I've explained [here](http://ocramius.github.com/blog/zf2-and-symfony-service-proxies-with-doctrine-proxies/).

## Implementation quirks of this PR

There's a couple of quirks in the implementation:

 * `Symfony\Component\DependencyInjection\CompilerBuilder#createService` is now public because of the limitations of PHP 5.3
 * `Symfony\Component\DependencyInjection\Dumper\PhpDumper` now with extra mess!
 * The proxies are dumped at the end of compiled containers, therefore the container class is not PSR compliant anymore

Commits
-------

78e3710 ProxyManager Bridge
This commit is contained in:
Fabien Potencier 2013-05-06 08:38:39 +02:00
commit dfd605fc1b
38 changed files with 1445 additions and 29 deletions

View File

@ -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 File

@ -0,0 +1,4 @@
vendor/
composer.lock
phpunit.xml

View File

@ -0,0 +1,7 @@
CHANGELOG
=========
2.3.0
-----
* First introduction of `Symfony\Bridge\ProxyManager`

View File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -22,6 +22,8 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
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.
@ -71,6 +73,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
private $trackResources = true;
/**
* @var InstantiatorInterface|null
*/
private $proxyInstantiator;
/**
* Sets the track resources flag.
*
@ -94,6 +101,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $this->trackResources;
}
/**
* Sets the instantiator to be used when fetching proxies.
*
* @param InstantiatorInterface $proxyInstantiator
*/
public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator)
{
$this->proxyInstantiator = $proxyInstantiator;
}
/**
* Registers an extension.
*
@ -222,15 +239,30 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
* @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
*/
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 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*
* @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 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
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 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*
* @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 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
* @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 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$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) {
@ -1019,6 +1072,20 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $services;
}
/**
* 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.
*
@ -1057,4 +1124,28 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
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 File

@ -37,6 +37,7 @@ class Definition
private $synthetic;
private $abstract;
private $synchronized;
private $lazy;
protected $arguments;
@ -58,6 +59,7 @@ class Definition
$this->public = true;
$this->synthetic = false;
$this->synchronized = false;
$this->lazy = false;
$this->abstract = false;
$this->properties = array();
}
@ -599,6 +601,30 @@ class Definition
return $this->synchronized;
}
/**
* 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 File

@ -21,6 +21,8 @@ use Symfony\Component\DependencyInjection\Parameter;
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.
@ -50,6 +52,11 @@ class PhpDumper extends Dumper
private $variableCount;
private $reservedVariables = array('instance', 'class');
/**
* @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface
*/
private $proxyDumper;
/**
* {@inheritDoc}
*
@ -62,6 +69,16 @@ class PhpDumper extends Dumper
$this->inlinedDefinitions = new \SplObjectStorage;
}
/**
* 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.
*
@ -94,12 +111,27 @@ class PhpDumper extends Dumper
$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.
*
@ -149,6 +181,27 @@ class PhpDumper extends Dumper
return $code;
}
/**
* 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.
*
@ -280,12 +333,13 @@ class PhpDumper extends Dumper
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 @@ EOF;
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 File

@ -133,6 +133,9 @@ class XmlDumper extends Dumper
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 File

@ -106,6 +106,10 @@ class YamlDumper extends Dumper
$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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -148,7 +148,7 @@ class XmlFileLoader extends FileLoader
$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 File

@ -157,6 +157,10 @@ class YamlFileLoader extends FileLoader
$definition->setSynchronized($service['synchronized']);
}
if (isset($service['lazy'])) {
$definition->setLazy($service['lazy']);
}
if (isset($service['public'])) {
$definition->setPublic($service['public']);
}

View File

@ -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 File

@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Tests;
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;
@ -263,6 +264,22 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$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 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
*/
@ -465,6 +482,95 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$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::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 File

@ -164,6 +164,18 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
$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

View File

@ -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 File

@ -28,3 +28,4 @@ services:
class: Request
synthetic: true
synchronized: true
lazy: true

View File

@ -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 File

@ -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 File

@ -187,6 +187,7 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
$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 File

@ -130,6 +130,7 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
$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 File

@ -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 File

@ -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 @@ abstract class Kernel implements KernelInterface, TerminableInterface
*/
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 @@ abstract class Kernel implements KernelInterface, TerminableInterface
{
// 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);