Completed
Push — master ( bfa80e...3adc88 )
by Alexander
02:11
created

ConstructorExecutionTransformer::getInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 9
c 0
b 0
f 0
ccs 0
cts 0
cp 0
rs 9.6666
cc 2
eloc 5
nc 2
nop 0
crap 6
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 void|bool Return false if transformation should be stopped
78
     */
79 13
    public function transform(StreamMetaData $metadata = null)
80
    {
81 13
        if (strpos($metadata->source, 'new ') === false) {
82
            return;
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
        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...
92 13
            list ($token, $tokenValue) = (array) $token + [1 => $token];
93 13
            if ($isWaitingClass && $token !== T_WHITESPACE && $token !== T_COMMENT) {
94 13
                $classNamePosition++;
95 13
                if ($token === T_VARIABLE && $classNamePosition === 1) {
96 2
                    $isWaitingEnd   = true;
97 2
                    $isWaitingClass = false;
98
                }
99 13
                if (in_array($tokenStream[$index + 1], ['(', ';', ')', '.'])) {
100 13
                    $isWaitingEnd   = true;
101 13
                    $isWaitingClass = false;
102
                }
103 13
                if ($isClassName && $token !== T_NS_SEPARATOR && $token !== T_STRING && $token !== T_STATIC) {
104 7
                    $isClassName = false;
105
                }
106
            }
107 13
            if ($token === T_NEW) {
108 13
                $tokenValue = '\\' . __CLASS__ . '::getInstance()->{';
109 13
                $isWaitingClass = true;
110 13
                $isClassName    = true;
111 13
                $classNamePosition = 0;
112
            }
113 13
            $transformedSource .= $tokenValue;
114
115 13
            if ($isWaitingEnd) {
116 13
                $transformedSource .= $isClassName ? '::class' : '';
117 13
                $transformedSource .= '}';
118 13
                $isWaitingEnd = false;
119
            }
120
        }
121 13
        $metadata->source = $transformedSource;
122 13
    }
123
124
    /**
125
     * Magic interceptor for instance creation
126
     *
127
     * @param string $className Name of the class to construct
128
     *
129
     * @return object Instance of required object
130
     */
131
    public function __get($className)
132
    {
133
        return static::construct($className);
134
    }
135
136
    /**
137
     * Magic interceptor for instance creation
138
     *
139
     * @param string $className Name of the class to construct
140
     * @param array $args Arguments for the constructor
141
     *
142
     * @return object Instance of required object
143
     */
144
    public function __call($className, array $args)
145
    {
146
        return static::construct($className, $args);
147
    }
148
149
    /**
150
     * Default implementation for accessing joinpoint or creating a new one on-fly
151
     *
152
     * @param string $fullClassName Name of the class to create
153
     * @param array $arguments Arguments for constructor
154
     *
155
     * @return object
156
     */
157
    protected static function construct($fullClassName, array $arguments = [])
158
    {
159
        $fullClassName = ltrim($fullClassName, '\\');
160
        if (!isset(self::$constructorInvocationsCache[$fullClassName])) {
161
            $invocation = null;
162
            $dynamicInit = AspectContainer::INIT_PREFIX . ':root';
163
            try {
164
                $joinPointsRef = new \ReflectionProperty($fullClassName, '__joinPoints');
165
                $joinPointsRef->setAccessible(true);
166
                $joinPoints = $joinPointsRef->getValue();
167
                if (isset($joinPoints[$dynamicInit])) {
168
                    $invocation = $joinPoints[$dynamicInit];
169
                }
170
            } catch (\ReflectionException $e) {
171
                $invocation = null;
172
            }
173
            if (!$invocation) {
174
                $invocation = new ReflectionConstructorInvocation($fullClassName, 'root', []);
175
            }
176
            self::$constructorInvocationsCache[$fullClassName] = $invocation;
177
        }
178
179
        return self::$constructorInvocationsCache[$fullClassName]->__invoke($arguments);
180
    }
181
}
182