|
1
|
|
|
<?php |
|
2
|
|
|
/* |
|
3
|
|
|
* Go! AOP framework |
|
4
|
|
|
* |
|
5
|
|
|
* @copyright Copyright 2012, 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\Proxy; |
|
12
|
|
|
|
|
13
|
|
|
use Go\Aop\Advice; |
|
14
|
|
|
use Go\Aop\Features; |
|
15
|
|
|
use Go\Aop\Framework\ClassFieldAccess; |
|
16
|
|
|
use Go\Aop\Framework\MethodInvocationComposer; |
|
17
|
|
|
use Go\Aop\Framework\ReflectionConstructorInvocation; |
|
18
|
|
|
use Go\Aop\Framework\StaticInitializationJoinpoint; |
|
19
|
|
|
use Go\Aop\Intercept\Joinpoint; |
|
20
|
|
|
use Go\Aop\IntroductionInfo; |
|
21
|
|
|
use Go\Core\AspectContainer; |
|
22
|
|
|
use Go\Core\AspectKernel; |
|
23
|
|
|
use Go\Core\LazyAdvisorAccessor; |
|
24
|
|
|
use Reflection; |
|
25
|
|
|
use ReflectionClass; |
|
26
|
|
|
use ReflectionMethod as Method; |
|
27
|
|
|
use ReflectionProperty as Property; |
|
28
|
|
|
use TokenReflection\ReflectionClass as ParsedClass; |
|
29
|
|
|
use TokenReflection\ReflectionMethod as ParsedMethod; |
|
30
|
|
|
use TokenReflection\ReflectionProperty as ParsedProperty; |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* Class proxy builder that is used to generate a child class from the list of joinpoints |
|
34
|
|
|
*/ |
|
35
|
|
|
class ClassProxy extends AbstractProxy |
|
36
|
|
|
{ |
|
37
|
|
|
/** |
|
38
|
|
|
* Parent class reflection |
|
39
|
|
|
* |
|
40
|
|
|
* @var null|ParsedClass |
|
41
|
|
|
*/ |
|
42
|
|
|
protected $class = null; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* Parent class name, can be changed manually |
|
46
|
|
|
* |
|
47
|
|
|
* @var string |
|
48
|
|
|
*/ |
|
49
|
|
|
protected $parentClassName = null; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* Source code for methods |
|
53
|
|
|
* |
|
54
|
|
|
* @var array Name of method => source code for it |
|
55
|
|
|
*/ |
|
56
|
|
|
protected $methodsCode = []; |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* Static mappings for class name for excluding if..else check |
|
60
|
|
|
* |
|
61
|
|
|
* @var null|array |
|
62
|
|
|
*/ |
|
63
|
|
|
protected static $invocationClassMap = null; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* List of additional interfaces to implement |
|
67
|
|
|
* |
|
68
|
|
|
* @var array |
|
69
|
|
|
*/ |
|
70
|
|
|
protected $interfaces = []; |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* List of additional traits for using |
|
74
|
|
|
* |
|
75
|
|
|
* @var array |
|
76
|
|
|
*/ |
|
77
|
|
|
protected $traits = []; |
|
78
|
|
|
|
|
79
|
|
|
/** |
|
80
|
|
|
* Source code for properties |
|
81
|
|
|
* |
|
82
|
|
|
* @var array Name of property => source code for it |
|
83
|
|
|
*/ |
|
84
|
|
|
protected $propertiesCode = []; |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Name for the current class |
|
88
|
|
|
* |
|
89
|
|
|
* @var string |
|
90
|
|
|
*/ |
|
91
|
|
|
protected $name = ''; |
|
92
|
|
|
|
|
93
|
|
|
/** |
|
94
|
|
|
* Flag to determine if we need to add a code for property interceptors |
|
95
|
|
|
* |
|
96
|
|
|
* @var bool |
|
97
|
|
|
*/ |
|
98
|
|
|
private $isFieldsIntercepted = false; |
|
99
|
|
|
|
|
100
|
|
|
/** |
|
101
|
|
|
* List of intercepted properties names |
|
102
|
|
|
* |
|
103
|
|
|
* @var array |
|
104
|
|
|
*/ |
|
105
|
|
|
private $interceptedProperties = []; |
|
106
|
|
|
|
|
107
|
|
|
/** |
|
108
|
|
|
* Generates an child code by parent class reflection and joinpoints for it |
|
109
|
|
|
* |
|
110
|
|
|
* @param ParsedClass $parent Parent class reflection |
|
111
|
|
|
* @param array|Advice[] $classAdvices List of advices for class |
|
112
|
|
|
* |
|
113
|
|
|
* @throws \InvalidArgumentException if there are unknown type of advices |
|
114
|
|
|
*/ |
|
115
|
5 |
|
public function __construct(ParsedClass $parent, array $classAdvices) |
|
116
|
|
|
{ |
|
117
|
5 |
|
parent::__construct($classAdvices); |
|
118
|
|
|
|
|
119
|
5 |
|
$this->class = $parent; |
|
120
|
5 |
|
$this->name = $parent->getShortName(); |
|
121
|
5 |
|
$this->parentClassName = $parent->getShortName(); |
|
122
|
|
|
|
|
123
|
5 |
|
$this->addInterface('\Go\Aop\Proxy'); |
|
124
|
5 |
|
$this->addJoinpointsProperty(); |
|
125
|
|
|
|
|
126
|
5 |
|
foreach ($classAdvices as $type => $typedAdvices) { |
|
|
|
|
|
|
127
|
|
|
|
|
128
|
|
|
switch ($type) { |
|
129
|
5 |
|
case AspectContainer::METHOD_PREFIX: |
|
130
|
|
|
case AspectContainer::STATIC_METHOD_PREFIX: |
|
131
|
5 |
|
foreach ($typedAdvices as $joinPointName => $advice) { |
|
132
|
5 |
|
$method = $parent->getMethod($joinPointName); |
|
133
|
5 |
|
if (!$method instanceof ParsedMethod) { |
|
|
|
|
|
|
134
|
|
|
continue; |
|
135
|
|
|
} |
|
136
|
5 |
|
$this->overrideMethod($method); |
|
137
|
|
|
} |
|
138
|
5 |
|
break; |
|
139
|
|
|
|
|
140
|
|
|
case AspectContainer::PROPERTY_PREFIX: |
|
141
|
|
|
foreach ($typedAdvices as $joinPointName => $advice) { |
|
142
|
|
|
$property = $parent->getProperty($joinPointName); |
|
143
|
|
|
if (!$property instanceof ParsedProperty) { |
|
|
|
|
|
|
144
|
|
|
continue; |
|
145
|
|
|
} |
|
146
|
|
|
$this->interceptProperty($property); |
|
147
|
|
|
} |
|
148
|
|
|
break; |
|
149
|
|
|
|
|
150
|
|
|
case AspectContainer::INTRODUCTION_TRAIT_PREFIX: |
|
151
|
|
|
foreach ($typedAdvices as $advice) { |
|
152
|
|
|
/** @var $advice IntroductionInfo */ |
|
153
|
|
|
foreach ($advice->getInterfaces() as $interface) { |
|
154
|
|
|
$this->addInterface($interface); |
|
155
|
|
|
} |
|
156
|
|
|
foreach ($advice->getTraits() as $trait) { |
|
157
|
|
|
$this->addTrait($trait); |
|
158
|
|
|
} |
|
159
|
|
|
} |
|
160
|
|
|
break; |
|
161
|
|
|
|
|
162
|
|
|
case AspectContainer::INIT_PREFIX: |
|
163
|
|
|
case AspectContainer::STATIC_INIT_PREFIX: |
|
164
|
|
|
break; // No changes for class |
|
165
|
|
|
|
|
166
|
|
|
default: |
|
167
|
5 |
|
throw new \InvalidArgumentException("Unsupported point `$type`"); |
|
168
|
|
|
} |
|
169
|
|
|
} |
|
170
|
5 |
|
} |
|
171
|
|
|
|
|
172
|
|
|
|
|
173
|
|
|
/** |
|
174
|
|
|
* Updates parent name for child |
|
175
|
|
|
* |
|
176
|
|
|
* @param string $newParentName New class name |
|
177
|
|
|
* |
|
178
|
|
|
* @return static |
|
179
|
|
|
*/ |
|
180
|
5 |
|
public function setParentName($newParentName) |
|
181
|
|
|
{ |
|
182
|
5 |
|
$this->parentClassName = $newParentName; |
|
183
|
|
|
|
|
184
|
5 |
|
return $this; |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
/** |
|
188
|
|
|
* Override parent method with new body |
|
189
|
|
|
* |
|
190
|
|
|
* @param string $methodName Method name to override |
|
191
|
|
|
* @param string $body New body for method |
|
192
|
|
|
* |
|
193
|
|
|
* @return static |
|
194
|
|
|
*/ |
|
195
|
5 |
|
public function override($methodName, $body) |
|
196
|
|
|
{ |
|
197
|
5 |
|
$this->methodsCode[$methodName] = $this->getOverriddenMethod($this->class->getMethod($methodName), $body); |
|
198
|
|
|
|
|
199
|
5 |
|
return $this; |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
/** |
|
203
|
|
|
* Creates a method |
|
204
|
|
|
* |
|
205
|
|
|
* @param int $methodFlags See ReflectionMethod modifiers |
|
206
|
|
|
* @param string $methodName Name of the method |
|
207
|
|
|
* @param bool $byReference Is method should return value by reference |
|
208
|
|
|
* @param string $body Body of method |
|
209
|
|
|
* @param string $parameters Definition of parameters |
|
210
|
|
|
* |
|
211
|
|
|
* @return static |
|
212
|
|
|
*/ |
|
213
|
|
|
public function setMethod($methodFlags, $methodName, $byReference, $body, $parameters) |
|
214
|
|
|
{ |
|
215
|
|
|
$this->methodsCode[$methodName] = ( |
|
216
|
|
|
"/**\n * Method was created automatically, do not change it manually\n */\n" . |
|
217
|
|
|
join(' ', Reflection::getModifierNames($methodFlags)) . // List of method modifiers |
|
218
|
|
|
' function ' . // 'function' keyword |
|
219
|
|
|
($byReference ? '&' : '') . // Return value by reference |
|
220
|
|
|
$methodName . // Method name |
|
221
|
|
|
'(' . // Start of parameter list |
|
222
|
|
|
$parameters . // List of parameters |
|
223
|
|
|
")\n" . // End of parameter list |
|
224
|
|
|
"{\n" . // Start of method body |
|
225
|
|
|
$this->indent($body) . "\n" . // Method body |
|
226
|
|
|
"}\n" // End of method body |
|
227
|
|
|
); |
|
228
|
|
|
|
|
229
|
|
|
return $this; |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
/** |
|
233
|
|
|
* Inject advices into given class |
|
234
|
|
|
* |
|
235
|
|
|
* NB This method will be used as a callback during source code evaluation to inject joinpoints |
|
236
|
|
|
* |
|
237
|
|
|
* @param string $className Aop child proxy class |
|
238
|
|
|
* @param array|Advice[] $advices List of advices to inject into class |
|
239
|
|
|
* |
|
240
|
|
|
* @return void |
|
241
|
|
|
*/ |
|
242
|
|
|
public static function injectJoinPoints($className, array $advices = []) |
|
243
|
|
|
{ |
|
244
|
|
|
$reflectionClass = new ReflectionClass($className); |
|
245
|
|
|
$joinPoints = static::wrapWithJoinPoints($advices, $reflectionClass->getParentClass()->name); |
|
246
|
|
|
|
|
247
|
|
|
$prop = $reflectionClass->getProperty('__joinPoints'); |
|
248
|
|
|
$prop->setAccessible(true); |
|
249
|
|
|
$prop->setValue($joinPoints); |
|
250
|
|
|
|
|
251
|
|
|
$staticInit = AspectContainer::STATIC_INIT_PREFIX . ':root'; |
|
252
|
|
|
if (isset($joinPoints[$staticInit])) { |
|
253
|
|
|
$joinPoints[$staticInit]->__invoke(); |
|
254
|
|
|
} |
|
255
|
|
|
} |
|
256
|
|
|
|
|
257
|
|
|
/** |
|
258
|
|
|
* Initialize static mappings to reduce the time for checking features |
|
259
|
|
|
* |
|
260
|
|
|
* @param bool $useSplatOperator Enables usage of optimized invocation with splat operator |
|
261
|
|
|
*/ |
|
262
|
|
|
protected static function setMappings($useSplatOperator) |
|
263
|
|
|
{ |
|
264
|
|
|
$dynamicMethodClass = MethodInvocationComposer::compose(false, $useSplatOperator, false); |
|
265
|
|
|
$staticMethodClass = MethodInvocationComposer::compose(true, $useSplatOperator, false); |
|
266
|
|
|
|
|
267
|
|
|
// We are using LSB here and overridden static property |
|
268
|
|
|
static::$invocationClassMap = array( |
|
269
|
|
|
AspectContainer::METHOD_PREFIX => $dynamicMethodClass, |
|
270
|
|
|
AspectContainer::STATIC_METHOD_PREFIX => $staticMethodClass, |
|
271
|
|
|
AspectContainer::PROPERTY_PREFIX => ClassFieldAccess::class, |
|
272
|
|
|
AspectContainer::STATIC_INIT_PREFIX => StaticInitializationJoinpoint::class, |
|
273
|
|
|
AspectContainer::INIT_PREFIX => ReflectionConstructorInvocation::class |
|
274
|
|
|
); |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
|
|
/** |
|
278
|
|
|
* Wrap advices with joinpoint object |
|
279
|
|
|
* |
|
280
|
|
|
* @param array|Advice[] $classAdvices Advices for specific class |
|
281
|
|
|
* @param string $className Name of the original class to use |
|
282
|
|
|
* |
|
283
|
|
|
* @throws \UnexpectedValueException If joinPoint type is unknown |
|
284
|
|
|
* |
|
285
|
|
|
* NB: Extension should be responsible for wrapping advice with join point. |
|
286
|
|
|
* |
|
287
|
|
|
* @return array|Joinpoint[] returns list of joinpoint ready to use |
|
288
|
|
|
*/ |
|
289
|
|
|
protected static function wrapWithJoinPoints($classAdvices, $className) |
|
290
|
|
|
{ |
|
291
|
|
|
/** @var LazyAdvisorAccessor $accessor */ |
|
292
|
|
|
static $accessor = null; |
|
293
|
|
|
|
|
294
|
|
|
if (!self::$invocationClassMap) { |
|
295
|
|
|
$aspectKernel = AspectKernel::getInstance(); |
|
296
|
|
|
$accessor = $aspectKernel->getContainer()->get('aspect.advisor.accessor'); |
|
297
|
|
|
self::setMappings( |
|
298
|
|
|
$aspectKernel->hasFeature(Features::USE_SPLAT_OPERATOR) |
|
299
|
|
|
); |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
$joinPoints = []; |
|
303
|
|
|
|
|
304
|
|
|
foreach ($classAdvices as $joinPointType => $typedAdvices) { |
|
305
|
|
|
// if not isset then we don't want to create such invocation for class |
|
306
|
|
|
if (!isset(self::$invocationClassMap[$joinPointType])) { |
|
307
|
|
|
continue; |
|
308
|
|
|
} |
|
309
|
|
|
foreach ($typedAdvices as $joinPointName => $advices) { |
|
310
|
|
|
$filledAdvices = []; |
|
311
|
|
|
foreach ($advices as $advisorName) { |
|
312
|
|
|
$filledAdvices[] = $accessor->$advisorName; |
|
313
|
|
|
} |
|
314
|
|
|
|
|
315
|
|
|
$joinpoint = new self::$invocationClassMap[$joinPointType]($className, $joinPointName, $filledAdvices); |
|
316
|
|
|
$joinPoints["$joinPointType:$joinPointName"] = $joinpoint; |
|
317
|
|
|
} |
|
318
|
|
|
} |
|
319
|
|
|
|
|
320
|
|
|
return $joinPoints; |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
|
/** |
|
324
|
|
|
* Add an interface for child |
|
325
|
|
|
* |
|
326
|
|
|
* @param string|ReflectionClass|ParsedClass $interface |
|
327
|
|
|
* |
|
328
|
|
|
* @throws \InvalidArgumentException If object is not an interface |
|
329
|
|
|
*/ |
|
330
|
5 |
|
public function addInterface($interface) |
|
331
|
|
|
{ |
|
332
|
5 |
|
$interfaceName = $interface; |
|
333
|
5 |
|
if ($interface instanceof ReflectionClass || $interface instanceof ParsedClass) { |
|
|
|
|
|
|
334
|
|
|
if (!$interface->isInterface()) { |
|
335
|
|
|
throw new \InvalidArgumentException("Interface expected to add"); |
|
336
|
|
|
} |
|
337
|
|
|
$interfaceName = $interface->name; |
|
338
|
|
|
} |
|
339
|
|
|
// Use absolute namespace to prevent NS-conflicts |
|
340
|
5 |
|
$this->interfaces[] = '\\' . ltrim($interfaceName, '\\'); |
|
341
|
5 |
|
} |
|
342
|
|
|
|
|
343
|
|
|
/** |
|
344
|
|
|
* Add a trait for child |
|
345
|
|
|
* |
|
346
|
|
|
* @param string|ReflectionClass|ParsedClass $trait |
|
347
|
|
|
* |
|
348
|
|
|
* @throws \InvalidArgumentException If object is not a trait |
|
349
|
|
|
*/ |
|
350
|
|
|
public function addTrait($trait) |
|
351
|
|
|
{ |
|
352
|
|
|
$traitName = $trait; |
|
353
|
|
|
if ($trait instanceof ReflectionClass || $trait instanceof ParsedClass) { |
|
|
|
|
|
|
354
|
|
|
if (!$trait->isTrait()) { |
|
355
|
|
|
throw new \InvalidArgumentException("Trait expected to add"); |
|
356
|
|
|
} |
|
357
|
|
|
$traitName = $trait->name; |
|
358
|
|
|
} |
|
359
|
|
|
// Use absolute namespace to prevent NS-conflicts |
|
360
|
|
|
$this->traits[] = '\\' . ltrim($traitName, '\\'); |
|
361
|
|
|
} |
|
362
|
|
|
|
|
363
|
|
|
/** |
|
364
|
|
|
* Creates a property |
|
365
|
|
|
* |
|
366
|
|
|
* @param int $propFlags See ReflectionProperty modifiers |
|
367
|
|
|
* @param string $propName Name of the property |
|
368
|
|
|
* @param null|string $defaultText Default value, should be string text! |
|
369
|
|
|
* |
|
370
|
|
|
* @return static |
|
371
|
|
|
*/ |
|
372
|
5 |
|
public function setProperty($propFlags, $propName, $defaultText = null) |
|
373
|
|
|
{ |
|
374
|
5 |
|
$this->propertiesCode[$propName] = ( |
|
375
|
|
|
"/**\n * Property was created automatically, do not change it manually\n */\n" . // Doc-block |
|
376
|
5 |
|
join(' ', Reflection::getModifierNames($propFlags)) . // List of modifiers for property |
|
377
|
5 |
|
' $' . // Space and vaiable symbol |
|
378
|
5 |
|
$propName . // Name of the property |
|
379
|
5 |
|
(is_string($defaultText) ? " = $defaultText" : '') . // Default value if present |
|
380
|
5 |
|
";\n" // End of line with property definition |
|
381
|
|
|
); |
|
382
|
|
|
|
|
383
|
5 |
|
return $this; |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
/** |
|
387
|
|
|
* Adds a definition for joinpoints private property in the class |
|
388
|
|
|
* |
|
389
|
|
|
* @return void |
|
390
|
|
|
*/ |
|
391
|
5 |
|
protected function addJoinpointsProperty() |
|
392
|
|
|
{ |
|
393
|
5 |
|
$this->setProperty( |
|
394
|
5 |
|
Property::IS_PRIVATE | Property::IS_STATIC, |
|
395
|
5 |
|
'__joinPoints', |
|
396
|
5 |
|
'[]' |
|
397
|
|
|
); |
|
398
|
5 |
|
} |
|
399
|
|
|
|
|
400
|
|
|
/** |
|
401
|
|
|
* Override parent method with joinpoint invocation |
|
402
|
|
|
* |
|
403
|
|
|
* @param ParsedMethod $method Method reflection |
|
404
|
|
|
*/ |
|
405
|
5 |
|
protected function overrideMethod(ParsedMethod $method) |
|
406
|
|
|
{ |
|
407
|
|
|
// temporary disable override of final methods |
|
408
|
5 |
|
if (!$method->isFinal() && !$method->isAbstract()) { |
|
409
|
5 |
|
$this->override($method->name, $this->getJoinpointInvocationBody($method)); |
|
410
|
|
|
} |
|
411
|
5 |
|
} |
|
412
|
|
|
|
|
413
|
|
|
/** |
|
414
|
|
|
* Creates definition for method body |
|
415
|
|
|
* |
|
416
|
|
|
* @param ParsedMethod $method Method reflection |
|
417
|
|
|
* |
|
418
|
|
|
* @return string new method body |
|
419
|
|
|
*/ |
|
420
|
5 |
|
protected function getJoinpointInvocationBody(ParsedMethod $method) |
|
421
|
|
|
{ |
|
422
|
5 |
|
$isStatic = $method->isStatic(); |
|
423
|
5 |
|
$scope = $isStatic ? self::$staticLsbExpression : '$this'; |
|
424
|
5 |
|
$prefix = $isStatic ? AspectContainer::STATIC_METHOD_PREFIX : AspectContainer::METHOD_PREFIX; |
|
425
|
|
|
|
|
426
|
5 |
|
$args = $this->prepareArgsLine($method); |
|
427
|
5 |
|
$body = ''; |
|
428
|
|
|
|
|
429
|
5 |
|
if (($this->class->name === $method->getDeclaringClassName()) && strpos($method->getSource(), 'func_get_args') !== false) { |
|
430
|
2 |
|
$body = '$argsList = \func_get_args();' . PHP_EOL; |
|
431
|
2 |
|
if (empty($args)) { |
|
432
|
|
|
$scope = "$scope, \$argsList"; |
|
433
|
|
|
} else { |
|
434
|
2 |
|
$scope = "$scope, [$args] + \$argsList"; |
|
435
|
|
|
} |
|
436
|
5 |
|
} elseif (!empty($args)) { |
|
437
|
2 |
|
$scope = "$scope, [$args]"; |
|
438
|
|
|
} |
|
439
|
|
|
|
|
440
|
5 |
|
$body .= "return self::\$__joinPoints['{$prefix}:{$method->name}']->__invoke($scope);"; |
|
441
|
|
|
|
|
442
|
5 |
|
return $body; |
|
443
|
|
|
} |
|
444
|
|
|
|
|
445
|
|
|
/** |
|
446
|
|
|
* Makes property intercepted |
|
447
|
|
|
* |
|
448
|
|
|
* @param ParsedProperty $property Reflection of property to intercept |
|
449
|
|
|
*/ |
|
450
|
|
|
protected function interceptProperty(ParsedProperty $property) |
|
451
|
|
|
{ |
|
452
|
|
|
$this->interceptedProperties[] = is_object($property) ? $property->name : $property; |
|
453
|
|
|
$this->isFieldsIntercepted = true; |
|
454
|
|
|
} |
|
455
|
|
|
|
|
456
|
|
|
/** |
|
457
|
|
|
* {@inheritDoc} |
|
458
|
|
|
*/ |
|
459
|
5 |
|
public function __toString() |
|
460
|
|
|
{ |
|
461
|
5 |
|
$ctor = $this->class->getConstructor(); |
|
462
|
5 |
|
if ($this->isFieldsIntercepted && (!$ctor || !$ctor->isPrivate())) { |
|
463
|
|
|
$this->addFieldInterceptorsCode($ctor); |
|
464
|
|
|
} |
|
465
|
|
|
|
|
466
|
5 |
|
$prefix = join(' ', Reflection::getModifierNames($this->class->getModifiers())); |
|
467
|
|
|
|
|
468
|
|
|
$classCode = ( |
|
469
|
5 |
|
$this->class->getDocComment() . "\n" . // Original doc-block |
|
470
|
5 |
|
($prefix ? "$prefix " : '') . // List of class modifiers |
|
471
|
5 |
|
'class ' . // 'class' keyword with one space |
|
472
|
5 |
|
$this->name . // Name of the class |
|
473
|
5 |
|
' extends ' . // 'extends' keyword with |
|
474
|
5 |
|
$this->parentClassName . // Name of the parent class |
|
475
|
5 |
|
($this->interfaces ? ' implements ' . join(', ', $this->interfaces) : '') . "\n" . // Interfaces list |
|
476
|
5 |
|
"{\n" . // Start of class definition |
|
477
|
5 |
|
($this->traits ? $this->indent('use ' . join(', ', $this->traits) . ';' . "\n") : '') . "\n" . // Traits list |
|
478
|
5 |
|
$this->indent(join("\n", $this->propertiesCode)) . "\n" . // Property definitions |
|
479
|
5 |
|
$this->indent(join("\n", $this->methodsCode)) . "\n" . // Method definitions |
|
480
|
5 |
|
"}" // End of class definition |
|
481
|
|
|
); |
|
482
|
|
|
|
|
483
|
|
|
return $classCode |
|
484
|
|
|
// Inject advices on call |
|
485
|
5 |
|
. PHP_EOL |
|
486
|
5 |
|
. '\\' . __CLASS__ . "::injectJoinPoints('" |
|
487
|
5 |
|
. $this->class->name . "'," |
|
488
|
5 |
|
. var_export($this->advices, true) . ");"; |
|
489
|
|
|
} |
|
490
|
|
|
|
|
491
|
|
|
/** |
|
492
|
|
|
* Add code for intercepting properties |
|
493
|
|
|
* |
|
494
|
|
|
* @param null|ParsedMethod $constructor Constructor reflection or null |
|
495
|
|
|
*/ |
|
496
|
|
|
protected function addFieldInterceptorsCode(ParsedMethod $constructor = null) |
|
497
|
|
|
{ |
|
498
|
|
|
$this->addTrait(PropertyInterceptionTrait::class); |
|
499
|
|
|
$this->isFieldsIntercepted = true; |
|
500
|
|
|
if ($constructor) { |
|
501
|
|
|
$this->override('__construct', $this->getConstructorBody($constructor, true)); |
|
502
|
|
|
} else { |
|
503
|
|
|
$this->setMethod(Method::IS_PUBLIC, '__construct', false, $this->getConstructorBody(), ''); |
|
504
|
|
|
} |
|
505
|
|
|
} |
|
506
|
|
|
|
|
507
|
|
|
/** |
|
508
|
|
|
* Creates a method code from Reflection |
|
509
|
|
|
* |
|
510
|
|
|
* @param ParsedMethod $method Reflection for method |
|
511
|
|
|
* @param string $body Body of method |
|
512
|
|
|
* |
|
513
|
|
|
* @return string |
|
514
|
|
|
*/ |
|
515
|
5 |
|
protected function getOverriddenMethod(ParsedMethod $method, $body) |
|
516
|
|
|
{ |
|
517
|
|
|
$code = ( |
|
518
|
5 |
|
preg_replace('/ {4}|\t/', '', $method->getDocComment()) . "\n" . // Original Doc-block |
|
519
|
5 |
|
join(' ', Reflection::getModifierNames($method->getModifiers())) . // List of modifiers |
|
520
|
5 |
|
' function ' . // 'function' keyword |
|
521
|
5 |
|
($method->returnsReference() ? '&' : '') . // By reference symbol |
|
522
|
5 |
|
$method->name . // Name of the method |
|
523
|
5 |
|
'(' . // Start of parameters list |
|
524
|
5 |
|
join(', ', $this->getParameters($method->getParameters())) . // List of parameters |
|
525
|
5 |
|
")\n" . // End of parameters list |
|
526
|
5 |
|
"{\n" . // Start of method body |
|
527
|
5 |
|
$this->indent($body) . "\n" . // Method body |
|
528
|
5 |
|
"}\n" // End of method body |
|
529
|
|
|
); |
|
530
|
|
|
|
|
531
|
5 |
|
return $code; |
|
532
|
|
|
} |
|
533
|
|
|
|
|
534
|
|
|
/** |
|
535
|
|
|
* Returns constructor code |
|
536
|
|
|
* |
|
537
|
|
|
* @param ParsedMethod $constructor Constructor reflection |
|
538
|
|
|
* @param bool $isCallParent Is there is a need to call parent code |
|
539
|
|
|
* |
|
540
|
|
|
* @return string |
|
541
|
|
|
*/ |
|
542
|
|
|
private function getConstructorBody(ParsedMethod $constructor = null, $isCallParent = false) |
|
543
|
|
|
{ |
|
544
|
|
|
$assocProperties = []; |
|
545
|
|
|
$listProperties = []; |
|
546
|
|
|
foreach ($this->interceptedProperties as $propertyName) { |
|
547
|
|
|
$assocProperties[] = "'$propertyName' => &\$this->$propertyName"; |
|
548
|
|
|
$listProperties[] = "\$this->$propertyName"; |
|
549
|
|
|
} |
|
550
|
|
|
$assocProperties = $this->indent(join(',' . PHP_EOL, $assocProperties)); |
|
551
|
|
|
$listProperties = $this->indent(join(',' . PHP_EOL, $listProperties)); |
|
552
|
|
|
if (isset($this->methodsCode['__construct'])) { |
|
553
|
|
|
$parentCall = $this->getJoinpointInvocationBody($constructor); |
|
|
|
|
|
|
554
|
|
|
} elseif ($isCallParent) { |
|
555
|
|
|
$parentCall = '\call_user_func_array(["parent", __FUNCTION__], \func_get_args());'; |
|
556
|
|
|
} else { |
|
557
|
|
|
$parentCall = ''; |
|
558
|
|
|
} |
|
559
|
|
|
|
|
560
|
|
|
return <<<CTOR |
|
561
|
|
|
\$this->__properties = array( |
|
562
|
|
|
$assocProperties |
|
563
|
|
|
); |
|
564
|
|
|
unset( |
|
565
|
|
|
$listProperties |
|
566
|
|
|
); |
|
567
|
|
|
$parentCall |
|
568
|
|
|
CTOR; |
|
569
|
|
|
} |
|
570
|
|
|
} |
|
571
|
|
|
|