Completed
Push — 2.x ( 98b57b...8b2aa1 )
by Alexander
05:24
created

ConstructorExecutionTransformer   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 121
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 39.53%

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 5
dl 0
loc 121
ccs 17
cts 43
cp 0.3953
rs 10
c 0
b 0
f 0

5 Methods

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