Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. ProxyHelper.php

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\VarExporter

Code

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>-&gt;<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>-&gt;<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;
}
RSS feed
Powered by Drupal