Generator::prepareStatmentForInt()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Saxulum\LazyService;
4
5
use PhpParser\Node\Arg;
6
use PhpParser\Node\Expr;
7
use PhpParser\Node\Expr\Array_;
8
use PhpParser\Node\Expr\ArrayItem;
9
use PhpParser\Node\Expr\Assign;
10
use PhpParser\Node\Expr\BinaryOp\Identical;
11
use PhpParser\Node\Expr\ConstFetch;
12
use PhpParser\Node\Expr\MethodCall;
13
use PhpParser\Node\Expr\New_;
14
use PhpParser\Node\Expr\PropertyFetch;
15
use PhpParser\Node\Expr\Variable;
16
use PhpParser\Node\Name;
17
use PhpParser\Node\Param;
18
use PhpParser\Node\Scalar\DNumber;
19
use PhpParser\Node\Scalar\LNumber;
20
use PhpParser\Node\Scalar\String_;
21
use PhpParser\Node\Stmt\Class_;
22
use PhpParser\Node\Stmt\ClassMethod;
23
use PhpParser\Node\Stmt\If_;
24
use PhpParser\Node\Stmt\Namespace_;
25
use PhpParser\Node\Stmt\Property;
26
use PhpParser\Node\Stmt\PropertyProperty;
27
use PhpParser\Node\Stmt\Return_;
28
use PhpParser\PrettyPrinter\Standard as PhpGenerator;
29
30
class Generator
31
{
32
    const PROP_CONTAINER = '__container';
33
    const PROP_ORIGINAL = '__original';
34
35
    /**
36
     * @var PhpGenerator
37
     */
38
    protected $phpGenerator;
39
40
    /**
41
     * @param PhpGenerator $phpGenerator
42
     */
43
    public function __construct(PhpGenerator $phpGenerator)
44
    {
45
        $this->phpGenerator = $phpGenerator;
46
    }
47
48
    /**
49
     * @param Mapping $mapping
50
     * @param string  $path
51
     *
52
     * @return string
53
     */
54
    public function generate(Mapping $mapping, $path)
55
    {
56
        $nodes = array_merge(
57
            $this->generatePropertyNodes(),
58
            $this->generateMethodNodes($mapping)
59
        );
60
61
        $lazyNamespaceParts = explode('\\', $mapping->getLazyClass());
62
        $lazyClass = array_pop($lazyNamespaceParts);
63
64
        $classNode = new Namespace_(
65
            new Name(implode('\\', $lazyNamespaceParts)), array(
66
                new Class_($lazyClass, array(
67
                    'extends' => new Name('\\' . $mapping->getOriginalClass()),
68
                    'stmts' => $nodes,
69
                )),
70
            )
71
        );
72
73
        $phpCode = '<?php' . "\n\n" . $this->phpGenerator->prettyPrint(array($classNode));
74
75
        file_put_contents($path . DIRECTORY_SEPARATOR . $lazyClass . '.php', $phpCode);
76
    }
77
78
    /**
79
     * @param string $class
80
     *
81
     * @return \ReflectionClass
82
     *
83
     * @throws \Exception
84
     */
85
    protected function getReflectionClass($class)
86
    {
87
        if (!class_exists($class)) {
88
            throw new \Exception(sprintf('Unknown class: %s', $class));
89
        }
90
91
        return new \ReflectionClass($class);
92
    }
93
94
    /**
95
     * @return Property[]
96
     */
97
    protected function generatePropertyNodes()
98
    {
99
        return array(
100
            new Property(2,
101
                array(
102
                    new PropertyProperty(self::PROP_CONTAINER),
103
                )
104
            ),
105
            new Property(2,
106
                array(
107
                    new PropertyProperty(self::PROP_ORIGINAL),
108
                )
109
            ),
110
        );
111
    }
112
113
    /**
114
     * @param Mapping $mapping
115
     *
116
     * @return ClassMethod[]
117
     */
118
    protected function generateMethodNodes(Mapping $mapping)
119
    {
120
        $reflectionClass = $this->getReflectionClass($mapping->getOriginalClass());
121
122
        $classMethodNodes = array();
123
        foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
124
            if ($reflectionMethod->getName() === '__construct') {
125
                $classMethodNodes[] = $this->generateMethodConstructNode();
126
                $classMethodNodes[] = $this->generateMethodOriginalNode($mapping, $reflectionMethod);
127
            } else {
128
                $classMethodNodes[] = $this->generateMethodNode($reflectionMethod);
129
            }
130
        }
131
132
        return $classMethodNodes;
133
    }
134
135
    /**
136
     * @return ClassMethod
137
     */
138
    protected function generateMethodConstructNode()
139
    {
140
        return new ClassMethod('__construct', array(
141
            'type' => 1,
142
            'params' => array(
143
                new Param(
144
                    'container',
145
                    null,
146
                    '\Saxulum\LazyService\Container\ReaderInterface'
147
                ),
148
            ),
149
            'stmts' => array(
150
                new Assign(
151
                    new PropertyFetch(new Variable('this'), self::PROP_CONTAINER),
152
                    new Variable('container')
153
                ),
154
            ),
155
        ));
156
    }
157
158
    /**
159
     * @param Mapping           $mapping
160
     * @param \ReflectionMethod $reflectionMethod
161
     *
162
     * @return ClassMethod
163
     */
164
    protected function generateMethodOriginalNode(Mapping $mapping, $reflectionMethod)
165
    {
166
        $constructArgumentsByName = $this->getConstructArgumentsByName($mapping);
167
        $args = $this->generateOriginalArgumentNodes($reflectionMethod, $constructArgumentsByName);
168
169
        return new ClassMethod(self::PROP_ORIGINAL, array(
170
            'type' => 2,
171
            'stmts' => array(
172
                new If_(
173
                    new Expr\BinaryOp\Identical(
174
                        new ConstFetch(new Name('null')),
175
                        new PropertyFetch(new Variable('this'), self::PROP_ORIGINAL)
176
                    ),
177
                    array(
178
                        'stmts' => array(
179
                            new Assign(
180
                                new PropertyFetch(new Variable('this'), self::PROP_ORIGINAL),
181
                                new New_(new Name('\\' . $mapping->getOriginalClass()), $args)
182
                            ),
183
                        ),
184
                    )
185
                ),
186
                new Return_(
187
                    new PropertyFetch(new Variable('this'), self::PROP_ORIGINAL)
188
                ),
189
            ),
190
        ));
191
    }
192
193
    /**
194
     * @param Mapping $mapping
195
     *
196
     * @return ConstructArgument[]
197
     */
198
    protected function getConstructArgumentsByName(Mapping $mapping)
199
    {
200
        $constructArgumentsByName = array();
201
        foreach ($mapping->getOriginalClassConstructArguments() as $constructArgument) {
202
            $constructArgumentsByName[$constructArgument->getName()] = $constructArgument;
203
        }
204
205
        return $constructArgumentsByName;
206
    }
207
208
    /**
209
     * @param \ReflectionMethod   $reflectionMethod
210
     * @param ConstructArgument[] $constructArgumentsByName
211
     *
212
     * @return Arg[]
213
     */
214
    protected function generateOriginalArgumentNodes(\ReflectionMethod $reflectionMethod, array $constructArgumentsByName)
215
    {
216
        $args = array();
217
        foreach ($reflectionMethod->getParameters() as $reflectionParameter) {
218
            if (isset($constructArgumentsByName[$reflectionParameter->getName()])) {
219
                $constructArgument = $constructArgumentsByName[$reflectionParameter->getName()];
220
                $args[] = new MethodCall(
221
                    new PropertyFetch(new Variable('this'), self::PROP_CONTAINER),
222
                    $constructArgument->getContainerMethod(),
223
                    array(
224
                        new Arg(new String_($constructArgument->getContainerKey())),
225
                    )
226
                );
227
            } else {
228
                $args[] = new Arg(new ConstFetch(new Name('null')));
229
            }
230
        }
231
232
        return $args;
233
    }
234
235
    /**
236
     * @param \ReflectionMethod $reflectionMethod
237
     *
238
     * @return ClassMethod
239
     */
240
    protected function generateMethodNode(\ReflectionMethod $reflectionMethod)
241
    {
242
        return new ClassMethod($reflectionMethod->getName(), array(
243
            'type' => 1,
244
            'params' => $this->generateParameterNodes($reflectionMethod),
245
            'stmts' => array(
246
                new Return_(
247
                    new MethodCall(
248
                        new MethodCall(new Variable('this'), self::PROP_ORIGINAL),
249
                        $reflectionMethod->getName(),
250
                        $this->generateArgumentNodes($reflectionMethod)
251
                    )
252
                ),
253
            ),
254
        ));
255
    }
256
257
    /**
258
     * @param \ReflectionMethod $reflectionMethod
259
     *
260
     * @return Expr[]
261
     */
262
    protected function generateParameterNodes(\ReflectionMethod $reflectionMethod)
263
    {
264
        $params = array();
265
        foreach ($reflectionMethod->getParameters() as $reflectionParameter) {
266
            $params[] = $this->generateParameterNode($reflectionParameter);
267
        }
268
269
        return $params;
270
    }
271
272
    /**
273
     * @param \ReflectionMethod $reflectionMethod
274
     *
275
     * @return Arg[]
276
     */
277
    protected function generateArgumentNodes(\ReflectionMethod $reflectionMethod)
278
    {
279
        $args = array();
280
        foreach ($reflectionMethod->getParameters() as $reflectionParameter) {
281
            $args[] = new Arg(new Variable($reflectionParameter->getName()));
282
        }
283
284
        return $args;
285
    }
286
287
    /**
288
     * @param \ReflectionParameter $reflectionParameter
289
     *
290
     * @return Param
291
     */
292
    protected function generateParameterNode(\ReflectionParameter $reflectionParameter)
293
    {
294
        return new Param(
295
            $reflectionParameter->getName(),
296
            $this->getParameterDefault($reflectionParameter),
297
            $this->getParameterTypeHint($reflectionParameter),
298
            $reflectionParameter->isPassedByReference(),
299
            $this->isParameterVariadic($reflectionParameter)
300
        );
301
    }
302
303
    /**
304
     * @param \ReflectionParameter $reflectionParameter
305
     *
306
     * @return null|Expr
307
     */
308
    protected function getParameterDefault(\ReflectionParameter $reflectionParameter)
309
    {
310
        if ($reflectionParameter->isDefaultValueAvailable()) {
311
            $defaultValue = $reflectionParameter->getDefaultValue();
312
313
            return $this->prepareStatement($defaultValue);
314
        }
315
316
        return null;
317
    }
318
319
    /**
320
     * @param \ReflectionParameter $reflectionParameter
321
     *
322
     * @return null|string
323
     */
324
    protected function getParameterTypeHint(\ReflectionParameter $reflectionParameter)
325
    {
326
        if (null !== $class = $reflectionParameter->getClass()) {
327
            return '\\' . $class->getName();
328
        } elseif ($reflectionParameter->isArray()) {
329
            return 'array';
330
        }
331
332
        return null;
333
    }
334
335
    /**
336
     * @param \ReflectionParameter $reflectionParameter
337
     *
338
     * @return bool
339
     */
340
    protected function isParameterVariadic(\ReflectionParameter $reflectionParameter)
341
    {
342
        if (is_callable(array($reflectionParameter, 'isVariadic')) &&
343
            $reflectionParameter->isVariadic()) {
344
            return true;
345
        }
346
347
        return false;
348
    }
349
350
    /**
351
     * @param mixed $value
352
     *
353
     * @return Expr
354
     */
355
    protected function prepareStatement($value)
356
    {
357
        $method = 'prepareStatmentFor' . ucfirst(gettype($value));
358
        if (!is_callable(array($this, $method))) {
359
            throw new \InvalidArgumentException(sprintf('Can\'t prepare default statement for type: %s', gettype($value)));
360
        }
361
362
        return $this->$method($value);
363
    }
364
365
    /**
366
     * @param array $value
367
     *
368
     * @return Array_
369
     */
370
    protected function prepareStatmentForArray($value)
371
    {
372
        $items = array();
373
        foreach ($value as $subKey => $subValue) {
374
            $items[] = new ArrayItem($this->prepareStatement($subValue), $this->prepareStatement($subKey));
375
        }
376
377
        return new Array_($items);
378
    }
379
380
    /**
381
     * @return ConstFetch
382
     */
383
    protected function prepareStatmentForNull()
384
    {
385
        return new ConstFetch(new Name('null'));
386
    }
387
388
    /**
389
     * @param bool $value
390
     *
391
     * @return ConstFetch
392
     */
393
    protected function prepareStatmentForBool($value)
394
    {
395
        return new ConstFetch(new Name($value ? 'true' : 'false'));
396
    }
397
398
    /**
399
     * @param int $value
400
     *
401
     * @return LNumber
402
     */
403
    protected function prepareStatmentForInt($value)
404
    {
405
        return new LNumber($value);
406
    }
407
408
    /**
409
     * @param float $value
410
     *
411
     * @return DNumber
412
     */
413
    protected function prepareStatmentForFloat($value)
414
    {
415
        return new DNumber($value);
416
    }
417
418
    /**
419
     * @param string $value
420
     *
421
     * @return String_
422
     */
423
    protected function prepareStatmentForString($value)
424
    {
425
        return new String_($value);
426
    }
427
}
428