1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Go! AOP framework |
4
|
|
|
* |
5
|
|
|
* @copyright Copyright 2011, 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\Advisor; |
14
|
|
|
use Go\Aop\Features; |
15
|
|
|
use Go\Aop\Framework\AbstractJoinpoint; |
16
|
|
|
use Go\Core\AdviceMatcher; |
17
|
|
|
use Go\Core\AspectContainer; |
18
|
|
|
use Go\Core\AspectKernel; |
19
|
|
|
use Go\Core\AspectLoader; |
20
|
|
|
use Go\Instrument\ClassLoading\CachePathManager; |
21
|
|
|
use Go\Instrument\CleanableMemory; |
22
|
|
|
use Go\ParserReflection\ReflectionFile; |
23
|
|
|
use Go\ParserReflection\ReflectionFileNamespace; |
24
|
|
|
use Go\Proxy\ClassProxy; |
25
|
|
|
use Go\Proxy\FunctionProxy; |
26
|
|
|
use Go\Proxy\TraitProxy; |
27
|
|
|
use ReflectionClass; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Main transformer that performs weaving of aspects into the source code |
31
|
|
|
*/ |
32
|
|
|
class WeavingTransformer extends BaseSourceTransformer |
33
|
|
|
{ |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var AdviceMatcher |
37
|
|
|
*/ |
38
|
|
|
protected $adviceMatcher; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var CachePathManager |
42
|
|
|
*/ |
43
|
|
|
private $cachePathManager; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Instance of aspect loader |
47
|
|
|
* |
48
|
|
|
* @var AspectLoader |
49
|
|
|
*/ |
50
|
|
|
protected $aspectLoader; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Constructs a weaving transformer |
54
|
|
|
* |
55
|
|
|
* @param AspectKernel $kernel Instance of aspect kernel |
56
|
|
|
* @param AdviceMatcher $adviceMatcher Advice matcher for class |
57
|
|
|
* @param CachePathManager $cachePathManager Cache manager |
58
|
|
|
* @param AspectLoader $loader Loader for aspects |
59
|
|
|
*/ |
60
|
|
|
public function __construct( |
61
|
|
|
AspectKernel $kernel, |
62
|
|
|
AdviceMatcher $adviceMatcher, |
63
|
|
|
CachePathManager $cachePathManager, |
64
|
|
|
AspectLoader $loader |
65
|
|
|
) |
66
|
|
|
{ |
67
|
|
|
parent::__construct($kernel); |
68
|
|
|
$this->adviceMatcher = $adviceMatcher; |
69
|
|
|
$this->cachePathManager = $cachePathManager; |
70
|
|
|
$this->aspectLoader = $loader; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* This method may transform the supplied source and return a new replacement for it |
75
|
|
|
* |
76
|
|
|
* @param StreamMetaData $metadata Metadata for source |
77
|
|
|
* @return void|bool Return false if transformation should be stopped |
78
|
|
|
*/ |
79
|
|
|
public function transform(StreamMetaData $metadata) |
80
|
|
|
{ |
81
|
|
|
$totalTransformations = 0; |
82
|
|
|
|
83
|
|
|
$fileName = $metadata->uri; |
84
|
|
|
|
85
|
|
|
try { |
86
|
|
|
$parsedSource = new ReflectionFile($fileName); |
87
|
|
|
} catch (FileProcessingException $e) { |
|
|
|
|
88
|
|
|
|
89
|
|
|
return false; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
// Check if we have some new aspects that weren't loaded yet |
93
|
|
|
$unloadedAspects = $this->aspectLoader->getUnloadedAspects(); |
94
|
|
|
if ($unloadedAspects) { |
|
|
|
|
95
|
|
|
$this->loadAndRegisterAspects($unloadedAspects); |
96
|
|
|
} |
97
|
|
|
$advisors = $this->container->getByTag('advisor'); |
98
|
|
|
|
99
|
|
|
/** @var $namespaces ReflectionFileNamespace[] */ |
100
|
|
|
$namespaces = $parsedSource->getFileNamespaces(); |
101
|
|
|
$lineOffset = 0; |
102
|
|
|
|
103
|
|
|
foreach ($namespaces as $namespace) { |
104
|
|
|
|
105
|
|
|
/** @var $classes ReflectionClass[] */ |
106
|
|
|
$classes = $namespace->getClasses(); |
107
|
|
|
foreach ($classes as $class) { |
108
|
|
|
|
109
|
|
|
// Skip interfaces and aspects |
110
|
|
|
if ($class->isInterface() || in_array('Go\Aop\Aspect', $class->getInterfaceNames())) { |
111
|
|
|
continue; |
112
|
|
|
} |
113
|
|
|
$wasClassProcessed = $this->processSingleClass($advisors, $metadata, $class, $lineOffset); |
114
|
|
|
$totalTransformations += (integer) $wasClassProcessed; |
115
|
|
|
} |
116
|
|
|
$wasFunctionsProcessed = $this->processFunctions($advisors, $metadata, $namespace); |
117
|
|
|
$totalTransformations += (integer) $wasFunctionsProcessed; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
// If we return false this will indicate no more transformation for following transformers |
121
|
|
|
return $totalTransformations > 0; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Performs weaving of single class if needed |
126
|
|
|
* |
127
|
|
|
* @param array|Advisor[] $advisors |
128
|
|
|
* @param StreamMetaData $metadata Source stream information |
129
|
|
|
* @param ReflectionClass $class Instance of class to analyze |
130
|
|
|
* @param integer $lineOffset Current offset, will be updated to store the last position |
131
|
|
|
* |
132
|
|
|
* @return bool True if was class processed, false otherwise |
133
|
|
|
*/ |
134
|
|
|
private function processSingleClass(array $advisors, StreamMetaData $metadata, ReflectionClass $class, &$lineOffset) |
|
|
|
|
135
|
|
|
{ |
136
|
|
|
$advices = $this->adviceMatcher->getAdvicesForClass($class, $advisors); |
137
|
|
|
|
138
|
|
|
if (!$advices) { |
|
|
|
|
139
|
|
|
// Fast return if there aren't any advices for that class |
140
|
|
|
return false; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
// Sort advices in advance to keep the correct order in cache |
144
|
|
|
foreach ($advices as &$typeAdvices) { |
145
|
|
|
foreach ($typeAdvices as &$joinpointAdvices) { |
146
|
|
|
if (is_array($joinpointAdvices)) { |
147
|
|
|
$joinpointAdvices = AbstractJoinpoint::sortAdvices($joinpointAdvices); |
148
|
|
|
} |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
// Prepare new parent name |
153
|
|
|
$newParentName = $class->getShortName() . AspectContainer::AOP_PROXIED_SUFFIX; |
154
|
|
|
|
155
|
|
|
// Replace original class name with new |
156
|
|
|
$metadata->source = $this->adjustOriginalClass($class, $metadata->source, $newParentName); |
157
|
|
|
|
158
|
|
|
// Prepare child Aop proxy |
159
|
|
|
$useStatic = $this->kernel->hasFeature(Features::USE_STATIC_FOR_LSB); |
160
|
|
|
$child = ($this->kernel->hasFeature(Features::USE_TRAIT) && $class->isTrait()) |
161
|
|
|
? new TraitProxy($class, $advices, $useStatic) |
162
|
|
|
: new ClassProxy($class, $advices, $useStatic); |
163
|
|
|
|
164
|
|
|
// Set new parent name instead of original |
165
|
|
|
$child->setParentName($newParentName); |
166
|
|
|
$contentToInclude = $this->saveProxyToCache($class, $child); |
167
|
|
|
|
168
|
|
|
$metadata->source .= $contentToInclude . PHP_EOL; |
169
|
|
|
|
170
|
|
|
/* // Add child to source |
171
|
|
|
$tokenCount = $class->getBroker()->getFileTokens($class->getFileName())->count(); |
172
|
|
|
if ($tokenCount - $class->getEndPosition() < 3) { |
173
|
|
|
// If it's the last class in a file, just add child source |
174
|
|
|
$metadata->source .= $contentToInclude . PHP_EOL; |
175
|
|
|
} else { |
176
|
|
|
$lastLine = $class->getEndLine() + $lineOffset; // returns the last line of class |
177
|
|
|
$dataArray = explode("\n", $metadata->source); |
178
|
|
|
|
179
|
|
|
$currentClassArray = array_splice($dataArray, 0, $lastLine); |
180
|
|
|
$childClassArray = explode("\n", $contentToInclude); |
181
|
|
|
$lineOffset += count($childClassArray) + 2; // returns LoC for child class + 2 blank lines |
182
|
|
|
|
183
|
|
|
$dataArray = array_merge($currentClassArray, array(''), $childClassArray, array(''), $dataArray); |
184
|
|
|
|
185
|
|
|
$metadata->source = implode("\n", $dataArray); |
186
|
|
|
}*/ |
187
|
|
|
|
188
|
|
|
return true; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Adjust definition of original class source to enable extending |
193
|
|
|
* |
194
|
|
|
* @param ReflectionClass $class Instance of class reflection |
195
|
|
|
* @param string $source Source code |
196
|
|
|
* @param string $newParentName New name for the parent class |
197
|
|
|
* |
198
|
|
|
* @return string Replaced code for class |
199
|
|
|
*/ |
200
|
|
|
private function adjustOriginalClass($class, $source, $newParentName) |
201
|
|
|
{ |
202
|
|
|
$type = ($this->kernel->hasFeature(Features::USE_TRAIT) && $class->isTrait()) ? 'trait' : 'class'; |
203
|
|
|
$source = preg_replace( |
204
|
|
|
"/{$type}\s+(" . $class->getShortName() . ')(\b)/iS', |
205
|
|
|
"{$type} {$newParentName}$2", |
206
|
|
|
$source |
207
|
|
|
); |
208
|
|
|
if ($class->isFinal()) { |
209
|
|
|
// Remove final from class, child will be final instead |
210
|
|
|
$source = str_replace("final {$type}", $type, $source); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
return $source; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Performs weaving of functions in the current namespace |
218
|
|
|
* |
219
|
|
|
* @param array|Advisor[] $advisors List of advisors |
220
|
|
|
* @param StreamMetaData $metadata Source stream information |
221
|
|
|
* @param ReflectionFileNamespace $namespace Current namespace for file |
222
|
|
|
* |
223
|
|
|
* @return boolean True if functions were processed, false otherwise |
224
|
|
|
*/ |
225
|
|
|
private function processFunctions(array $advisors, StreamMetaData $metadata, $namespace) |
226
|
|
|
{ |
227
|
|
|
static $cacheDirSuffix = '/_functions/'; |
228
|
|
|
|
229
|
|
|
$wasProcessedFunctions = false; |
230
|
|
|
$functionAdvices = $this->adviceMatcher->getAdvicesForFunctions($namespace, $advisors); |
231
|
|
|
$cacheDir = $this->cachePathManager->getCacheDir(); |
232
|
|
|
if ($functionAdvices && $cacheDir) { |
|
|
|
|
233
|
|
|
$cacheDir = $cacheDir . $cacheDirSuffix; |
234
|
|
|
$fileName = str_replace('\\', '/', $namespace->getName()) . '.php'; |
235
|
|
|
|
236
|
|
|
$functionFileName = $cacheDir . $fileName; |
237
|
|
|
if (!file_exists($functionFileName) || !$this->container->isFresh(filemtime($functionFileName))) { |
238
|
|
|
$dirname = dirname($functionFileName); |
239
|
|
|
if (!file_exists($dirname)) { |
240
|
|
|
mkdir($dirname, 0770, true); |
241
|
|
|
} |
242
|
|
|
$source = new FunctionProxy($namespace, $functionAdvices); |
243
|
|
|
file_put_contents($functionFileName, $source); |
244
|
|
|
} |
245
|
|
|
$content = 'include_once AOP_CACHE_DIR . ' . var_export($cacheDirSuffix . $fileName, true) . ';' . PHP_EOL; |
246
|
|
|
$metadata->source .= $content; |
247
|
|
|
$wasProcessedFunctions = true; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
return $wasProcessedFunctions; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Save AOP proxy to the separate file anr returns the php source code for inclusion |
255
|
|
|
* |
256
|
|
|
* @param ReflectionClass $class Original class reflection |
257
|
|
|
* @param string|ClassProxy $child |
258
|
|
|
* |
259
|
|
|
* @return string |
260
|
|
|
*/ |
261
|
|
|
private function saveProxyToCache($class, $child) |
262
|
|
|
{ |
263
|
|
|
static $cacheDirSuffix = '/_proxies/'; |
264
|
|
|
|
265
|
|
|
$cacheDir = $this->cachePathManager->getCacheDir(); |
266
|
|
|
|
267
|
|
|
// Without cache we should rewrite original file |
268
|
|
|
if (!$cacheDir) { |
|
|
|
|
269
|
|
|
return $child; |
270
|
|
|
} |
271
|
|
|
$cacheDir = $cacheDir . $cacheDirSuffix; |
272
|
|
|
$fileName = str_replace($this->options['appDir'] . DIRECTORY_SEPARATOR, '', $class->getFileName()); |
273
|
|
|
|
274
|
|
|
$proxyFileName = $cacheDir . $fileName; |
275
|
|
|
$dirname = dirname($proxyFileName); |
276
|
|
|
if (!file_exists($dirname)) { |
277
|
|
|
mkdir($dirname, 0770, true); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
$body = '<?php' . PHP_EOL; |
281
|
|
|
$namespace = $class->getNamespaceName(); |
282
|
|
|
if ($namespace) { |
283
|
|
|
$body .= "namespace {$namespace};" . PHP_EOL . PHP_EOL; |
284
|
|
|
} |
285
|
|
|
// foreach ($class->getNamespaceAliases() as $alias => $fqdn) { |
|
|
|
|
286
|
|
|
// $body .= "use {$fqdn} as {$alias};" . PHP_EOL; |
287
|
|
|
// } |
288
|
|
|
$body .= $child; |
289
|
|
|
file_put_contents($proxyFileName, $body); |
290
|
|
|
|
291
|
|
|
return 'include_once AOP_CACHE_DIR . ' . var_export($cacheDirSuffix . $fileName, true) . ';' . PHP_EOL; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Utility method to load and register unloaded aspects |
296
|
|
|
* |
297
|
|
|
* @param array $unloadedAspects List of unloaded aspects |
298
|
|
|
*/ |
299
|
|
|
private function loadAndRegisterAspects(array $unloadedAspects) |
300
|
|
|
{ |
301
|
|
|
foreach ($unloadedAspects as $unloadedAspect) { |
302
|
|
|
$this->aspectLoader->loadAndRegister($unloadedAspect); |
303
|
|
|
} |
304
|
|
|
} |
305
|
|
|
} |
306
|
|
|
|
Scrutinizer analyzes your
composer.json
/composer.lock
file if available to determine the classes, and functions that are defined by your dependencies.It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.