function ProxyHelper::generateLazyProxy
Helps generate lazy-loading virtual proxies.
Parameters
\ReflectionClass[] $interfaces:
Throws
LogicException When the class is incompatible with virtual proxies
1 call to ProxyHelper::generateLazyProxy()
- LazyServiceDumper::getProxyCode in vendor/
symfony/ dependency-injection/ LazyProxy/ PhpDumper/ LazyServiceDumper.php - Generates the code for the lazy proxy.
File
-
vendor/
symfony/ var-exporter/ ProxyHelper.php, line 86
Class
- ProxyHelper
- @author Nicolas Grekas <p@tchwork.com>
Namespace
Symfony\Component\VarExporterCode
public static function generateLazyProxy(?\ReflectionClass $class, array $interfaces = []) : string {
if (!class_exists($class?->name ?? \stdClass::class, false)) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not a class.', $class->name));
}
if ($class?->isFinal()) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name));
}
if (\PHP_VERSION_ID < 80300 && $class?->isReadOnly()) {
throw new LogicException(\sprintf('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly.', $class->name));
}
$methodReflectors = [
$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? [],
];
foreach ($interfaces as $interface) {
if (!$interface->isInterface()) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name));
}
$methodReflectors[] = $interface->getMethods();
}
$methodReflectors = array_merge(...$methodReflectors);
$extendsInternalClass = false;
if ($parent = $class) {
do {
$extendsInternalClass = \stdClass::class !== $parent->name && $parent->isInternal();
} while (!$extendsInternalClass && ($parent = $parent->getParentClass()));
}
$methodsHaveToBeProxied = $extendsInternalClass;
$methods = [];
foreach ($methodReflectors as $method) {
if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) {
continue;
}
$methodsHaveToBeProxied = true;
$trait = new \ReflectionMethod(LazyProxyTrait::class, '__get');
$body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine());
$body[0] = str_replace('): mixed', '): ' . $type, $body[0]);
$methods['__get'] = strtr(implode('', $body) . ' }', [
'Hydrator' => '\\' . Hydrator::class,
'Registry' => '\\' . LazyObjectRegistry::class,
]);
break;
}
foreach ($methodReflectors as $method) {
if ($method->isStatic() && !$method->isAbstract() || isset($methods[$lcName = strtolower($method->name)])) {
continue;
}
if ($method->isFinal()) {
if ($extendsInternalClass || $methodsHaveToBeProxied || method_exists(LazyProxyTrait::class, $method->name)) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name));
}
continue;
}
if (method_exists(LazyProxyTrait::class, $method->name) || $method->isProtected() && !$method->isAbstract()) {
continue;
}
$signature = self::exportSignature($method, true, $args);
$parentCall = $method->isAbstract() ? "throw new \\BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}({$args})";
if ($method->isStatic()) {
$body = " {$parentCall};";
}
elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) {
$body = <<<EOPHP
if (isset(\$this->lazyObjectState)) {
(\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{<span class="php-variable">$method</span>-><span class="php-function-or-constant property member-of-variable">name</span>}({<span class="php-variable">$args</span>});
} else {
{<span class="php-variable">$parentCall</span>};
}
EOPHP;
}
else {
if (!$methodsHaveToBeProxied && !$method->isAbstract()) {
// Skip proxying methods that might return $this
foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) {
if (\in_array($type = ltrim($type, '?'), [
'static',
'object',
], true)) {
continue 2;
}
foreach ([
$class,
$interfaces,
] as $r) {
if ($r && is_a($r->name, $type, true)) {
continue 3;
}
}
}
}
$body = <<<EOPHP
if (isset(\$this->lazyObjectState)) {
return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{<span class="php-variable">$method</span>-><span class="php-function-or-constant property member-of-variable">name</span>}({<span class="php-variable">$args</span>});
}
return {<span class="php-variable">$parentCall</span>};
EOPHP;
}
$methods[$lcName] = " {$signature}\n {\n{$body}\n }";
}
$types = $interfaces = array_unique(array_column($interfaces, 'name'));
$interfaces[] = LazyObjectInterface::class;
$interfaces = implode(', \\', $interfaces);
$parent = $class ? ' extends \\' . $class->name : '';
array_unshift($types, $class ? 'parent' : '');
$type = ltrim(implode('&\\', $types), '&');
if (!$class) {
$trait = new \ReflectionMethod(LazyProxyTrait::class, 'initializeLazyObject');
$body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine());
$body[0] = str_replace('): parent', '): ' . $type, $body[0]);
$methods = [
'initializeLazyObject' => implode('', $body) . ' }',
] + $methods;
}
$body = $methods ? "\n" . implode("\n\n", $methods) . "\n" : '';
$propertyScopes = $class ? self::exportPropertyScopes($class->name) : '[]';
if ($class?->hasMethod('__unserialize') && !$class->getMethod('__unserialize')
->getParameters()[0]
->getType()) {
// fix contravariance type problem when $class declares a `__unserialize()` method without typehint.
$lazyProxyTraitStatement = <<<EOPHP
use \\Symfony\\Component\\VarExporter\\LazyProxyTrait {
__unserialize as private __doUnserialize;
}
EOPHP;
$body .= <<<EOPHP
public function __unserialize(\$data): void
{
\$this->__doUnserialize(\$data);
}
EOPHP;
}
else {
$lazyProxyTraitStatement = <<<EOPHP
use \\Symfony\\Component\\VarExporter\\LazyProxyTrait;
EOPHP;
}
return <<<EOPHP
{<span class="php-variable">$parent</span>} implements \\{<span class="php-variable">$interfaces</span>}
{
{<span class="php-variable">$lazyProxyTraitStatement</span>}
private const LAZY_OBJECT_PROPERTY_SCOPES = {<span class="php-variable">$propertyScopes</span>};
{<span class="php-variable">$body</span>}}
// Help opcache.preload discover always-needed symbols
class_exists(\\Symfony\\Component\\VarExporter\\Internal\\Hydrator::class);
class_exists(\\Symfony\\Component\\VarExporter\\Internal\\LazyObjectRegistry::class);
class_exists(\\Symfony\\Component\\VarExporter\\Internal\\LazyObjectState::class);
EOPHP;
}