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:
commit
a5709ee9ba
@ -19,6 +19,7 @@ return PhpCsFixer\Config::create()
|
|||||||
->in(__DIR__.'/src')
|
->in(__DIR__.'/src')
|
||||||
->append(array(__FILE__))
|
->append(array(__FILE__))
|
||||||
->exclude(array(
|
->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
|
// 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/Cache/Tests/Marshaller/Fixtures',
|
||||||
'Symfony/Component/DependencyInjection/Tests/Fixtures',
|
'Symfony/Component/DependencyInjection/Tests/Fixtures',
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
4.2.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* allowed creating lazy-proxies from interfaces
|
||||||
|
|
||||||
3.3.0
|
3.3.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -18,14 +18,14 @@ use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenera
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
class LazyLoadingValueHolderFactoryV2 extends BaseFactory
|
class LazyLoadingValueHolderFactory extends BaseFactory
|
||||||
{
|
{
|
||||||
private $generator;
|
private $generator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected function getGenerator(): ProxyGeneratorInterface
|
public function getGenerator(): ProxyGeneratorInterface
|
||||||
{
|
{
|
||||||
return $this->generator ?: $this->generator = new LazyLoadingValueHolderGenerator();
|
return $this->generator ?: $this->generator = new LazyLoadingValueHolderGenerator();
|
||||||
}
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,6 @@
|
|||||||
namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator;
|
namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator;
|
||||||
|
|
||||||
use ProxyManager\Configuration;
|
use ProxyManager\Configuration;
|
||||||
use ProxyManager\Factory\LazyLoadingValueHolderFactory;
|
|
||||||
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
|
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
|
||||||
use ProxyManager\Proxy\LazyLoadingInterface;
|
use ProxyManager\Proxy\LazyLoadingInterface;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
@ -26,9 +25,6 @@ use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInt
|
|||||||
*/
|
*/
|
||||||
class RuntimeInstantiator implements InstantiatorInterface
|
class RuntimeInstantiator implements InstantiatorInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var LazyLoadingValueHolderFactory
|
|
||||||
*/
|
|
||||||
private $factory;
|
private $factory;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
@ -36,11 +32,7 @@ class RuntimeInstantiator implements InstantiatorInterface
|
|||||||
$config = new Configuration();
|
$config = new Configuration();
|
||||||
$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
|
$config->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
|
||||||
|
|
||||||
if (method_exists('ProxyManager\Version', 'getVersion')) {
|
$this->factory = new LazyLoadingValueHolderFactory($config);
|
||||||
$this->factory = new LazyLoadingValueHolderFactoryV2($config);
|
|
||||||
} else {
|
|
||||||
$this->factory = new LazyLoadingValueHolderFactoryV1($config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,9 +41,9 @@ class RuntimeInstantiator implements InstantiatorInterface
|
|||||||
public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator)
|
public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator)
|
||||||
{
|
{
|
||||||
return $this->factory->createProxy(
|
return $this->factory->createProxy(
|
||||||
$definition->getClass(),
|
$this->factory->getGenerator()->getProxifiedClass($definition),
|
||||||
function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
|
function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) {
|
||||||
$wrappedInstance = call_user_func($realInstantiator);
|
$wrappedInstance = \call_user_func($realInstantiator);
|
||||||
|
|
||||||
$proxy->setProxyInitializer(null);
|
$proxy->setProxyInitializer(null);
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper;
|
namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper;
|
||||||
|
|
||||||
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator;
|
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator;
|
||||||
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
use Zend\Code\Generator\ClassGenerator;
|
use Zend\Code\Generator\ClassGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,6 +20,13 @@ use Zend\Code\Generator\ClassGenerator;
|
|||||||
*/
|
*/
|
||||||
class LazyLoadingValueHolderGenerator extends BaseGenerator
|
class LazyLoadingValueHolderGenerator extends BaseGenerator
|
||||||
{
|
{
|
||||||
|
private $fluentSafe = false;
|
||||||
|
|
||||||
|
public function setFluentSafe(bool $fluentSafe)
|
||||||
|
{
|
||||||
|
$this->fluentSafe = $fluentSafe;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -26,6 +34,52 @@ class LazyLoadingValueHolderGenerator extends BaseGenerator
|
|||||||
{
|
{
|
||||||
parent::generate($originalClass, $classGenerator);
|
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')) {
|
if ($classGenerator->hasMethod('__destruct')) {
|
||||||
$destructor = $classGenerator->getMethod('__destruct');
|
$destructor = $classGenerator->getMethod('__destruct');
|
||||||
$body = $destructor->getBody();
|
$body = $destructor->getBody();
|
||||||
@ -37,5 +91,53 @@ class LazyLoadingValueHolderGenerator extends BaseGenerator
|
|||||||
|
|
||||||
$destructor->setBody($newBody);
|
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper;
|
|||||||
use ProxyManager\Generator\ClassGenerator;
|
use ProxyManager\Generator\ClassGenerator;
|
||||||
use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy;
|
use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy;
|
||||||
use ProxyManager\Version;
|
use ProxyManager\Version;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|
||||||
use Symfony\Component\DependencyInjection\Definition;
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface;
|
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface;
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ class ProxyDumper implements DumperInterface
|
|||||||
*/
|
*/
|
||||||
public function isProxyCandidate(Definition $definition)
|
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);
|
$proxyClass = $this->getProxyClassName($definition);
|
||||||
|
|
||||||
$hasStaticConstructor = $this->generateProxyClass($definition)->hasMethod('staticProxyConstructor');
|
|
||||||
|
|
||||||
$constructorCall = sprintf($hasStaticConstructor ? '%s::staticProxyConstructor' : 'new %s', '\\'.$proxyClass);
|
|
||||||
|
|
||||||
return <<<EOF
|
return <<<EOF
|
||||||
if (\$lazyLoad) {
|
if (\$lazyLoad) {
|
||||||
$instantiation \$this->createProxy('$proxyClass', function () {
|
$instantiation \$this->createProxy('$proxyClass', function () {
|
||||||
return $constructorCall(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) {
|
return \\$proxyClass::staticProxyConstructor(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) {
|
||||||
\$wrappedInstance = $factoryCode;
|
\$wrappedInstance = $factoryCode;
|
||||||
|
|
||||||
\$proxy->setProxyInitializer(null);
|
\$proxy->setProxyInitializer(null);
|
||||||
@ -91,12 +86,6 @@ EOF;
|
|||||||
{
|
{
|
||||||
$code = $this->classGenerator->generate($this->generateProxyClass($definition));
|
$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', '<')) {
|
if (version_compare(self::getProxyManagerVersion(), '2.2', '<')) {
|
||||||
$code = preg_replace(
|
$code = preg_replace(
|
||||||
'/((?:\$(?:this|initializer|instance)->)?(?:publicProperties|initializer|valueHolder))[0-9a-f]++/',
|
'/((?:\$(?:this|initializer|instance)->)?(?:publicProperties|initializer|valueHolder))[0-9a-f]++/',
|
||||||
@ -122,20 +111,26 @@ EOF;
|
|||||||
*/
|
*/
|
||||||
private function getProxyClassName(Definition $definition): string
|
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
|
private function generateProxyClass(Definition $definition): ClassGenerator
|
||||||
{
|
{
|
||||||
$generatedClass = new ClassGenerator($this->getProxyClassName($definition));
|
$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;
|
return $generatedClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getIdentifierSuffix(Definition $definition): string
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -120,6 +120,61 @@ class ProxyDumperTest extends TestCase
|
|||||||
$this->dumper->getProxyFactoryCode($definition, 'foo');
|
$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
|
* @return array
|
||||||
*/
|
*/
|
||||||
@ -142,3 +197,34 @@ class ProxyDumperTest extends TestCase
|
|||||||
return $definitions;
|
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();
|
||||||
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"symfony/dependency-injection": "~3.4|~4.0",
|
"symfony/dependency-injection": "~3.4|~4.0",
|
||||||
"ocramius/proxy-manager": "~0.4|~1.0|~2.0"
|
"ocramius/proxy-manager": "~2.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/config": "~3.4|~4.0"
|
"symfony/config": "~3.4|~4.0"
|
||||||
|
@ -44,6 +44,7 @@ class UnusedTagsPass implements CompilerPassInterface
|
|||||||
'messenger.receiver',
|
'messenger.receiver',
|
||||||
'messenger.message_handler',
|
'messenger.message_handler',
|
||||||
'monolog.logger',
|
'monolog.logger',
|
||||||
|
'proxy',
|
||||||
'routing.expression_language_provider',
|
'routing.expression_language_provider',
|
||||||
'routing.loader',
|
'routing.loader',
|
||||||
'security.expression_language_provider',
|
'security.expression_language_provider',
|
||||||
|
@ -16,11 +16,16 @@ trait LazyTrait
|
|||||||
/**
|
/**
|
||||||
* Sets the lazy flag of this service.
|
* 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
|
* @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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -265,10 +265,17 @@ class XmlFileLoader extends FileLoader
|
|||||||
$definition->setChanges(array());
|
$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)) {
|
if ($value = $service->getAttribute($key)) {
|
||||||
$method = 'set'.$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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +407,10 @@ class YamlFileLoader extends FileLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($service['lazy'])) {
|
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'])) {
|
if (isset($service['public'])) {
|
||||||
|
@ -124,7 +124,7 @@
|
|||||||
<xsd:attribute name="shared" type="boolean" />
|
<xsd:attribute name="shared" type="boolean" />
|
||||||
<xsd:attribute name="public" type="boolean" />
|
<xsd:attribute name="public" type="boolean" />
|
||||||
<xsd:attribute name="synthetic" 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="abstract" type="boolean" />
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
<xsd:attribute name="parent" 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="id" type="xsd:string" use="required" />
|
||||||
<xsd:attribute name="shared" type="boolean" />
|
<xsd:attribute name="shared" type="boolean" />
|
||||||
<xsd:attribute name="public" 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="autowire" type="boolean" />
|
||||||
<xsd:attribute name="autoconfigure" type="boolean" />
|
<xsd:attribute name="autoconfigure" type="boolean" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
@ -167,7 +167,7 @@
|
|||||||
<xsd:attribute name="exclude" type="xsd:string" />
|
<xsd:attribute name="exclude" type="xsd:string" />
|
||||||
<xsd:attribute name="shared" type="boolean" />
|
<xsd:attribute name="shared" type="boolean" />
|
||||||
<xsd:attribute name="public" 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="abstract" type="boolean" />
|
||||||
<xsd:attribute name="parent" type="xsd:string" />
|
<xsd:attribute name="parent" type="xsd:string" />
|
||||||
<xsd:attribute name="autowire" type="boolean" />
|
<xsd:attribute name="autowire" type="boolean" />
|
||||||
|
@ -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
|
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||||
|
|
||||||
|
return function (ContainerConfigurator $c) {
|
||||||
|
$di = $c->services();
|
||||||
|
$di->set('foo', 'stdClass')->lazy('SomeInterface');
|
||||||
|
};
|
@ -10,7 +10,7 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: foo }
|
- { name: foo }
|
||||||
- { name: baz }
|
- { name: baz }
|
||||||
deprecated: "%service_id%"
|
deprecated: '%service_id%'
|
||||||
arguments: [1]
|
arguments: [1]
|
||||||
factory: f
|
factory: f
|
||||||
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar:
|
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar:
|
||||||
@ -19,7 +19,7 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: foo }
|
- { name: foo }
|
||||||
- { name: baz }
|
- { name: baz }
|
||||||
deprecated: "%service_id%"
|
deprecated: '%service_id%'
|
||||||
lazy: true
|
lazy: true
|
||||||
arguments: [1]
|
arguments: [1]
|
||||||
factory: f
|
factory: f
|
||||||
|
@ -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>
|
@ -0,0 +1,3 @@
|
|||||||
|
services:
|
||||||
|
foo:
|
||||||
|
lazy: SomeInterface
|
@ -72,9 +72,11 @@ class PhpFileLoaderTest extends TestCase
|
|||||||
yield array('defaults');
|
yield array('defaults');
|
||||||
yield array('instanceof');
|
yield array('instanceof');
|
||||||
yield array('prototype');
|
yield array('prototype');
|
||||||
|
yield array('prototype_array');
|
||||||
yield array('child');
|
yield array('child');
|
||||||
yield array('php7');
|
yield array('php7');
|
||||||
yield array('anonymous');
|
yield array('anonymous');
|
||||||
|
yield array('lazy_fqcn');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -816,4 +816,14 @@ class XmlFileLoaderTest extends TestCase
|
|||||||
'$factory' => 'factory',
|
'$factory' => 'factory',
|
||||||
), array_map(function (BoundArgument $v) { return $v->getValues()[0]; }, $definition->getBindings()));
|
), 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'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -739,4 +739,14 @@ class YamlFileLoaderTest extends TestCase
|
|||||||
'$factory' => 'factory',
|
'$factory' => 'factory',
|
||||||
), array_map(function (BoundArgument $v) { return $v->getValues()[0]; }, $definition->getBindings()));
|
), 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'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user