Completed
Push — master ( 090f96...5ad5e6 )
by Alexander
02:26
created

ConstructorExecutionTransformer   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 139
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 97.14%

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 2
dl 0
loc 139
ccs 34
cts 35
cp 0.9714
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getInstance() 0 9 2
C transform() 0 49 16
A __get() 0 4 1
A __call() 0 4 1
B construct() 0 24 5
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
17
/**
18
 * Transforms the source code to add an ability to intercept new instances creation
19
 *
20
 * @see https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y
21
 *
22
 * new_expr:
23
 *   T_NEW
24
 *   class_name_reference
25
 *   ctor_arguments
26
 *
27
 * class_name_reference:
28
 *   class_name
29
 *   | new_variable
30
 *
31
 * class_name:
32
 *   T_STATIC
33
 *   | name
34
 *
35
 * namespace_name:
36
 *   T_STRING
37
 *   | namespace_name T_NS_SEPARATOR T_STRING
38
 *
39
 * name:
40
 *   namespace_name
41
 *   | T_NAMESPACE T_NS_SEPARATOR namespace_name
42
 *   | T_NS_SEPARATOR namespace_name
43
 *
44
 * ctor_arguments:
45
 *   / empty /
46
 *   | argument_list
47
 */
48
class ConstructorExecutionTransformer implements SourceTransformer
49
{
50
    /**
51
     * List of constructor invocations per class
52
     *
53
     * @var array|ReflectionConstructorInvocation[]
54
     */
55
    private static $constructorInvocationsCache = [];
56
57
    /**
58
     * Singletone
59
     *
60
     * @return static
61
     */
62
    public static function getInstance()
63
    {
64
        static $instance;
65
        if (!$instance) {
66
            $instance = new static;
67
        }
68
69
        return $instance;
70
    }
71
72
    /**
73
     * Rewrites all "new" expressions with our implementation
74
     *
75
     * @param StreamMetaData $metadata Metadata for source
76
     *
77
     * @return string See RESULT_XXX constants in the interface
78
     */
79 13
    public function transform(StreamMetaData $metadata): string
80
    {
81 13
        if (strpos($metadata->source, 'new ') === false) {
82
            return self::RESULT_ABSTAIN;
83
        }
84
85 13
        $tokenStream       = token_get_all($metadata->source);
86 13
        $transformedSource = '';
87 13
        $isWaitingClass    = false;
88 13
        $isWaitingEnd      = false;
89 13
        $isClassName       = true;
90 13
        $classNamePosition = 0;
91 13
        $weavingResult     = self::RESULT_ABSTAIN;
92 13
        foreach ($tokenStream as $index=>$token) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "=>"; 0 found
Loading history...
Coding Style introduced by
Expected 1 space after "=>"; 0 found
Loading history...
93 13
            list ($token, $tokenValue) = (array) $token + [1 => $token];
94 13
            if ($isWaitingClass && $token !== T_WHITESPACE && $token !== T_COMMENT) {
95 13
                $classNamePosition++;
96 13
                if ($token === T_VARIABLE && $classNamePosition === 1) {
97 2
                    $isWaitingEnd   = true;
98 2
                    $isWaitingClass = false;
99
                }
100 13
                if (in_array($tokenStream[$index + 1], ['(', ';', ')', '.'])) {
101 13
                    $isWaitingEnd   = true;
102 13
                    $isWaitingClass = false;
103
                }
104 13
                if ($isClassName && $token !== T_NS_SEPARATOR && $token !== T_STRING && $token !== T_STATIC) {
105 7
                    $isClassName = false;
106
                }
107
            }
108 13
            if ($token === T_NEW) {
109 13
                $tokenValue = '\\' . __CLASS__ . '::getInstance()->{';
110 13
                $isWaitingClass = true;
111 13
                $isClassName    = true;
112
113 13
                $classNamePosition = 0;
114 13
                $weavingResult     = self::RESULT_TRANSFORMED;
115
            }
116 13
            $transformedSource .= $tokenValue;
117
118 13
            if ($isWaitingEnd) {
119 13
                $transformedSource .= $isClassName ? '::class' : '';
120 13
                $transformedSource .= '}';
121 13
                $isWaitingEnd = false;
122
            }
123
        }
124 13
        $metadata->source = $transformedSource;
125
126 13
        return $weavingResult;
127
    }
128
129
    /**
130
     * Magic interceptor for instance creation
131
     *
132
     * @param string $className Name of the class to construct
133
     *
134
     * @return object Instance of required object
135
     */
136
    public function __get($className)
137
    {
138
        return static::construct($className);
139
    }
140
141
    /**
142
     * Magic interceptor for instance creation
143
     *
144
     * @param string $className Name of the class to construct
145
     * @param array $args Arguments for the constructor
146
     *
147
     * @return object Instance of required object
148
     */
149
    public function __call($className, array $args)
150
    {
151
        return static::construct($className, $args);
152
    }
153
154
    /**
155
     * Default implementation for accessing joinpoint or creating a new one on-fly
156
     *
157
     * @param string $fullClassName Name of the class to create
158
     * @param array $arguments Arguments for constructor
159
     *
160
     * @return object
161
     */
162
    protected static function construct($fullClassName, array $arguments = [])
163
    {
164
        $fullClassName = ltrim($fullClassName, '\\');
165
        if (!isset(self::$constructorInvocationsCache[$fullClassName])) {
166
            $invocation = null;
167
            $dynamicInit = AspectContainer::INIT_PREFIX . ':root';
168
            try {
169
                $joinPointsRef = new \ReflectionProperty($fullClassName, '__joinPoints');
170
                $joinPointsRef->setAccessible(true);
171
                $joinPoints = $joinPointsRef->getValue();
172
                if (isset($joinPoints[$dynamicInit])) {
173
                    $invocation = $joinPoints[$dynamicInit];
174
                }
175
            } catch (\ReflectionException $e) {
176
                $invocation = null;
177
            }
178
            if (!$invocation) {
179
                $invocation = new ReflectionConstructorInvocation($fullClassName, 'root', []);
180
            }
181
            self::$constructorInvocationsCache[$fullClassName] = $invocation;
182
        }
183
184
        return self::$constructorInvocationsCache[$fullClassName]->__invoke($arguments);
185
    }
186
}
187