Completed
Push — master ( eccf6a...345cbf )
by Alexander
03:07
created

ConstructorExecutionTransformer::transform()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5.0042

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
ccs 17
cts 18
cp 0.9444
rs 8.439
cc 5
eloc 18
nc 6
nop 1
crap 5.0042
1
<?php
2
declare(strict_types = 1);
3
/*
4
 * Go! AOP framework
5
 *
6
 * @copyright Copyright 2014, Lisachenko Alexander <[email protected]>
7
 *
8
 * This source file is subject to the license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Go\Instrument\Transformer;
13
14
use Go\Aop\Framework\ReflectionConstructorInvocation;
15
use Go\Core\AspectContainer;
16
use PhpParser\Node;
17
use PhpParser\NodeTraverser;
18
19
/**
20
 * Transforms the source code to add an ability to intercept new instances creation
21
 *
22
 * @see https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y
23
 *
24
 */
25
class ConstructorExecutionTransformer implements SourceTransformer
26
{
27
    /**
28
     * List of constructor invocations per class
29
     *
30
     * @var array|ReflectionConstructorInvocation[]
31
     */
32
    private static $constructorInvocationsCache = [];
33
34
    /**
35
     * Singletone
36
     *
37
     * @return static
38
     */
39
    public static function getInstance()
40
    {
41
        static $instance;
42
        if (!$instance) {
43
            $instance = new static;
44
        }
45
46
        return $instance;
47
    }
48
49
    /**
50
     * Rewrites all "new" expressions with our implementation
51
     *
52
     * @param StreamMetaData $metadata Metadata for source
53
     *
54
     * @return string See RESULT_XXX constants in the interface
55
     */
56 13
    public function transform(StreamMetaData $metadata): string
57
    {
58 13
        $newExpressionFinder = new NodeFinderVisitor([Node\Expr\New_::class]);
59
60
        // TODO: move this logic into walkSyntaxTree(Visitor $nodeVistor) method
61 13
        $traverser = new NodeTraverser();
62 13
        $traverser->addVisitor($newExpressionFinder);
63 13
        $traverser->traverse($metadata->syntaxTree);
64
65
        /** @var Node\Expr\New_[] $newExpressions */
66 13
        $newExpressions = $newExpressionFinder->getFoundNodes();
67
68 13
        if (empty($newExpressions)) {
69
            return self::RESULT_ABSTAIN;
70
        }
71
72 13
        foreach ($newExpressions as $newExpressionNode) {
73 13
            $startPosition = $newExpressionNode->getAttribute('startTokenPos');
74
75 13
            $metadata->tokenStream[$startPosition][1] = '\\' . __CLASS__ . '::getInstance()->{';
76 13
            if ($metadata->tokenStream[$startPosition+1][0] === T_WHITESPACE) {
77 13
                unset($metadata->tokenStream[$startPosition+1]);
78
            }
79 13
            $isExplicitClass  = $newExpressionNode->class instanceof Node\Name;
80 13
            $endClassNamePos  = $newExpressionNode->class->getAttribute('endTokenPos');
81 13
            $expressionSuffix = $isExplicitClass ? '::class}' : '}';
82 13
            $metadata->tokenStream[$endClassNamePos][1] .= $expressionSuffix;
83
        }
84
85 13
        return self::RESULT_TRANSFORMED;
86
    }
87
88
    /**
89
     * Magic interceptor for instance creation
90
     *
91
     * @param string $className Name of the class to construct
92
     *
93
     * @return object Instance of required object
94
     */
95
    public function __get($className)
96
    {
97
        return static::construct($className);
98
    }
99
100
    /**
101
     * Magic interceptor for instance creation
102
     *
103
     * @param string $className Name of the class to construct
104
     * @param array $args Arguments for the constructor
105
     *
106
     * @return object Instance of required object
107
     */
108
    public function __call($className, array $args)
109
    {
110
        return static::construct($className, $args);
111
    }
112
113
    /**
114
     * Default implementation for accessing joinpoint or creating a new one on-fly
115
     *
116
     * @param string $fullClassName Name of the class to create
117
     * @param array $arguments Arguments for constructor
118
     *
119
     * @return object
120
     */
121
    protected static function construct(string $fullClassName, array $arguments = [])
122
    {
123
        $fullClassName = ltrim($fullClassName, '\\');
124
        if (!isset(self::$constructorInvocationsCache[$fullClassName])) {
125
            $invocation = null;
126
            $dynamicInit = AspectContainer::INIT_PREFIX . ':root';
127
            try {
128
                $joinPointsRef = new \ReflectionProperty($fullClassName, '__joinPoints');
129
                $joinPointsRef->setAccessible(true);
130
                $joinPoints = $joinPointsRef->getValue();
131
                if (isset($joinPoints[$dynamicInit])) {
132
                    $invocation = $joinPoints[$dynamicInit];
133
                }
134
            } catch (\ReflectionException $e) {
135
                $invocation = null;
136
            }
137
            if (!$invocation) {
138
                $invocation = new ReflectionConstructorInvocation($fullClassName, 'root', []);
139
            }
140
            self::$constructorInvocationsCache[$fullClassName] = $invocation;
141
        }
142
143
        return self::$constructorInvocationsCache[$fullClassName]->__invoke($arguments);
144
    }
145
}
146