Passed
Pull Request — master (#49)
by
unknown
02:38
created

ProxyManager::generateProxyClassConfig()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.0144

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 8
nop 2
dl 0
loc 20
ccs 11
cts 12
cp 0.9167
crap 5.0144
rs 9.6111
c 0
b 0
f 0
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 8
    public function __construct(string $cachePath = null)
34
    {
35 8
        $this->classCache = $cachePath ? new ClassCache($cachePath) : null;
36 8
        $this->classRenderer = new ClassRenderer();
37 8
        $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
     * @throws Exception In case of error during creation or working with cache / requiring PHP code.
50
     *
51
     * @return ObjectProxy A subclass of {@see ObjectProxy}.
52
     */
53 8
    public function createObjectProxy(
54
        string $baseStructure,
55
        string $parentProxyClass,
56
        array $proxyConstructorArguments
57
    ): ObjectProxy {
58 8
        $className = $baseStructure . self::PROXY_SUFFIX;
59 8
        $shortClassName = self::getProxyClassName($className);
60
61 8
        if (class_exists($shortClassName)) {
62 5
            return new $shortClassName(...$proxyConstructorArguments);
63
        }
64
65 3
        $classDeclaration = $this->classCache?->get($className, $parentProxyClass);
66 3
        if (!$classDeclaration) {
67 3
            $classConfig = $this->classConfigFactory->getClassConfig($baseStructure);
68 3
            $classConfig = $this->generateProxyClassConfig($classConfig, $parentProxyClass);
69 3
            $classDeclaration = $this->classRenderer->render($classConfig);
70 3
            $this->classCache?->set($baseStructure, $parentProxyClass, $classDeclaration);
71
        }
72 3
        if (!$this->classCache) {
73 1
            eval(str_replace('<?php', '', $classDeclaration));
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
74
        } else {
75 2
            $path = $this->classCache->getClassPath($baseStructure, $parentProxyClass);
76 2
            require $path;
77
        }
78 3
        return new $shortClassName(...$proxyConstructorArguments);
79
    }
80
81
    /**
82
     * Generates class config for using with proxy from a regular class config.
83
     *
84
     * @param ClassConfig $classConfig Initial class config.
85
     * @param string $parentProxyClass A base proxy class which acts like a parent for dynamically created proxy.
86
     * {@see ObjectProxy} or a class extended from it must be used.
87
     *
88
     * @return ClassConfig Modified class config ready for using with proxy.
89
     */
90 3
    private function generateProxyClassConfig(ClassConfig $classConfig, string $parentProxyClass): ClassConfig
91
    {
92 3
        if ($classConfig->isInterface) {
93 2
            $classConfig->isInterface = false;
94 2
            $classConfig->interfaces = [$classConfig->name];
95
        }
96
97 3
        $classConfig->parent = $parentProxyClass;
98 3
        $classConfig->name .= self::PROXY_SUFFIX;
99 3
        $classConfig->shortName = self::getProxyClassName($classConfig->name);
100
101 3
        foreach ($classConfig->methods as $methodIndex => $method) {
102 3
            foreach ($method->modifiers as $index => $modifier) {
103 3
                if ($modifier === 'abstract') {
104
                    unset($classConfig->methods[$methodIndex]->modifiers[$index]);
105
                }
106
            }
107
        }
108
109 3
        return $classConfig;
110
    }
111
112
    /**
113
     * Transforms full class / interface name with namespace to short class name for using in proxy. For example:
114
     *
115
     * - `Yiisoft\Proxy\Tests\Stub\GraphInterfaceProxy` becomes `Yiisoft_Proxy_Tests_Stub_GraphInterfaceProxy`.
116
     * - `Yiisoft\Proxy\Tests\Stub\GraphProxy` becomes `Yiisoft_Proxy_Tests_Stub_GraphProxy`.
117
     *
118
     * and so on.
119
     *
120
     * @param string $fullClassName Initial class name.
121
     *
122
     * @return string Proxy class name.
123
     */
124 8
    private static function getProxyClassName(string $fullClassName): string
125
    {
126 8
        return str_replace('\\', '_', $fullClassName);
127
    }
128
}
129