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 (!empty($args)) { |
430
|
2 |
|
$scope = "$scope, [$args]"; |
431
|
|
|
} |
432
|
|
|
|
433
|
5 |
|
$body .= "return self::\$__joinPoints['{$prefix}:{$method->name}']->__invoke($scope);"; |
434
|
|
|
|
435
|
5 |
|
return $body; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Makes property intercepted |
440
|
|
|
* |
441
|
|
|
* @param ParsedProperty $property Reflection of property to intercept |
442
|
|
|
*/ |
443
|
|
|
protected function interceptProperty(ParsedProperty $property) |
444
|
|
|
{ |
445
|
|
|
$this->interceptedProperties[] = is_object($property) ? $property->name : $property; |
446
|
|
|
$this->isFieldsIntercepted = true; |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* {@inheritDoc} |
451
|
|
|
*/ |
452
|
5 |
|
public function __toString() |
453
|
|
|
{ |
454
|
5 |
|
$ctor = $this->class->getConstructor(); |
455
|
5 |
|
if ($this->isFieldsIntercepted && (!$ctor || !$ctor->isPrivate())) { |
456
|
|
|
$this->addFieldInterceptorsCode($ctor); |
457
|
|
|
} |
458
|
|
|
|
459
|
5 |
|
$prefix = join(' ', Reflection::getModifierNames($this->class->getModifiers())); |
460
|
|
|
|
461
|
|
|
$classCode = ( |
462
|
5 |
|
$this->class->getDocComment() . "\n" . // Original doc-block |
463
|
5 |
|
($prefix ? "$prefix " : '') . // List of class modifiers |
464
|
5 |
|
'class ' . // 'class' keyword with one space |
465
|
5 |
|
$this->name . // Name of the class |
466
|
5 |
|
' extends ' . // 'extends' keyword with |
467
|
5 |
|
$this->parentClassName . // Name of the parent class |
468
|
5 |
|
($this->interfaces ? ' implements ' . join(', ', $this->interfaces) : '') . "\n" . // Interfaces list |
469
|
5 |
|
"{\n" . // Start of class definition |
470
|
5 |
|
($this->traits ? $this->indent('use ' . join(', ', $this->traits) . ';' . "\n") : '') . "\n" . // Traits list |
471
|
5 |
|
$this->indent(join("\n", $this->propertiesCode)) . "\n" . // Property definitions |
472
|
5 |
|
$this->indent(join("\n", $this->methodsCode)) . "\n" . // Method definitions |
473
|
5 |
|
"}" // End of class definition |
474
|
|
|
); |
475
|
|
|
|
476
|
|
|
return $classCode |
477
|
|
|
// Inject advices on call |
478
|
5 |
|
. PHP_EOL |
479
|
5 |
|
. '\\' . __CLASS__ . "::injectJoinPoints('" |
480
|
5 |
|
. $this->class->name . "'," |
481
|
5 |
|
. var_export($this->advices, true) . ");"; |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* Add code for intercepting properties |
486
|
|
|
* |
487
|
|
|
* @param null|ParsedMethod $constructor Constructor reflection or null |
488
|
|
|
*/ |
489
|
|
|
protected function addFieldInterceptorsCode(ParsedMethod $constructor = null) |
490
|
|
|
{ |
491
|
|
|
$byReference = false; |
492
|
|
|
$this->setProperty(Property::IS_PRIVATE, '__properties', 'array()'); |
493
|
|
|
$this->setMethod(Method::IS_PUBLIC, '__get', $byReference, $this->getMagicGetterBody(), '$name'); |
494
|
|
|
$this->setMethod(Method::IS_PUBLIC, '__set', $byReference, $this->getMagicSetterBody(), '$name, $value'); |
495
|
|
|
$this->isFieldsIntercepted = true; |
496
|
|
|
if ($constructor) { |
497
|
|
|
$this->override('__construct', $this->getConstructorBody($constructor, true)); |
498
|
|
|
} else { |
499
|
|
|
$this->setMethod(Method::IS_PUBLIC, '__construct', $byReference, $this->getConstructorBody(), ''); |
500
|
|
|
} |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
/** |
504
|
|
|
* Creates a method code from Reflection |
505
|
|
|
* |
506
|
|
|
* @param ParsedMethod $method Reflection for method |
507
|
|
|
* @param string $body Body of method |
508
|
|
|
* |
509
|
|
|
* @return string |
510
|
|
|
*/ |
511
|
5 |
|
protected function getOverriddenMethod(ParsedMethod $method, $body) |
512
|
|
|
{ |
513
|
|
|
$code = ( |
514
|
5 |
|
preg_replace('/ {4}|\t/', '', $method->getDocComment()) . "\n" . // Original Doc-block |
515
|
5 |
|
join(' ', Reflection::getModifierNames($method->getModifiers())) . // List of modifiers |
516
|
5 |
|
' function ' . // 'function' keyword |
517
|
5 |
|
($method->returnsReference() ? '&' : '') . // By reference symbol |
518
|
5 |
|
$method->name . // Name of the method |
519
|
5 |
|
'(' . // Start of parameters list |
520
|
5 |
|
join(', ', $this->getParameters($method->getParameters())) . // List of parameters |
521
|
5 |
|
")\n" . // End of parameters list |
522
|
5 |
|
"{\n" . // Start of method body |
523
|
5 |
|
$this->indent($body) . "\n" . // Method body |
524
|
5 |
|
"}\n" // End of method body |
525
|
|
|
); |
526
|
|
|
|
527
|
5 |
|
return $code; |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* Returns a code for magic getter to perform interception |
532
|
|
|
* |
533
|
|
|
* @return string |
534
|
|
|
*/ |
535
|
|
|
private function getMagicGetterBody() |
536
|
|
|
{ |
537
|
|
|
return <<<'GETTER' |
538
|
|
|
if (\array_key_exists($name, $this->__properties)) { |
539
|
|
|
return self::$__joinPoints["prop:$name"]->__invoke( |
540
|
|
|
$this, |
541
|
|
|
\Go\Aop\Intercept\FieldAccess::READ, |
542
|
|
|
$this->__properties[$name] |
543
|
|
|
); |
544
|
|
|
} elseif (\method_exists(\get_parent_class(), __FUNCTION__)) { |
545
|
|
|
return parent::__get($name); |
546
|
|
|
} else { |
547
|
|
|
trigger_error("Trying to access undeclared property {$name}"); |
548
|
|
|
|
549
|
|
|
return null; |
550
|
|
|
} |
551
|
|
|
GETTER; |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
/** |
555
|
|
|
* Returns a code for magic setter to perform interception |
556
|
|
|
* |
557
|
|
|
* @return string |
558
|
|
|
*/ |
559
|
|
|
private function getMagicSetterBody() |
560
|
|
|
{ |
561
|
|
|
return <<<'SETTER' |
562
|
|
|
if (\array_key_exists($name, $this->__properties)) { |
563
|
|
|
$this->__properties[$name] = self::$__joinPoints["prop:$name"]->__invoke( |
564
|
|
|
$this, |
565
|
|
|
\Go\Aop\Intercept\FieldAccess::WRITE, |
566
|
|
|
$this->__properties[$name], |
567
|
|
|
$value |
568
|
|
|
); |
569
|
|
|
} elseif (\method_exists(\get_parent_class(), __FUNCTION__)) { |
570
|
|
|
parent::__set($name, $value); |
571
|
|
|
} else { |
572
|
|
|
$this->$name = $value; |
573
|
|
|
} |
574
|
|
|
SETTER; |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
/** |
578
|
|
|
* Returns constructor code |
579
|
|
|
* |
580
|
|
|
* @param ParsedMethod $constructor Constructor reflection |
581
|
|
|
* @param bool $isCallParent Is there is a need to call parent code |
582
|
|
|
* |
583
|
|
|
* @return string |
584
|
|
|
*/ |
585
|
|
|
private function getConstructorBody(ParsedMethod $constructor = null, $isCallParent = false) |
586
|
|
|
{ |
587
|
|
|
$assocProperties = []; |
588
|
|
|
$listProperties = []; |
589
|
|
|
foreach ($this->interceptedProperties as $propertyName) { |
590
|
|
|
$assocProperties[] = "'$propertyName' => \$this->$propertyName"; |
591
|
|
|
$listProperties[] = "\$this->$propertyName"; |
592
|
|
|
} |
593
|
|
|
$assocProperties = $this->indent(join(',' . PHP_EOL, $assocProperties)); |
594
|
|
|
$listProperties = $this->indent(join(',' . PHP_EOL, $listProperties)); |
595
|
|
|
if ($constructor) { |
596
|
|
|
$parentCall = $this->getJoinpointInvocationBody($constructor); |
597
|
|
|
} elseif ($isCallParent) { |
598
|
|
|
$parentCall = '\call_user_func_array(["parent", __FUNCTION__], \func_get_args());'; |
599
|
|
|
} else { |
600
|
|
|
$parentCall = ''; |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
return <<<CTOR |
604
|
|
|
\$this->__properties = array( |
605
|
|
|
$assocProperties |
606
|
|
|
); |
607
|
|
|
unset( |
608
|
|
|
$listProperties |
609
|
|
|
); |
610
|
|
|
$parentCall |
611
|
|
|
CTOR; |
612
|
|
|
} |
613
|
|
|
} |
614
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.