Completed
Push — 1.x ( c183a4...05b51a )
by Alexander
8s
created

ConstructorExecutionTransformer::transform()   C

Complexity

Conditions 16
Paths 56

Size

Total Lines 44
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 16.0071

Importance

Changes 3
Bugs 1 Features 0
Metric Value
dl 0
loc 44
ccs 32
cts 33
cp 0.9697
rs 5.0151
c 3
b 1
f 0
cc 16
eloc 32
nc 56
nop 1
crap 16.0071

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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