RuntimeClass::generateCode()   F
last analyzed

Complexity

Conditions 11
Paths 768

Size

Total Lines 50
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 50
ccs 26
cts 26
cp 1
rs 3.5
c 0
b 0
f 0
cc 11
eloc 26
nc 768
nop 0
crap 11

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace Igni\Utils\ReflectionApi;
4
5
use Igni\Utils\Exception\ReflectionApiException;
6
use Igni\Utils\ReflectionApi;
7
8
final class RuntimeClass implements CodeGenerator
9
{
10
    use FinalTrait;
11
    use AbstractTrait;
12
13
    /**
14
     * @var string
15
     */
16
    private $class;
17
18
    /**
19
     * @var string
20
     */
21
    private $className;
22
23
    /**
24
     * @var string[]
25
     */
26
    private $namespace;
27
28
    /**
29
     * @var string
30
     */
31
    private $extends;
32
33
    /**
34
     * @var string[]
35
     */
36
    private $implements = [];
37
38
    /**
39
     * @var string[]
40
     */
41
    private $uses = [];
42
43
    /**
44
     * @var bool
45
     */
46
    private $isLoaded = false;
47
48
    /**
49
     * @var RuntimeMethod[]
50
     */
51
    private $methods = [];
52
53
    /**
54
     * @var RuntimeProperty[]
55
     */
56
    private $properties = [];
57
58
    /**
59
     * @var RuntimeConstant[]
60
     */
61
    private $constant = [];
62
63
    /**
64
     * @var string[]
65
     */
66
    private static $registeredClasses = [];
67
68 16
    public function __construct(string $class, string ...$implements)
69
    {
70 16
        if (isset(self::$registeredClasses[$class]) || class_exists($class) || interface_exists($class)) {
71 1
            throw ReflectionApiException::forAlreadyDefinedClass($class);
72
        }
73
74 15
        $this->class = $class;
75 15
        $classParts = explode('\\', $class);
76 15
        $this->className = array_pop($classParts);
77 15
        $this->namespace = implode('\\', $classParts);
0 ignored issues
show
Documentation Bug introduced by
It seems like implode('\\', $classParts) of type string is incompatible with the declared type array<integer,string> of property $namespace.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
78
79 15
        $this->implements(...$implements);
80 15
    }
81
82 2
    public function getNamespace(): string
83
    {
84 2
        return $this->namespace;
85
    }
86
87 1
    public function getClass(): string
88
    {
89 1
        return $this->class;
90
    }
91
92 1
    public function getClassName(): string
93
    {
94 1
        return $this->className;
95
    }
96
97 15
    public function implements(string ...$interfaces): self
98
    {
99 15
        foreach ($interfaces as &$interface) {
100 2
            if (!interface_exists($interface)) {
101
                throw ReflectionApiException::forNonExistentInterface($interface);
102
            }
103 2
            $interface = '\\' . $interface;
104
        }
105 15
        $this->implements = $interfaces;
106
107 15
        return $this;
108
    }
109
110 1
    public function extends(string $class): self
111
    {
112 1
        if (!class_exists($class)) {
113
            throw ReflectionApiException::forNonExistentClass($class);
114
        }
115 1
        $this->extends = '\\' . $class;
116
117 1
        return $this;
118
    }
119
120
    public function use(string ...$traits): self
121
    {
122
        foreach ($traits as &$trait) {
123
            if (!trait_exists($trait)) {
124
                throw ReflectionApiException::forNonExistentTrait($trait);
125
            }
126
            $trait = '\\' . $trait;
127
        }
128
        $this->uses = $traits;
129
130
        return $this;
131
    }
132
133
    public function isUsing(string $name): bool
134
    {
135
        return in_array('\\' . $name, $this->uses);
136
    }
137
138 1
    public function isExtending(string $name): bool
139
    {
140 1
        return ('\\' . $name) === $this->extends;
141
    }
142
143 1
    public function implementsInterface(string $interface): bool
144
    {
145 1
        return in_array('\\' . $interface, $this->implements);
146
    }
147
148 2
    public function addMethod(RuntimeMethod $method): self
149
    {
150 2
        $this->methods[$method->getName()] = $method;
151
152 2
        return $this;
153
    }
154
155 1
    public function hasMethod(string $name): bool
156
    {
157 1
        return isset($this->methods[$name]);
158
    }
159
160 1
    public function addProperty(RuntimeProperty $property): self
161
    {
162 1
        $this->properties[$property->getName()] = $property;
163
164 1
        return $this;
165
    }
166
167 1
    public function addConstant(RuntimeConstant $constant): self
168
    {
169 1
        $this->constant[$constant->getName()] = $constant;
170
171 1
        return $this;
172
    }
173
174 1
    public function hasConstant(string $name): bool
175
    {
176 1
        return isset($this->constant[$name]);
177
    }
178
179 1
    public function hasProperty(string $name): bool
180
    {
181 1
        return isset($this->properties[$name]);
182
    }
183
184 10
    public function generateCode(): string
185
    {
186 10
        $code = '';
187 10
        if ($this->namespace) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->namespace of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
188 1
            $code .= "namespace {$this->namespace} {\n";
189
        }
190
191 10
        if ($this->final) {
192 1
            $code .= 'final ';
193
        }
194
195 10
        if ($this->abstract) {
196 1
            $code .= 'abstract ';
197
        }
198
199 10
        $code .= "class {$this->className}";
200
201 10
        if ($this->extends) {
202 1
            $code .= " extends {$this->extends}";
203
        }
204
205 10
        if ($this->implements) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->implements of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
206 2
            $code .= ' implements ' . implode(',', $this->implements);
207
        }
208
209 10
        $code .= "\n{\n";
210
211 10
        foreach ($this->constant as $constant) {
212 1
            $code .= "\t{$constant->generateCode()}\n";
213
        }
214
215 10
        foreach ($this->properties as $property) {
216 1
            $code .= "\t{$property->generateCode()}\n";
217
        }
218
219 10
        foreach ($this->methods as $method) {
220 2
            $methodBody = explode("\n", $method->generateCode());
221 2
            foreach ($methodBody as $methodLine) {
222 2
                $code .= "\t" . $methodLine . PHP_EOL;
223
            }
224
        }
225
226 10
        $code .= "}\n";
227
228 10
        if ($this->namespace) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->namespace of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
229 1
            $code .= "\n}\n";
230
        }
231
232 10
        return $code;
233
    }
234
235 1
    public function createInstance()
236
    {
237 1
        $this->load();
238
239 1
        return ReflectionApi::createInstance($this->class);
240
    }
241
242 2
    public function register(): bool
243
    {
244 2
        if (isset(self::$registeredClasses[$this->class])) {
245
            return false;
246
        }
247
248 2
        self::$registeredClasses[$this->class] = $this->class;
249
250 2
        return true;
251
    }
252
253 2
    public function load(): bool
254
    {
255 2
        if (!$this->register()) {
256
            throw ReflectionApiException::forAlreadyDefinedClass($this->class);
257
        }
258
259 2
        if ($this->isLoaded) {
260
            return $this->isLoaded;
261
        }
262
263
        try {
264 2
            $fileName = tempnam(sys_get_temp_dir(), 'igni-reflection-api');
265 2
            $file = fopen($fileName,'w');
266 2
            fwrite($file, '<?php ' . $this);
267 2
            fclose($file);
268 2
            $this->isLoaded = true;
269
270 2
            require_once $fileName;
271
272
        } catch (\Throwable $exception) {
273
            return false;
274
        }
275
276 2
        return true;
277
    }
278
279 2
    public function __toString(): string
280
    {
281 2
        return $this->generateCode();
282
    }
283
}
284