Passed
Push — master ( cd5e00...1a9c2f )
by Jelmer
01:52
created

DiCompiler::compile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace jschreuder\MiddleDi;
4
5
use ReflectionClass;
6
use ReflectionNamedType;
7
8
final class DiCompiler implements DiCompilerInterface
9
{
10
    const COMPILED_EXTENSION = '__Compiled';
11
12
    private string $parentDi;
13
14 14
    public function __construct(string $parentDi)
15
    {
16 14
        $this->parentDi = $parentDi;
17
    }
18
19 12
    private function getCompiledName(): string
20
    {
21 12
        return $this->parentDi . self::COMPILED_EXTENSION;
22
    }
23
24 6
    public function compiledClassExists(): bool
25
    {
26 6
        return class_exists($this->getCompiledName());
27
    }
28
29 5
    public function compile(): void
30
    {
31 5
        if ($this->compiledClassExists()) {
32 1
            throw new \RuntimeException('Cannot recompile already compiled container');
33
        }
34
35 4
        eval(substr($this->generateCode(), 6));
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
36
    }
37
38 5
    public function generateCode(): string
39
    {
40 5
        $parent = new ReflectionClass($this->parentDi);
41 5
        return $this->generateHeader($parent)
42 5
            .$this->generateMethods($parent)
43 5
            .$this->generateFooter();
44
    }
45
46 5
    private function generateHeader(ReflectionClass $parent)
47
    {
48 5
        return
49 5
'<?php
50
51 5
namespace ' . $parent->getNamespaceName() . ';
52
53 5
use ' . $parent->getName() . ';
54
55 5
class ' . $parent->getShortName() . self::COMPILED_EXTENSION . ' extends ' . $parent->getShortName() . '
56
{
57
    private array $__services = [];
58
59
    private function __service(string $method, ?string $instanceName = null)
60
    {
61
        $suffix = is_null($instanceName) ? \'\' : \'.\' . $instanceName;
62
        return $this->__services[$method . $suffix] ?? ($this->__services[$method . $suffix] = parent::{$method}($instanceName));
63
    }
64
65 5
';
66
    }
67
68 5
    private function generateMethods(ReflectionClass $parent)
69
    {
70 5
        $code = '';
71
72 5
        foreach ($parent->getMethods() as $method) {
73 5
            if ($method->isPublic() && (substr($method->getName(), 0, 3) !== 'get')) {
74 2
                continue;
75
            }
76 5
            if (!$method->hasReturnType()) {
77 1
                throw new \RuntimeException('Service definitions must have return types');
78
            }
79 4
            if ($method->getNumberOfParameters() > 1) {
80 1
                throw new \RuntimeException('Service definitions cannot take more than a name parameter');
81
            }
82 3
            if ($method->getNumberOfParameters() === 1) {
83 3
                $parameter = $method->getParameters()[0];
84
85 3
                if (!is_a($parameter->getType(), ReflectionNamedType::class) || $parameter->getType()->getName() !== 'string') {
86 1
                    throw new \RuntimeException('Service factories are only allowed a single named nullable string argument.');
87
                }
88
            }
89
90 2
            $returnType = $method->getReturnType();
91 2
            if (!in_array($returnType, ['mixed', 'string', 'int', 'bool', 'float', 'resource', 'void', 'null', 'array', 'object'])) {
92 2
                $returnType = '\\' . $returnType;
93
            }
94
95 2
            $code .= '
96 2
    public function '.$method->getName().'(?string $instanceName = null): '.$returnType.'
97
    {
98 2
        return $this->__service(\'' . $method->getName() . '\', $instanceName);
99
    }
100 2
';
101
        }
102
103 2
        return $code;
104
    }
105
106 2
    private function generateFooter(): string
107
    {
108 2
        return PHP_EOL . '}' . PHP_EOL;
109
    }
110
111 6
    public function newInstance(array ...$args): mixed
112
    {
113 6
        $reflectedClass = new ReflectionClass($this->getCompiledName());
114 6
        return $reflectedClass->newInstance(...$args);
115
    }
116
}
117