diff --git a/.php_cs.dist b/.php_cs.dist index 4720014f40..620c6ee2db 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -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', diff --git a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md index 56c8b20e28..3435a4a186 100644 --- a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md +++ b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.2.0 +----- + + * allowed creating lazy-proxies from interfaces + 3.3.0 ----- diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php similarity index 85% rename from src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php rename to src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php index f41fc20b5d..a37aa2e04c 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php @@ -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(); } diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php deleted file mode 100644 index 3298b84d46..0000000000 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * 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(); - } -} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 33fc49e101..cc68d65058 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -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); diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php index 1d9432f622..356fd44cda 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php @@ -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; } } diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index 0871c4ce9d..3ce787b852 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -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 <<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); } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php new file mode 100644 index 0000000000..648eb36d4b --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php @@ -0,0 +1,31 @@ +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(); + } +}; diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php new file mode 100644 index 0000000000..1b5ea69c90 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php @@ -0,0 +1,165 @@ +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; + } + + +} diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index e12b25fcb0..634ab6627c 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -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 = "dumper->getProxyCode($definition); + $factory = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFooService(false)'); + $factory = <<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(); +} diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index a224d5a8a9..441389af82 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -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" diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 2ddbe41744..37d35c1a09 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -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', diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/LazyTrait.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/LazyTrait.php index 2af867c19b..d8ea55e82f 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/LazyTrait.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/Traits/LazyTrait.php @@ -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; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 71e991c9ff..584e3992c9 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -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)); } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 11eb8e45c7..79b377f0bd 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -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'])) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index 21e3b59310..2e17476cd1 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -124,7 +124,7 @@ - + @@ -145,7 +145,7 @@ - + @@ -167,7 +167,7 @@ - + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.expected.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.expected.yml new file mode 100644 index 0000000000..d5a272c4bf --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.expected.yml @@ -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 diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.php new file mode 100644 index 0000000000..7cde4ef2d0 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/lazy_fqcn.php @@ -0,0 +1,8 @@ +services(); + $di->set('foo', 'stdClass')->lazy('SomeInterface'); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml index e8a03691c9..ebfe087d77 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/prototype_array.expected.yml @@ -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 diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_lazy_fqcn.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_lazy_fqcn.xml new file mode 100644 index 0000000000..f7783a89a8 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_lazy_fqcn.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_lazy_fqcn.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_lazy_fqcn.yml new file mode 100644 index 0000000000..3ac3a1a762 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_lazy_fqcn.yml @@ -0,0 +1,3 @@ +services: + foo: + lazy: SomeInterface diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php index b584a6922c..e7c7f009e3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php @@ -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'); } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 2e87291bf2..27a3d8ca94 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -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')); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 8ddc15f888..4e9ca2a489 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -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')); + } }