Passed
Push — master ( 7c747f...11d3a2 )
by
unknown
03:14 queued 59s
created

ProxyManager   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 124
Duplicated Lines 0 %

Test Coverage

Coverage 97.3%

Importance

Changes 6
Bugs 4 Features 0
Metric Value
eloc 39
dl 0
loc 124
ccs 36
cts 37
cp 0.973
rs 10
c 6
b 4
f 0
wmc 13

4 Methods

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