ProxyManager   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 137
Duplicated Lines 0 %

Test Coverage

Coverage 94.59%

Importance

Changes 7
Bugs 4 Features 0
Metric Value
eloc 39
c 7
b 4
f 0
dl 0
loc 137
ccs 35
cts 37
cp 0.9459
rs 10
wmc 13

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A getProxyClassName() 0 3 1
A generateProxyClassConfig() 0 26 6
A createObjectProxy() 0 38 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Proxy;
6
7
use Exception;
8
use Yiisoft\Proxy\Config\ClassConfig;
9
10
final class ProxyManager
11
{
12
    /**
13
     * @var ClassRenderer A class renderer dependency.
14
     */
15
    private ClassRenderer $classRenderer;
16
    /**
17
     * @var ClassConfigFactory A class config factory dependency.
18
     */
19
    private ClassConfigFactory $classConfigFactory;
20
    /**
21
     * @var ClassCache|null A class cache dependency (optional).
22
     */
23
    private ?ClassCache $classCache;
24
25
    /**
26
     * A suffix appended to proxy class names / files.
27
     */
28
    public const PROXY_SUFFIX = 'Proxy';
29
30
    /**
31
     * @param string|null $cachePath Cache path, optional, {@see ClassCache::$cachePath}.
32
     */
33 10
    public function __construct(string $cachePath = null)
34
    {
35 10
        $this->classCache = $cachePath ? new ClassCache($cachePath) : null;
36 10
        $this->classRenderer = new ClassRenderer();
37 10
        $this->classConfigFactory = new ClassConfigFactory();
38
    }
39
40
    /**
41
     * Creates object proxy based on an interface / a class and parent proxy class.
42
     *
43
     * @param string $baseStructure Either or an interface or a class for proxying method calls.
44
     * @param string $parentProxyClass A base proxy class which acts like a parent for dynamically created proxy.
45
     * {@see ObjectProxy} or a class extended from it must be used.
46
     * @param array $proxyConstructorArguments A list of arguments passed to proxy constructor
47
     * ({@see ObjectProxy::__construct}).
48
     *
49
     * @psalm-param class-string $baseStructure
50
     *
51
     * @throws Exception In case of error during creation or working with cache / requiring PHP code.
52
     *
53
     * @return ObjectProxy A subclass of {@see ObjectProxy}.
54
     */
55 10
    public function createObjectProxy(
56
        string $baseStructure,
57
        string $parentProxyClass,
58
        array $proxyConstructorArguments
59
    ): ObjectProxy {
60 10
        $className = $baseStructure . self::PROXY_SUFFIX;
61
        /** @psalm-var class-string $shortClassName */
62 10
        $shortClassName = self::getProxyClassName($className);
63
64 10
        if (class_exists($shortClassName)) {
65
            /**
66
             * @var ObjectProxy
67
             * @psalm-suppress MixedMethodCall
68
             */
69 5
            return new $shortClassName(...$proxyConstructorArguments);
70
        }
71
72 5
        $classDeclaration = $this->classCache?->get($className, $parentProxyClass);
73 5
        if (!$classDeclaration) {
74 5
            $classConfig = $this->classConfigFactory->getClassConfig($baseStructure);
75 5
            $classConfig = $this->generateProxyClassConfig($classConfig, $parentProxyClass);
76 5
            $classDeclaration = $this->classRenderer->render($classConfig);
77 5
            $this->classCache?->set($baseStructure, $parentProxyClass, $classDeclaration);
78
        }
79 5
        if (!$this->classCache) {
80
            /** @psalm-suppress UnusedFunctionCall Bug https://github.com/vimeo/psalm/issues/8406 */
81
            eval(str_replace('<?php', '', $classDeclaration));
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
82
        } else {
83 5
            $path = $this->classCache->getClassPath($baseStructure, $parentProxyClass);
84
            /** @psalm-suppress UnresolvableInclude */
85 5
            require $path;
86
        }
87
88
        /**
89
         * @var ObjectProxy
90
         * @psalm-suppress MixedMethodCall
91
         */
92 5
        return new $shortClassName(...$proxyConstructorArguments);
93
    }
94
95
    /**
96
     * Generates class config for using with proxy from a regular class config.
97
     *
98
     * @param ClassConfig $classConfig Initial class config.
99
     * @param string $parentProxyClass A base proxy class which acts like a parent for dynamically created proxy.
100
     * {@see ObjectProxy} or a class extended from it must be used.
101
     *
102
     * @return ClassConfig Modified class config ready for using with proxy.
103
     */
104 5
    private function generateProxyClassConfig(ClassConfig $classConfig, string $parentProxyClass): ClassConfig
105
    {
106 5
        if ($classConfig->isInterface) {
107 3
            $classConfig->isInterface = false;
108 3
            $classConfig->interfaces = [$classConfig->name];
109
        }
110
111 5
        $classConfig->parent = $parentProxyClass;
112 5
        $classConfig->name .= self::PROXY_SUFFIX;
113 5
        $classConfig->shortName = self::getProxyClassName($classConfig->name);
114
115 5
        foreach ($classConfig->methods as $methodIndex => $method) {
116 5
            if ($method->name === '__construct') {
117 1
                unset($classConfig->methods[$methodIndex]);
118
119 1
                continue;
120
            }
121
122 5
            foreach ($method->modifiers as $index => $modifier) {
123 5
                if ($modifier === 'abstract') {
124
                    unset($classConfig->methods[$methodIndex]->modifiers[$index]);
125
                }
126
            }
127
        }
128
129 5
        return $classConfig;
130
    }
131
132
    /**
133
     * Transforms full class / interface name with namespace to short class name for using in proxy. For example:
134
     *
135
     * - `Yiisoft\Proxy\Tests\Stub\GraphInterfaceProxy` becomes `Yiisoft_Proxy_Tests_Stub_GraphInterfaceProxy`.
136
     * - `Yiisoft\Proxy\Tests\Stub\GraphProxy` becomes `Yiisoft_Proxy_Tests_Stub_GraphProxy`.
137
     *
138
     * and so on.
139
     *
140
     * @param string $fullClassName Initial class name.
141
     *
142
     * @return string Proxy class name.
143
     */
144 10
    private static function getProxyClassName(string $fullClassName): string
145
    {
146 10
        return str_replace('\\', '_', $fullClassName);
147
    }
148
}
149