feature #27697 [ProxyManagerBridge][DI] allow proxifying interfaces with "lazy: Some\ProxifiedInterface" (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[ProxyManagerBridge][DI] allow proxifying interfaces with "lazy: Some\ProxifiedInterface"

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #20656
| License       | MIT
| Doc PR        | -

By adding `<tag name="proxy" interface="Some\ProxifiedInterface" />` to your service definitions, this PR allows generating interface-based proxies.

This would allow two things:
- generating lazy proxies for final classes
- wrapping a service into such proxy to forbid using the methods that are on a specific implementation but not on some interface - the proxy acting as a safe guard.

The generated proxies are always lazy, so that lazy=true/false makes no difference.

As a shortcut, you can also use `lazy: Some\ProxifiedInterface` to do the same (yaml example this time.)

Commits
-------

1d9f1d1b70 [ProxyManagerBridge][DI] allow proxifying interfaces with "lazy: Some\ProxifiedInterface"
This commit is contained in:
Fabien Potencier 2018-07-09 16:47:21 +02:00
commit a5709ee9ba
24 changed files with 484 additions and 71 deletions

View File

@ -19,6 +19,7 @@ return PhpCsFixer\Config::create()
->in(__DIR__.'/src')
->append(array(__FILE__))
->exclude(array(
'Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures',
// directories containing files with content that is autogenerated by `var_export`, which breaks CS in output code
'Symfony/Component/Cache/Tests/Marshaller/Fixtures',
'Symfony/Component/DependencyInjection/Tests/Fixtures',

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
4.2.0
-----
* allowed creating lazy-proxies from interfaces
3.3.0
-----

View File

@ -18,14 +18,14 @@ use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenera
/**
* @internal
*/
class LazyLoadingValueHolderFactoryV2 extends BaseFactory
class LazyLoadingValueHolderFactory extends BaseFactory
{
private $generator;
/**
* {@inheritdoc}
*/
protected function getGenerator(): ProxyGeneratorInterface
public function getGenerator(): ProxyGeneratorInterface
{
return $this->generator ?: $this->generator = new LazyLoadingValueHolderGenerator();
}

View File

@ -1,31 +0,0 @@
<?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\Factory\LazyLoadingValueHolderFactory as BaseFactory;
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenerator;
/**
* @internal
*/
class LazyLoadingValueHolderFactoryV1 extends BaseFactory
{
private $generatorV1;
/**
* {@inheritdoc}
*/
protected function getGenerator()
{
return $this->generatorV1 ?: $this->generatorV1 = new LazyLoadingValueHolderGenerator();
}
}

View File

@ -12,7 +12,6 @@
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;
@ -26,9 +25,6 @@ use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInt
*/
class RuntimeInstantiator implements InstantiatorInterface
{
/**
* @var LazyLoadingValueHolderFactory
*/
private $factory;
public function __construct()
@ -36,11 +32,7 @@ class RuntimeInstantiator implements InstantiatorInterface
$config = new Configuration();
$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
if (method_exists('ProxyManager\Version', 'getVersion')) {
$this->factory = new LazyLoadingValueHolderFactoryV2($config);
} else {
$this->factory = new LazyLoadingValueHolderFactoryV1($config);
}
$this->factory = new LazyLoadingValueHolderFactory($config);
}
/**
@ -49,9 +41,9 @@ class RuntimeInstantiator implements InstantiatorInterface
public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator)
{
return $this->factory->createProxy(
$definition->getClass(),
$this->factory->getGenerator()->getProxifiedClass($definition),
function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
$wrappedInstance = call_user_func($realInstantiator);
$wrappedInstance = \call_user_func($realInstantiator);
$proxy->setProxyInitializer(null);

View File

@ -12,6 +12,7 @@
namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper;
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator;
use Symfony\Component\DependencyInjection\Definition;
use Zend\Code\Generator\ClassGenerator;
/**
@ -19,6 +20,13 @@ use Zend\Code\Generator\ClassGenerator;
*/
class LazyLoadingValueHolderGenerator extends BaseGenerator
{
private $fluentSafe = false;
public function setFluentSafe(bool $fluentSafe)
{
$this->fluentSafe = $fluentSafe;
}
/**
* {@inheritdoc}
*/
@ -26,6 +34,52 @@ class LazyLoadingValueHolderGenerator extends BaseGenerator
{
parent::generate($originalClass, $classGenerator);
foreach ($classGenerator->getMethods() as $method) {
$body = preg_replace(
'/(\$this->initializer[0-9a-f]++) && \1->__invoke\(\$this->(valueHolder[0-9a-f]++), (.*?), \1\);/',
'$1 && ($1->__invoke(\$$2, $3, $1) || 1) && $this->$2 = \$$2;',
$method->getBody()
);
$body = str_replace('(new \ReflectionClass(get_class()))', '$reflection', $body);
if ($originalClass->isInterface()) {
$body = str_replace('get_parent_class($this)', var_export($originalClass->name, true), $body);
$body = preg_replace_callback('/\n\n\$realInstanceReflection = [^{]++\{([^}]++)\}\n\n.*/s', function ($m) {
$r = '';
foreach (explode("\n", $m[1]) as $line) {
$r .= "\n".substr($line, 4);
if (0 === strpos($line, ' return ')) {
break;
}
}
return $r;
}, $body);
}
if ($this->fluentSafe) {
$indent = $method->getIndentation();
$method->setIndentation('');
$code = $method->generate();
if (null !== $docBlock = $method->getDocBlock()) {
$code = substr($code, \strlen($docBlock->generate()));
}
$refAmp = (strpos($code, '&') ?: \PHP_INT_MAX) <= strpos($code, '(') ? '&' : '';
$body = preg_replace(
'/\nreturn (\$this->valueHolder[0-9a-f]++)(->[^;]++);$/',
"\nif ($1 === \$returnValue = {$refAmp}$1$2) {\n \$returnValue = \$this;\n}\n\nreturn \$returnValue;",
$body
);
$method->setIndentation($indent);
}
if (0 === strpos($originalClass->getFilename(), __FILE__)) {
$body = str_replace(var_export($originalClass->name, true), '__CLASS__', $body);
}
$method->setBody($body);
}
if ($classGenerator->hasMethod('__destruct')) {
$destructor = $classGenerator->getMethod('__destruct');
$body = $destructor->getBody();
@ -37,5 +91,53 @@ class LazyLoadingValueHolderGenerator extends BaseGenerator
$destructor->setBody($newBody);
}
if (0 === strpos($originalClass->getFilename(), __FILE__)) {
$interfaces = $classGenerator->getImplementedInterfaces();
array_pop($interfaces);
$classGenerator->setImplementedInterfaces(array_merge($interfaces, $originalClass->getInterfaceNames()));
}
}
public function getProxifiedClass(Definition $definition): ?string
{
if (!$definition->hasTag('proxy')) {
return \class_exists($class = $definition->getClass()) || \interface_exists($class) ? $class : null;
}
if (!$definition->isLazy()) {
throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": setting the "proxy" tag on a service requires it to be "lazy".', $definition->getClass()));
}
$tags = $definition->getTag('proxy');
if (!isset($tags[0]['interface'])) {
throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": the "interface" attribute is missing on the "proxy" tag.', $definition->getClass()));
}
if (1 === \count($tags)) {
return \class_exists($tags[0]['interface']) || \interface_exists($tags[0]['interface']) ? $tags[0]['interface'] : null;
}
$proxyInterface = 'LazyProxy';
$interfaces = '';
foreach ($tags as $tag) {
if (!isset($tag['interface'])) {
throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": the "interface" attribute is missing on a "proxy" tag.', $definition->getClass()));
}
if (!\interface_exists($tag['interface'])) {
throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": several "proxy" tags found but "%s" is not an interface.', $definition->getClass(), $tag['interface']));
}
$proxyInterface .= '\\'.$tag['interface'];
$interfaces .= ', \\'.$tag['interface'];
}
if (!\interface_exists($proxyInterface)) {
$i = strrpos($proxyInterface, '\\');
$namespace = substr($proxyInterface, 0, $i);
$interface = substr($proxyInterface, 1 + $i);
$interfaces = substr($interfaces, 2);
eval("namespace {$namespace}; interface {$interface} extends {$interfaces} {}");
}
return $proxyInterface;
}
}

View File

@ -14,7 +14,6 @@ namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper;
use ProxyManager\Generator\ClassGenerator;
use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy;
use ProxyManager\Version;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface;
@ -43,7 +42,7 @@ class ProxyDumper implements DumperInterface
*/
public function isProxyCandidate(Definition $definition)
{
return $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class);
return ($definition->isLazy() || $definition->hasTag('proxy')) && $this->proxyGenerator->getProxifiedClass($definition);
}
/**
@ -63,14 +62,10 @@ class ProxyDumper implements DumperInterface
$proxyClass = $this->getProxyClassName($definition);
$hasStaticConstructor = $this->generateProxyClass($definition)->hasMethod('staticProxyConstructor');
$constructorCall = sprintf($hasStaticConstructor ? '%s::staticProxyConstructor' : 'new %s', '\\'.$proxyClass);
return <<<EOF
if (\$lazyLoad) {
$instantiation \$this->createProxy('$proxyClass', function () {
return $constructorCall(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) {
return \\$proxyClass::staticProxyConstructor(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) {
\$wrappedInstance = $factoryCode;
\$proxy->setProxyInitializer(null);
@ -91,12 +86,6 @@ EOF;
{
$code = $this->classGenerator->generate($this->generateProxyClass($definition));
$code = preg_replace(
'/(\$this->initializer[0-9a-f]++) && \1->__invoke\(\$this->(valueHolder[0-9a-f]++), (.*?), \1\);/',
'$1 && ($1->__invoke(\$$2, $3, $1) || 1) && $this->$2 = \$$2;',
$code
);
if (version_compare(self::getProxyManagerVersion(), '2.2', '<')) {
$code = preg_replace(
'/((?:\$(?:this|initializer|instance)->)?(?:publicProperties|initializer|valueHolder))[0-9a-f]++/',
@ -122,20 +111,26 @@ EOF;
*/
private function getProxyClassName(Definition $definition): string
{
return preg_replace('/^.*\\\\/', '', $definition->getClass()).'_'.$this->getIdentifierSuffix($definition);
$class = $this->proxyGenerator->getProxifiedClass($definition);
return preg_replace('/^.*\\\\/', '', $class).'_'.$this->getIdentifierSuffix($definition);
}
private function generateProxyClass(Definition $definition): ClassGenerator
{
$generatedClass = new ClassGenerator($this->getProxyClassName($definition));
$class = $this->proxyGenerator->getProxifiedClass($definition);
$this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass);
$this->proxyGenerator->setFluentSafe($definition->hasTag('proxy'));
$this->proxyGenerator->generate(new \ReflectionClass($class), $generatedClass);
return $generatedClass;
}
private function getIdentifierSuffix(Definition $definition): string
{
return substr(hash('sha256', $definition->getClass().$this->salt), -7);
$class = $this->proxyGenerator->getProxifiedClass($definition);
return substr(hash('sha256', $class.$this->salt), -7);
}
}

View File

@ -0,0 +1,31 @@
<?php
return new class
{
public $proxyClass;
private $privates = array();
public function getFooService($lazyLoad = true)
{
if ($lazyLoad) {
return $this->privates['foo'] = $this->createProxy('SunnyInterface_1eff735', function () {
return \SunnyInterface_1eff735::staticProxyConstructor(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) {
$wrappedInstance = $this->getFooService(false);
$proxy->setProxyInitializer(null);
return true;
});
});
}
return new Symfony\Bridge\ProxyManager\Tests\LazyProxy\PhpDumper\DummyClass();
}
protected function createProxy($class, \Closure $factory)
{
$this->proxyClass = $class;
return $factory();
}
};

View File

@ -0,0 +1,165 @@
<?php
class SunnyInterface_1eff735 implements \ProxyManager\Proxy\VirtualProxyInterface, \Symfony\Bridge\ProxyManager\Tests\LazyProxy\PhpDumper\DummyInterface, \Symfony\Bridge\ProxyManager\Tests\LazyProxy\PhpDumper\SunnyInterface
{
private $valueHolder1eff735 = null;
private $initializer1eff735 = null;
private static $publicProperties1eff735 = [
];
public function dummy()
{
$this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, 'dummy', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
if ($this->valueHolder1eff735 === $returnValue = $this->valueHolder1eff735->dummy()) {
$returnValue = $this;
}
return $returnValue;
}
public function & dummyRef()
{
$this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, 'dummyRef', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
if ($this->valueHolder1eff735 === $returnValue = &$this->valueHolder1eff735->dummyRef()) {
$returnValue = $this;
}
return $returnValue;
}
public function sunny()
{
$this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, 'sunny', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
if ($this->valueHolder1eff735 === $returnValue = $this->valueHolder1eff735->sunny()) {
$returnValue = $this;
}
return $returnValue;
}
public static function staticProxyConstructor($initializer)
{
static $reflection;
$reflection = $reflection ?: $reflection = new \ReflectionClass(__CLASS__);
$instance = $reflection->newInstanceWithoutConstructor();
$instance->initializer1eff735 = $initializer;
return $instance;
}
public function __construct()
{
static $reflection;
if (! $this->valueHolder1eff735) {
$reflection = $reflection ?: new \ReflectionClass(__CLASS__);
$this->valueHolder1eff735 = $reflection->newInstanceWithoutConstructor();
}
}
public function & __get($name)
{
$this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__get', ['name' => $name], $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
if (isset(self::$publicProperties1eff735[$name])) {
return $this->valueHolder1eff735->$name;
}
$targetObject = $this->valueHolder1eff735;
$backtrace = debug_backtrace(false);
trigger_error(
sprintf(
'Undefined property: %s::$%s in %s on line %s',
__CLASS__,
$name,
$backtrace[0]['file'],
$backtrace[0]['line']
),
\E_USER_NOTICE
);
return $targetObject->$name;
}
public function __set($name, $value)
{
$this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__set', array('name' => $name, 'value' => $value), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
$targetObject = $this->valueHolder1eff735;
return $targetObject->$name = $value;
}
public function __isset($name)
{
$this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__isset', array('name' => $name), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
$targetObject = $this->valueHolder1eff735;
return isset($targetObject->$name);
}
public function __unset($name)
{
$this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__unset', array('name' => $name), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
$targetObject = $this->valueHolder1eff735;
unset($targetObject->$name);
return;
}
public function __clone()
{
$this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__clone', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
$this->valueHolder1eff735 = clone $this->valueHolder1eff735;
}
public function __sleep()
{
$this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__sleep', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
return array('valueHolder1eff735');
}
public function __wakeup()
{
}
public function setProxyInitializer(\Closure $initializer = null)
{
$this->initializer1eff735 = $initializer;
}
public function getProxyInitializer()
{
return $this->initializer1eff735;
}
public function initializeProxy() : bool
{
return $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, 'initializeProxy', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735;
}
public function isProxyInitialized() : bool
{
return null !== $this->valueHolder1eff735;
}
public function getWrappedValueHolderValue()
{
return $this->valueHolder1eff735;
}
}

View File

@ -120,6 +120,61 @@ class ProxyDumperTest extends TestCase
$this->dumper->getProxyFactoryCode($definition, 'foo');
}
public function testGetProxyFactoryCodeForInterface()
{
$class = DummyClass::class;
$definition = new Definition($class);
$definition->setLazy(true);
$definition->addTag('proxy', array('interface' => DummyInterface::class));
$definition->addTag('proxy', array('interface' => SunnyInterface::class));
$implem = "<?php\n\n".$this->dumper->getProxyCode($definition);
$factory = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFooService(false)');
$factory = <<<EOPHP
<?php
return new class
{
public \$proxyClass;
private \$privates = array();
public function getFooService(\$lazyLoad = true)
{
{$factory} return new {$class}();
}
protected function createProxy(\$class, \Closure \$factory)
{
\$this->proxyClass = \$class;
return \$factory();
}
};
EOPHP;
$implem = preg_replace('#\n /\*\*.*?\*/#s', '', $implem);
$implem = str_replace('getWrappedValueHolderValue() : ?object', 'getWrappedValueHolderValue()', $implem);
$implem = str_replace("array(\n \n );", "[\n \n ];", $implem);
$this->assertStringEqualsFile(__DIR__.'/Fixtures/proxy-implem.php', $implem);
$this->assertStringEqualsFile(__DIR__.'/Fixtures/proxy-factory.php', $factory);
require_once __DIR__.'/Fixtures/proxy-implem.php';
$factory = require __DIR__.'/Fixtures/proxy-factory.php';
$foo = $factory->getFooService();
$this->assertInstanceof($factory->proxyClass, $foo);
$this->assertInstanceof(DummyInterface::class, $foo);
$this->assertInstanceof(SunnyInterface::class, $foo);
$this->assertNotInstanceof(DummyClass::class, $foo);
$this->assertSame($foo, $foo->dummy());
$foo->dynamicProp = 123;
$this->assertSame(123, @$foo->dynamicProp);
}
/**
* @return array
*/
@ -142,3 +197,34 @@ class ProxyDumperTest extends TestCase
return $definitions;
}
}
final class DummyClass implements DummyInterface, SunnyInterface
{
public function dummy()
{
return $this;
}
public function sunny()
{
}
public function &dummyRef()
{
return $this->ref;
}
}
interface DummyInterface
{
public function dummy();
public function &dummyRef();
}
interface SunnyInterface
{
public function dummy();
public function sunny();
}

View File

@ -18,7 +18,7 @@
"require": {
"php": "^7.1.3",
"symfony/dependency-injection": "~3.4|~4.0",
"ocramius/proxy-manager": "~0.4|~1.0|~2.0"
"ocramius/proxy-manager": "~2.1"
},
"require-dev": {
"symfony/config": "~3.4|~4.0"

View File

@ -44,6 +44,7 @@ class UnusedTagsPass implements CompilerPassInterface
'messenger.receiver',
'messenger.message_handler',
'monolog.logger',
'proxy',
'routing.expression_language_provider',
'routing.loader',
'security.expression_language_provider',

View File

@ -16,11 +16,16 @@ trait LazyTrait
/**
* Sets the lazy flag of this service.
*
* @param bool|string A FQCN to derivate the lazy proxy from or `true` to make it extend from the definition's class
*
* @return $this
*/
final public function lazy(bool $lazy = true)
final public function lazy($lazy = true)
{
$this->definition->setLazy($lazy);
$this->definition->setLazy((bool) $lazy);
if (\is_string($lazy)) {
$this->definition->addTag('proxy', array('interface' => $lazy));
}
return $this;
}

View File

@ -265,10 +265,17 @@ class XmlFileLoader extends FileLoader
$definition->setChanges(array());
}
foreach (array('class', 'public', 'shared', 'synthetic', 'lazy', 'abstract') as $key) {
foreach (array('class', 'public', 'shared', 'synthetic', 'abstract') as $key) {
if ($value = $service->getAttribute($key)) {
$method = 'set'.$key;
$definition->$method(XmlUtils::phpize($value));
$definition->$method($value = XmlUtils::phpize($value));
}
}
if ($value = $service->getAttribute('lazy')) {
$definition->setLazy((bool) $value = XmlUtils::phpize($value));
if (\is_string($value)) {
$definition->addTag('proxy', array('interface' => $value));
}
}

View File

@ -407,7 +407,10 @@ class YamlFileLoader extends FileLoader
}
if (isset($service['lazy'])) {
$definition->setLazy($service['lazy']);
$definition->setLazy((bool) $service['lazy']);
if (\is_string($service['lazy'])) {
$definition->addTag('proxy', array('interface' => $service['lazy']));
}
}
if (isset($service['public'])) {

View File

@ -124,7 +124,7 @@
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="synthetic" type="boolean" />
<xsd:attribute name="lazy" type="boolean" />
<xsd:attribute name="lazy" type="xsd:string" />
<xsd:attribute name="abstract" type="boolean" />
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="parent" type="xsd:string" />
@ -145,7 +145,7 @@
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="lazy" type="boolean" />
<xsd:attribute name="lazy" type="xsd:string" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
@ -167,7 +167,7 @@
<xsd:attribute name="exclude" type="xsd:string" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="lazy" type="boolean" />
<xsd:attribute name="lazy" type="xsd:string" />
<xsd:attribute name="abstract" type="boolean" />
<xsd:attribute name="parent" type="xsd:string" />
<xsd:attribute name="autowire" type="boolean" />

View File

@ -0,0 +1,12 @@
services:
service_container:
class: Symfony\Component\DependencyInjection\ContainerInterface
public: true
synthetic: true
foo:
class: stdClass
public: true
tags:
- { name: proxy, interface: SomeInterface }
lazy: true

View File

@ -0,0 +1,8 @@
<?php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return function (ContainerConfigurator $c) {
$di = $c->services();
$di->set('foo', 'stdClass')->lazy('SomeInterface');
};

View File

@ -10,7 +10,7 @@ services:
tags:
- { name: foo }
- { name: baz }
deprecated: "%service_id%"
deprecated: '%service_id%'
arguments: [1]
factory: f
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar:
@ -19,7 +19,7 @@ services:
tags:
- { name: foo }
- { name: baz }
deprecated: "%service_id%"
deprecated: '%service_id%'
lazy: true
arguments: [1]
factory: f

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="foo" lazy="SomeInterface" />
</services>
</container>

View File

@ -0,0 +1,3 @@
services:
foo:
lazy: SomeInterface

View File

@ -72,9 +72,11 @@ class PhpFileLoaderTest extends TestCase
yield array('defaults');
yield array('instanceof');
yield array('prototype');
yield array('prototype_array');
yield array('child');
yield array('php7');
yield array('anonymous');
yield array('lazy_fqcn');
}
/**

View File

@ -816,4 +816,14 @@ class XmlFileLoaderTest extends TestCase
'$factory' => 'factory',
), array_map(function (BoundArgument $v) { return $v->getValues()[0]; }, $definition->getBindings()));
}
public function testFqcnLazyProxy()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('services_lazy_fqcn.xml');
$definition = $container->getDefinition('foo');
$this->assertSame(array(array('interface' => 'SomeInterface')), $definition->getTag('proxy'));
}
}

View File

@ -739,4 +739,14 @@ class YamlFileLoaderTest extends TestCase
'$factory' => 'factory',
), array_map(function (BoundArgument $v) { return $v->getValues()[0]; }, $definition->getBindings()));
}
public function testFqcnLazyProxy()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('services_lazy_fqcn.yml');
$definition = $container->getDefinition('foo');
$this->assertSame(array(array('interface' => 'SomeInterface')), $definition->getTag('proxy'));
}
}