Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dfd605f

Browse files
committed
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/zendframework#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
2 parents e0d2065 + 78e3710 commit dfd605f

38 files changed

+1445
-29
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666
"doctrine/orm": "~2.2,>=2.2.3",
6767
"monolog/monolog": "~1.3",
6868
"propel/propel1": "1.6.*",
69-
"ircmaxell/password-compat": "1.0.*"
69+
"ircmaxell/password-compat": "1.0.*",
70+
"ocramius/proxy-manager": ">=0.3.1,<0.4-dev"
7071
},
7172
"autoload": {
7273
"psr-0": { "Symfony\\": "src/" },
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
4+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
2.3.0
5+
-----
6+
7+
* First introduction of `Symfony\Bridge\ProxyManager`
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2004-2013 Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator;
13+
14+
use ProxyManager\Configuration;
15+
use ProxyManager\Factory\LazyLoadingValueHolderFactory;
16+
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
17+
use ProxyManager\Proxy\LazyLoadingInterface;
18+
use Symfony\Component\DependencyInjection\ContainerInterface;
19+
use Symfony\Component\DependencyInjection\Definition;
20+
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
21+
22+
/**
23+
* Runtime lazy loading proxy generator
24+
*
25+
* @author Marco Pivetta <[email protected]>
26+
*/
27+
class RuntimeInstantiator implements InstantiatorInterface
28+
{
29+
/**
30+
* @var \ProxyManager\Factory\LazyLoadingValueHolderFactory
31+
*/
32+
private $factory;
33+
34+
/**
35+
* Constructor
36+
*/
37+
public function __construct()
38+
{
39+
$config = new Configuration();
40+
41+
$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
42+
43+
$this->factory = new LazyLoadingValueHolderFactory($config);
44+
}
45+
46+
/**
47+
* {@inheritDoc}
48+
*/
49+
public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator)
50+
{
51+
return $this->factory->createProxy(
52+
$definition->getClass(),
53+
function (& $wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
54+
$proxy->setProxyInitializer(null);
55+
56+
$wrappedInstance = call_user_func($realInstantiator);
57+
58+
return true;
59+
}
60+
);
61+
}
62+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper;
13+
14+
use ProxyManager\Generator\ClassGenerator;
15+
use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy;
16+
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator;
17+
use Symfony\Component\DependencyInjection\Container;
18+
use Symfony\Component\DependencyInjection\ContainerInterface;
19+
use Symfony\Component\DependencyInjection\Definition;
20+
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface;
21+
22+
/**
23+
* Generates dumped php code of proxies via reflection
24+
*
25+
* @author Marco Pivetta <[email protected]>
26+
*/
27+
class ProxyDumper implements DumperInterface
28+
{
29+
/**
30+
* @var \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator
31+
*/
32+
private $proxyGenerator;
33+
34+
/**
35+
* @var \ProxyManager\GeneratorStrategy\BaseGeneratorStrategy
36+
*/
37+
private $classGenerator;
38+
39+
/**
40+
* Constructor
41+
*/
42+
public function __construct()
43+
{
44+
$this->proxyGenerator = new LazyLoadingValueHolderGenerator();
45+
$this->classGenerator = new BaseGeneratorStrategy();
46+
}
47+
48+
/**
49+
* {@inheritDoc}
50+
*/
51+
public function isProxyCandidate(Definition $definition)
52+
{
53+
return $definition->isLazy()
54+
&& ($class = $definition->getClass())
55+
&& class_exists($class);
56+
}
57+
58+
/**
59+
* {@inheritDoc}
60+
*/
61+
public function getProxyFactoryCode(Definition $definition, $id)
62+
{
63+
$instantiation = 'return';
64+
65+
if (ContainerInterface::SCOPE_CONTAINER === $definition->getScope()) {
66+
$instantiation .= " \$this->services['$id'] =";
67+
} elseif (ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) {
68+
$instantiation .= " \$this->services['$id'] = \$this->scopedServices['$scope']['$id'] =";
69+
}
70+
71+
$methodName = 'get' . Container::camelize($id) . 'Service';
72+
$proxyClass = $this->getProxyClassName($definition);
73+
74+
return <<<EOF
75+
if (\$lazyLoad) {
76+
\$container = \$this;
77+
78+
$instantiation new $proxyClass(
79+
function (& \$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) use (\$container) {
80+
\$proxy->setProxyInitializer(null);
81+
82+
\$wrappedInstance = \$container->$methodName(false);
83+
84+
return true;
85+
}
86+
);
87+
}
88+
89+
90+
EOF;
91+
}
92+
93+
/**
94+
* {@inheritDoc}
95+
*/
96+
public function getProxyCode(Definition $definition)
97+
{
98+
$generatedClass = new ClassGenerator($this->getProxyClassName($definition));
99+
100+
$this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass);
101+
102+
return $this->classGenerator->generate($generatedClass);
103+
}
104+
105+
/**
106+
* Produces the proxy class name for the given definition
107+
*
108+
* @param Definition $definition
109+
*
110+
* @return string
111+
*/
112+
private function getProxyClassName(Definition $definition)
113+
{
114+
return str_replace('\\', '', $definition->getClass()) . '_' . spl_object_hash($definition);
115+
}
116+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
ProxyManager Bridge
2+
===================
3+
4+
Provides integration for [ProxyManager](https://github.com/Ocramius/ProxyManager) with various Symfony2 components.
5+
6+
Resources
7+
---------
8+
9+
You can run the unit tests with the following command:
10+
11+
$ cd path/to/Symfony/Bridge/ProxyManager/
12+
$ composer.phar install --dev
13+
$ phpunit
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests;
13+
14+
require_once __DIR__ . '/Fixtures/includes/foo.php';
15+
16+
use ProxyManager\Configuration;
17+
use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator;
18+
use Symfony\Component\DependencyInjection\ContainerBuilder;
19+
use Symfony\Component\DependencyInjection\ContainerInterface;
20+
21+
/**
22+
* Integration tests for {@see \Symfony\Component\DependencyInjection\ContainerBuilder} combined
23+
* with the ProxyManager bridge
24+
*
25+
* @author Marco Pivetta <[email protected]>
26+
*/
27+
class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
28+
{
29+
/**
30+
* @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService
31+
*/
32+
public function testCreateProxyServiceWithRuntimeInstantiator()
33+
{
34+
$builder = new ContainerBuilder();
35+
36+
$builder->setProxyInstantiator(new RuntimeInstantiator());
37+
38+
$builder->register('foo1', 'ProxyManagerBridgeFooClass')->setFile(__DIR__.'/Fixtures/includes/foo.php');
39+
$builder->getDefinition('foo1')->setLazy(true);
40+
41+
/* @var $foo1 \ProxyManager\Proxy\LazyLoadingInterface|\ProxyManager\Proxy\ValueHolderInterface */
42+
$foo1 = $builder->get('foo1');
43+
44+
$this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved on multiple subsequent calls');
45+
$this->assertInstanceOf('\ProxyManagerBridgeFooClass', $foo1);
46+
$this->assertInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1);
47+
$this->assertFalse($foo1->isProxyInitialized());
48+
49+
$foo1->initializeProxy();
50+
51+
$this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved after initialization');
52+
$this->assertTrue($foo1->isProxyInitialized());
53+
$this->assertInstanceOf('\ProxyManagerBridgeFooClass', $foo1->getWrappedValueHolderValue());
54+
$this->assertNotInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1->getWrappedValueHolderValue());
55+
}
56+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\ProxyManager\LazyProxy\Tests\Dumper;
13+
14+
use ProxyManager\Configuration;
15+
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\ContainerInterface;
18+
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
19+
20+
/**
21+
* Integration tests for {@see \Symfony\Component\DependencyInjection\Dumper\PhpDumper} combined
22+
* with the ProxyManager bridge
23+
*
24+
* @author Marco Pivetta <[email protected]>
25+
*/
26+
class PhpDumperTest extends \PHPUnit_Framework_TestCase
27+
{
28+
public function testDumpContainerWithProxyService()
29+
{
30+
$container = new ContainerBuilder();
31+
32+
$container->register('foo', 'stdClass');
33+
$container->getDefinition('foo')->setLazy(true);
34+
$container->compile();
35+
36+
$dumper = new PhpDumper($container);
37+
38+
$dumper->setProxyDumper(new ProxyDumper());
39+
40+
$dumpedString = $dumper->dump();
41+
42+
$this->assertStringMatchesFormatFile(
43+
__DIR__ . '/../Fixtures/php/lazy_service_structure.txt',
44+
$dumpedString,
45+
'->dump() does generate proxy lazy loading logic.'
46+
);
47+
}
48+
49+
50+
/**
51+
* Verifies that the generated container retrieves the same proxy instance on multiple subsequent requests
52+
*/
53+
public function testDumpContainerWithProxyServiceWillShareProxies()
54+
{
55+
require_once __DIR__ . '/../Fixtures/php/lazy_service.php';
56+
57+
$container = new \LazyServiceProjectServiceContainer();
58+
59+
/* @var $proxy \stdClass_c1d194250ee2e2b7d2eab8b8212368a8 */
60+
$proxy = $container->get('foo');
61+
62+
$this->assertInstanceOf('stdClass_c1d194250ee2e2b7d2eab8b8212368a8', $proxy);
63+
$this->assertSame($proxy, $container->get('foo'));
64+
65+
$this->assertFalse($proxy->isProxyInitialized());
66+
67+
$proxy->initializeProxy();
68+
69+
$this->assertTrue($proxy->isProxyInitialized());
70+
$this->assertSame($proxy, $container->get('foo'));
71+
}
72+
}

0 commit comments

Comments
 (0)