|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
namespace ProxyManagerTest\Functional; |
|
6
|
|
|
|
|
7
|
|
|
use PHPUnit\Framework\TestCase; |
|
8
|
|
|
use PHPUnit_Framework_MockObject_MockObject as Mock; |
|
9
|
|
|
use ProxyManager\Generator\ClassGenerator; |
|
10
|
|
|
use ProxyManager\Generator\Util\UniqueIdentifierGenerator; |
|
11
|
|
|
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; |
|
12
|
|
|
use ProxyManager\Proxy\GhostObjectInterface; |
|
13
|
|
|
use ProxyManager\Proxy\LazyLoadingInterface; |
|
14
|
|
|
use ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator; |
|
15
|
|
|
use ProxyManager\ProxyGenerator\Util\Properties; |
|
16
|
|
|
use ProxyManagerTestAsset\BaseClass; |
|
17
|
|
|
use ProxyManagerTestAsset\ClassWithAbstractPublicMethod; |
|
18
|
|
|
use ProxyManagerTestAsset\ClassWithCollidingPrivateInheritedProperties; |
|
19
|
|
|
use ProxyManagerTestAsset\ClassWithCounterConstructor; |
|
20
|
|
|
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod; |
|
21
|
|
|
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction; |
|
22
|
|
|
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction; |
|
23
|
|
|
use ProxyManagerTestAsset\ClassWithMixedProperties; |
|
24
|
|
|
use ProxyManagerTestAsset\ClassWithMixedPropertiesAndAccessorMethods; |
|
25
|
|
|
use ProxyManagerTestAsset\ClassWithMixedTypedProperties; |
|
26
|
|
|
use ProxyManagerTestAsset\ClassWithParentHint; |
|
27
|
|
|
use ProxyManagerTestAsset\ClassWithPrivateProperties; |
|
28
|
|
|
use ProxyManagerTestAsset\ClassWithProtectedProperties; |
|
29
|
|
|
use ProxyManagerTestAsset\ClassWithPublicArrayProperty; |
|
30
|
|
|
use ProxyManagerTestAsset\ClassWithPublicProperties; |
|
31
|
|
|
use ProxyManagerTestAsset\ClassWithSelfHint; |
|
32
|
|
|
use ProxyManagerTestAsset\EmptyClass; |
|
33
|
|
|
use ProxyManagerTestAsset\OtherObjectAccessClass; |
|
34
|
|
|
use ProxyManagerTestAsset\VoidCounter; |
|
35
|
|
|
use ReflectionClass; |
|
36
|
|
|
use ReflectionProperty; |
|
37
|
|
|
use stdClass; |
|
38
|
|
|
use function array_values; |
|
39
|
|
|
use function get_class; |
|
40
|
|
|
use function get_parent_class; |
|
41
|
|
|
use function random_int; |
|
42
|
|
|
use function serialize; |
|
43
|
|
|
use function sprintf; |
|
44
|
|
|
use function str_replace; |
|
45
|
|
|
use function uniqid; |
|
46
|
|
|
use function unserialize; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator} produced objects |
|
50
|
|
|
* |
|
51
|
|
|
* @group Functional |
|
52
|
|
|
* @coversNothing |
|
53
|
|
|
*/ |
|
54
|
|
|
class LazyLoadingGhostFunctionalTest extends TestCase |
|
55
|
|
|
{ |
|
56
|
|
|
/** |
|
57
|
|
|
* @dataProvider getProxyInitializingMethods |
|
58
|
|
|
* |
|
59
|
|
|
* @param mixed[] $params |
|
60
|
|
|
* @param mixed $expectedValue |
|
61
|
|
|
*/ |
|
62
|
|
|
public function testMethodCallsThatLazyLoadTheObject( |
|
63
|
|
|
string $className, |
|
64
|
|
|
object $instance, |
|
65
|
|
|
string $method, |
|
66
|
|
|
array $params, |
|
67
|
|
|
$expectedValue |
|
68
|
|
|
) : void { |
|
69
|
|
|
$proxyName = $this->generateProxy($className); |
|
70
|
|
|
|
|
71
|
|
|
/** @var GhostObjectInterface $proxy */ |
|
72
|
|
|
$proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance)); |
|
73
|
|
|
|
|
74
|
|
|
self::assertFalse($proxy->isProxyInitialized()); |
|
75
|
|
|
|
|
76
|
|
|
/** @var callable $callProxyMethod */ |
|
77
|
|
|
$callProxyMethod = [$proxy, $method]; |
|
78
|
|
|
$parameterValues = array_values($params); |
|
79
|
|
|
|
|
80
|
|
|
self::assertInternalType('callable', $callProxyMethod); |
|
|
|
|
|
|
81
|
|
|
self::assertSame($expectedValue, $callProxyMethod(...$parameterValues)); |
|
82
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* @dataProvider getProxyNonInitializingMethods |
|
87
|
|
|
* |
|
88
|
|
|
* @param mixed[] $params |
|
89
|
|
|
* @param mixed $expectedValue |
|
90
|
|
|
*/ |
|
91
|
|
|
public function testMethodCallsThatDoNotLazyLoadTheObject( |
|
92
|
|
|
string $className, |
|
93
|
|
|
object $instance, |
|
94
|
|
|
string $method, |
|
95
|
|
|
array $params, |
|
96
|
|
|
$expectedValue |
|
97
|
|
|
) : void { |
|
98
|
|
|
$proxyName = $this->generateProxy($className); |
|
99
|
|
|
$initializeMatcher = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock(); |
|
100
|
|
|
|
|
101
|
|
|
$initializeMatcher->expects(self::never())->method('__invoke'); // should not initialize the proxy |
|
102
|
|
|
|
|
103
|
|
|
/** @var GhostObjectInterface $proxy */ |
|
104
|
|
|
$proxy = $proxyName::staticProxyConstructor( |
|
105
|
|
|
$this->createInitializer($className, $instance, $initializeMatcher) |
|
106
|
|
|
); |
|
107
|
|
|
|
|
108
|
|
|
self::assertFalse($proxy->isProxyInitialized()); |
|
109
|
|
|
|
|
110
|
|
|
/** @var callable $callProxyMethod */ |
|
111
|
|
|
$callProxyMethod = [$proxy, $method]; |
|
112
|
|
|
$parameterValues = array_values($params); |
|
113
|
|
|
|
|
114
|
|
|
self::assertInternalType('callable', $callProxyMethod); |
|
|
|
|
|
|
115
|
|
|
self::assertSame($expectedValue, $callProxyMethod(...$parameterValues)); |
|
116
|
|
|
self::assertFalse($proxy->isProxyInitialized()); |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
/** |
|
120
|
|
|
* @dataProvider getProxyMethods |
|
121
|
|
|
* |
|
122
|
|
|
* @param mixed[] $params |
|
123
|
|
|
* @param mixed $expectedValue |
|
124
|
|
|
*/ |
|
125
|
|
|
public function testMethodCallsAfterUnSerialization( |
|
126
|
|
|
string $className, |
|
127
|
|
|
object $instance, |
|
128
|
|
|
string $method, |
|
129
|
|
|
array $params, |
|
130
|
|
|
$expectedValue |
|
131
|
|
|
) : void { |
|
132
|
|
|
$proxyName = $this->generateProxy($className); |
|
133
|
|
|
|
|
134
|
|
|
/** @var GhostObjectInterface $proxy */ |
|
135
|
|
|
$proxy = unserialize(serialize($proxyName::staticProxyConstructor( |
|
136
|
|
|
$this->createInitializer($className, $instance) |
|
137
|
|
|
))); |
|
138
|
|
|
|
|
139
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
140
|
|
|
|
|
141
|
|
|
/** @var callable $callProxyMethod */ |
|
142
|
|
|
$callProxyMethod = [$proxy, $method]; |
|
143
|
|
|
$parameterValues = array_values($params); |
|
144
|
|
|
|
|
145
|
|
|
self::assertInternalType('callable', $callProxyMethod); |
|
|
|
|
|
|
146
|
|
|
self::assertSame($expectedValue, $callProxyMethod(...$parameterValues)); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* @dataProvider getProxyMethods |
|
151
|
|
|
* |
|
152
|
|
|
* @param mixed[] $params |
|
153
|
|
|
* @param mixed $expectedValue |
|
154
|
|
|
*/ |
|
155
|
|
|
public function testMethodCallsAfterCloning( |
|
156
|
|
|
string $className, |
|
157
|
|
|
object $instance, |
|
158
|
|
|
string $method, |
|
159
|
|
|
array $params, |
|
160
|
|
|
$expectedValue |
|
161
|
|
|
) : void { |
|
162
|
|
|
$proxyName = $this->generateProxy($className); |
|
163
|
|
|
|
|
164
|
|
|
/** @var GhostObjectInterface $proxy */ |
|
165
|
|
|
$proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance)); |
|
166
|
|
|
$cloned = clone $proxy; |
|
167
|
|
|
|
|
168
|
|
|
self::assertTrue($cloned->isProxyInitialized()); |
|
169
|
|
|
|
|
170
|
|
|
/** @var callable $callProxyMethod */ |
|
171
|
|
|
$callProxyMethod = [$proxy, $method]; |
|
172
|
|
|
$parameterValues = array_values($params); |
|
173
|
|
|
|
|
174
|
|
|
self::assertInternalType('callable', $callProxyMethod); |
|
|
|
|
|
|
175
|
|
|
self::assertSame($expectedValue, $callProxyMethod(...$parameterValues)); |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
/** |
|
179
|
|
|
* @dataProvider getPropertyAccessProxies |
|
180
|
|
|
* |
|
181
|
|
|
* @param mixed $propertyValue |
|
182
|
|
|
*/ |
|
183
|
|
|
public function testPropertyReadAccess( |
|
184
|
|
|
object $instance, |
|
|
|
|
|
|
185
|
|
|
GhostObjectInterface $proxy, |
|
186
|
|
|
string $publicProperty, |
|
187
|
|
|
$propertyValue |
|
188
|
|
|
) : void { |
|
189
|
|
|
self::assertSame($propertyValue, $proxy->$publicProperty); |
|
190
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
/** |
|
194
|
|
|
* @dataProvider getPropertyAccessProxies |
|
195
|
|
|
* |
|
196
|
|
|
*/ |
|
197
|
|
|
public function testPropertyWriteAccess(object $instance, GhostObjectInterface $proxy, string $publicProperty |
|
|
|
|
|
|
198
|
|
|
) : void |
|
199
|
|
|
{ |
|
200
|
|
|
$newValue = uniqid('', true); |
|
201
|
|
|
$proxy->$publicProperty = $newValue; |
|
202
|
|
|
|
|
203
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
204
|
|
|
self::assertSame($newValue, $proxy->$publicProperty); |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
/** |
|
208
|
|
|
* @dataProvider getPropertyAccessProxies |
|
209
|
|
|
* |
|
210
|
|
|
*/ |
|
211
|
|
|
public function testPropertyExistence(object $instance, GhostObjectInterface $proxy, string $publicProperty) : void |
|
212
|
|
|
{ |
|
213
|
|
|
self::assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty)); |
|
214
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
215
|
|
|
} |
|
216
|
|
|
|
|
217
|
|
|
/** |
|
218
|
|
|
* @dataProvider getPropertyAccessProxies |
|
219
|
|
|
* |
|
220
|
|
|
*/ |
|
221
|
|
|
public function testPropertyAbsence(object $instance, GhostObjectInterface $proxy, string $publicProperty) : void |
|
|
|
|
|
|
222
|
|
|
{ |
|
223
|
|
|
$proxy->$publicProperty = null; |
|
224
|
|
|
self::assertFalse(isset($proxy->$publicProperty)); |
|
225
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* @dataProvider getPropertyAccessProxies |
|
230
|
|
|
* |
|
231
|
|
|
*/ |
|
232
|
|
|
public function testPropertyUnset(object $instance, GhostObjectInterface $proxy, string $publicProperty) : void |
|
233
|
|
|
{ |
|
234
|
|
|
unset($proxy->$publicProperty); |
|
235
|
|
|
|
|
236
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
237
|
|
|
self::assertTrue(isset($instance->$publicProperty)); |
|
238
|
|
|
self::assertFalse(isset($proxy->$publicProperty)); |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
/** |
|
242
|
|
|
* Verifies that accessing a public property containing an array behaves like in a normal context |
|
243
|
|
|
*/ |
|
244
|
|
|
public function testCanWriteToArrayKeysInPublicProperty() : void |
|
245
|
|
|
{ |
|
246
|
|
|
$instance = new ClassWithPublicArrayProperty(); |
|
247
|
|
|
$className = get_class($instance); |
|
248
|
|
|
$initializer = $this->createInitializer($className, $instance); |
|
|
|
|
|
|
249
|
|
|
$proxyName = $this->generateProxy($className); |
|
250
|
|
|
/** @var ClassWithPublicArrayProperty $proxy */ |
|
251
|
|
|
$proxy = $proxyName::staticProxyConstructor($initializer); |
|
252
|
|
|
|
|
253
|
|
|
$proxy->arrayProperty['foo'] = 'bar'; |
|
254
|
|
|
|
|
255
|
|
|
self::assertSame('bar', $proxy->arrayProperty['foo']); |
|
256
|
|
|
|
|
257
|
|
|
$proxy->arrayProperty = ['tab' => 'taz']; |
|
258
|
|
|
|
|
259
|
|
|
self::assertSame(['tab' => 'taz'], $proxy->arrayProperty); |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
/** |
|
263
|
|
|
* Verifies that public properties retrieved via `__get` don't get modified in the object itself |
|
264
|
|
|
*/ |
|
265
|
|
|
public function testWillNotModifyRetrievedPublicProperties() : void |
|
266
|
|
|
{ |
|
267
|
|
|
$instance = new ClassWithPublicProperties(); |
|
268
|
|
|
$className = get_class($instance); |
|
269
|
|
|
$initializer = $this->createInitializer($className, $instance); |
|
|
|
|
|
|
270
|
|
|
$proxyName = $this->generateProxy($className); |
|
271
|
|
|
/** @var ClassWithPublicProperties $proxy */ |
|
272
|
|
|
$proxy = $proxyName::staticProxyConstructor($initializer); |
|
273
|
|
|
$variable = $proxy->property0; |
|
274
|
|
|
|
|
275
|
|
|
self::assertSame('property0', $variable); |
|
276
|
|
|
|
|
277
|
|
|
$variable = 'foo'; |
|
278
|
|
|
|
|
279
|
|
|
self::assertSame('property0', $proxy->property0); |
|
280
|
|
|
self::assertSame('foo', $variable); |
|
281
|
|
|
} |
|
282
|
|
|
|
|
283
|
|
|
/** |
|
284
|
|
|
* Verifies that public properties references retrieved via `__get` modify in the object state |
|
285
|
|
|
*/ |
|
286
|
|
|
public function testWillModifyByRefRetrievedPublicProperties() : void |
|
287
|
|
|
{ |
|
288
|
|
|
$instance = new ClassWithPublicProperties(); |
|
289
|
|
|
$className = get_class($instance); |
|
290
|
|
|
$initializer = $this->createInitializer($className, $instance); |
|
|
|
|
|
|
291
|
|
|
$proxyName = $this->generateProxy($className); |
|
292
|
|
|
/** @var ClassWithPublicProperties $proxy */ |
|
293
|
|
|
$proxy = $proxyName::staticProxyConstructor($initializer); |
|
294
|
|
|
$variable = &$proxy->property0; |
|
295
|
|
|
|
|
296
|
|
|
self::assertSame('property0', $variable); |
|
297
|
|
|
|
|
298
|
|
|
$variable = 'foo'; |
|
299
|
|
|
|
|
300
|
|
|
self::assertSame('foo', $proxy->property0); |
|
301
|
|
|
self::assertSame('foo', $variable); |
|
302
|
|
|
} |
|
303
|
|
|
|
|
304
|
|
|
public function testKeepsInitializerWhenNotOverwitten() : void |
|
305
|
|
|
{ |
|
306
|
|
|
$instance = new BaseClass(); |
|
307
|
|
|
$proxyName = $this->generateProxy(get_class($instance)); |
|
308
|
|
|
$initializer = function () : void { |
|
309
|
|
|
}; |
|
310
|
|
|
/** @var GhostObjectInterface $proxy */ |
|
311
|
|
|
$proxy = $proxyName::staticProxyConstructor($initializer); |
|
312
|
|
|
|
|
313
|
|
|
$proxy->initializeProxy(); |
|
314
|
|
|
|
|
315
|
|
|
self::assertSame($initializer, $proxy->getProxyInitializer()); |
|
316
|
|
|
} |
|
317
|
|
|
|
|
318
|
|
|
/** |
|
319
|
|
|
* Verifies that public properties are not being initialized multiple times |
|
320
|
|
|
*/ |
|
321
|
|
|
public function testKeepsInitializedPublicProperties() : void |
|
322
|
|
|
{ |
|
323
|
|
|
$instance = new BaseClass(); |
|
324
|
|
|
$proxyName = $this->generateProxy(get_class($instance)); |
|
325
|
|
|
$initializer = function (BaseClass $proxy, string $method, $parameters, & $initializer) : void { |
|
326
|
|
|
$initializer = null; |
|
327
|
|
|
$proxy->publicProperty = 'newValue'; |
|
328
|
|
|
}; |
|
329
|
|
|
/** @var GhostObjectInterface&BaseClass $proxy */ |
|
|
|
|
|
|
330
|
|
|
$proxy = $proxyName::staticProxyConstructor($initializer); |
|
331
|
|
|
|
|
332
|
|
|
$proxy->initializeProxy(); |
|
333
|
|
|
self::assertSame('newValue', $proxy->publicProperty); |
|
334
|
|
|
|
|
335
|
|
|
$proxy->publicProperty = 'otherValue'; |
|
336
|
|
|
|
|
337
|
|
|
$proxy->initializeProxy(); |
|
338
|
|
|
|
|
339
|
|
|
self::assertSame('otherValue', $proxy->publicProperty); |
|
340
|
|
|
} |
|
341
|
|
|
|
|
342
|
|
|
/** |
|
343
|
|
|
* Verifies that properties' default values are preserved |
|
344
|
|
|
*/ |
|
345
|
|
|
public function testPublicPropertyDefaultWillBePreserved() : void |
|
346
|
|
|
{ |
|
347
|
|
|
$instance = new ClassWithPublicProperties(); |
|
348
|
|
|
$proxyName = $this->generateProxy(get_class($instance)); |
|
349
|
|
|
/** @var ClassWithPublicProperties $proxy */ |
|
350
|
|
|
$proxy = $proxyName::staticProxyConstructor(function () : void { |
|
351
|
|
|
}); |
|
352
|
|
|
|
|
353
|
|
|
self::assertSame('property0', $proxy->property0); |
|
354
|
|
|
} |
|
355
|
|
|
|
|
356
|
|
|
/** |
|
357
|
|
|
* Verifies that protected properties' default values are preserved |
|
358
|
|
|
*/ |
|
359
|
|
|
public function testProtectedPropertyDefaultWillBePreserved() : void |
|
360
|
|
|
{ |
|
361
|
|
|
$instance = new ClassWithProtectedProperties(); |
|
362
|
|
|
$proxyName = $this->generateProxy(get_class($instance)); |
|
363
|
|
|
/** @var ClassWithProtectedProperties $proxy */ |
|
364
|
|
|
$proxy = $proxyName::staticProxyConstructor(function () : void { |
|
365
|
|
|
}); |
|
366
|
|
|
|
|
367
|
|
|
// Check protected property via reflection |
|
368
|
|
|
$reflectionProperty = new ReflectionProperty($instance, 'property0'); |
|
369
|
|
|
$reflectionProperty->setAccessible(true); |
|
370
|
|
|
|
|
371
|
|
|
self::assertSame('property0', $reflectionProperty->getValue($proxy)); |
|
372
|
|
|
} |
|
373
|
|
|
|
|
374
|
|
|
/** |
|
375
|
|
|
* Verifies that private properties' default values are preserved |
|
376
|
|
|
*/ |
|
377
|
|
|
public function testPrivatePropertyDefaultWillBePreserved() : void |
|
378
|
|
|
{ |
|
379
|
|
|
$instance = new ClassWithPrivateProperties(); |
|
380
|
|
|
$proxyName = $this->generateProxy(get_class($instance)); |
|
381
|
|
|
/** @var ClassWithPrivateProperties $proxy */ |
|
382
|
|
|
$proxy = $proxyName::staticProxyConstructor(function () : void { |
|
383
|
|
|
}); |
|
384
|
|
|
|
|
385
|
|
|
// Check protected property via reflection |
|
386
|
|
|
$reflectionProperty = new ReflectionProperty($instance, 'property0'); |
|
387
|
|
|
$reflectionProperty->setAccessible(true); |
|
388
|
|
|
|
|
389
|
|
|
self::assertSame('property0', $reflectionProperty->getValue($proxy)); |
|
390
|
|
|
} |
|
391
|
|
|
|
|
392
|
|
|
/** |
|
393
|
|
|
* @group 159 |
|
394
|
|
|
* @group 192 |
|
395
|
|
|
*/ |
|
396
|
|
|
public function testMultiLevelPrivatePropertiesDefaultsWillBePreserved() : void |
|
397
|
|
|
{ |
|
398
|
|
|
$instance = new ClassWithCollidingPrivateInheritedProperties(); |
|
399
|
|
|
$proxyName = $this->generateProxy(ClassWithCollidingPrivateInheritedProperties::class); |
|
400
|
|
|
/** @var ClassWithPrivateProperties $proxy */ |
|
401
|
|
|
$proxy = $proxyName::staticProxyConstructor(function () : void { |
|
402
|
|
|
}); |
|
403
|
|
|
|
|
404
|
|
|
$childProperty = new ReflectionProperty($instance, 'property0'); |
|
405
|
|
|
$parentProperty = new ReflectionProperty(get_parent_class($instance), 'property0'); |
|
406
|
|
|
|
|
407
|
|
|
$childProperty->setAccessible(true); |
|
408
|
|
|
$parentProperty->setAccessible(true); |
|
409
|
|
|
|
|
410
|
|
|
self::assertSame('childClassProperty0', $childProperty->getValue($proxy)); |
|
411
|
|
|
self::assertSame('property0', $parentProperty->getValue($proxy)); |
|
412
|
|
|
} |
|
413
|
|
|
|
|
414
|
|
|
/** |
|
415
|
|
|
* @group 159 |
|
416
|
|
|
* @group 192 |
|
417
|
|
|
*/ |
|
418
|
|
|
public function testMultiLevelPrivatePropertiesByRefInitialization() : void |
|
419
|
|
|
{ |
|
420
|
|
|
$class = ClassWithCollidingPrivateInheritedProperties::class; |
|
421
|
|
|
$proxyName = $this->generateProxy($class); |
|
422
|
|
|
/** @var ClassWithPrivateProperties $proxy */ |
|
423
|
|
|
$proxy = $proxyName::staticProxyConstructor( |
|
424
|
|
|
function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void { |
|
425
|
|
|
$initializer = null; |
|
426
|
|
|
$properties["\0" . $class . "\0property0"] = 'foo'; |
|
427
|
|
|
$properties["\0" . get_parent_class($class) . "\0property0"] = 'bar'; |
|
428
|
|
|
} |
|
429
|
|
|
); |
|
430
|
|
|
|
|
431
|
|
|
$childProperty = new ReflectionProperty($class, 'property0'); |
|
432
|
|
|
$parentProperty = new ReflectionProperty(get_parent_class($class), 'property0'); |
|
433
|
|
|
|
|
434
|
|
|
$childProperty->setAccessible(true); |
|
435
|
|
|
$parentProperty->setAccessible(true); |
|
436
|
|
|
|
|
437
|
|
|
self::assertSame('foo', $childProperty->getValue($proxy)); |
|
438
|
|
|
self::assertSame('bar', $parentProperty->getValue($proxy)); |
|
439
|
|
|
} |
|
440
|
|
|
|
|
441
|
|
|
/** |
|
442
|
|
|
* @group 159 |
|
443
|
|
|
* @group 192 |
|
444
|
|
|
* |
|
445
|
|
|
* Test designed to verify that the cached logic does take into account the fact that |
|
446
|
|
|
* proxies are different instances |
|
447
|
|
|
*/ |
|
448
|
|
|
public function testGetPropertyFromDifferentProxyInstances() : void |
|
449
|
|
|
{ |
|
450
|
|
|
$class = ClassWithCollidingPrivateInheritedProperties::class; |
|
451
|
|
|
$proxyName = $this->generateProxy($class); |
|
452
|
|
|
|
|
453
|
|
|
/** @var ClassWithPrivateProperties $proxy1 */ |
|
454
|
|
|
$proxy1 = $proxyName::staticProxyConstructor( |
|
455
|
|
|
function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void { |
|
456
|
|
|
$initializer = null; |
|
457
|
|
|
$properties["\0" . $class . "\0property0"] = 'foo'; |
|
458
|
|
|
$properties["\0" . get_parent_class($class) . "\0property0"] = 'bar'; |
|
459
|
|
|
} |
|
460
|
|
|
); |
|
461
|
|
|
/** @var ClassWithPrivateProperties $proxy2 */ |
|
462
|
|
|
$proxy2 = $proxyName::staticProxyConstructor( |
|
463
|
|
|
function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void { |
|
464
|
|
|
$initializer = null; |
|
465
|
|
|
$properties["\0" . $class . "\0property0"] = 'baz'; |
|
466
|
|
|
$properties["\0" . get_parent_class($class) . "\0property0"] = 'tab'; |
|
467
|
|
|
} |
|
468
|
|
|
); |
|
469
|
|
|
|
|
470
|
|
|
$childProperty = new ReflectionProperty($class, 'property0'); |
|
471
|
|
|
$parentProperty = new ReflectionProperty(get_parent_class($class), 'property0'); |
|
472
|
|
|
|
|
473
|
|
|
$childProperty->setAccessible(true); |
|
474
|
|
|
$parentProperty->setAccessible(true); |
|
475
|
|
|
|
|
476
|
|
|
self::assertSame('foo', $childProperty->getValue($proxy1)); |
|
477
|
|
|
self::assertSame('bar', $parentProperty->getValue($proxy1)); |
|
478
|
|
|
|
|
479
|
|
|
self::assertSame('baz', $childProperty->getValue($proxy2)); |
|
480
|
|
|
self::assertSame('tab', $parentProperty->getValue($proxy2)); |
|
481
|
|
|
} |
|
482
|
|
|
|
|
483
|
|
|
/** |
|
484
|
|
|
* @group 159 |
|
485
|
|
|
* @group 192 |
|
486
|
|
|
* |
|
487
|
|
|
* Test designed to verify that the cached logic does take into account the fact that |
|
488
|
|
|
* proxies are different instances |
|
489
|
|
|
*/ |
|
490
|
|
|
public function testSetPrivatePropertyOnDifferentProxyInstances() : void |
|
491
|
|
|
{ |
|
492
|
|
|
$class = ClassWithMixedPropertiesAndAccessorMethods::class; |
|
493
|
|
|
$proxyName = $this->generateProxy($class); |
|
494
|
|
|
|
|
495
|
|
|
/** @var ClassWithMixedPropertiesAndAccessorMethods $proxy1 */ |
|
496
|
|
|
$proxy1 = $proxyName::staticProxyConstructor( |
|
497
|
|
|
function ($proxy, $method, $params, & $initializer) : void { |
|
498
|
|
|
$initializer = null; |
|
499
|
|
|
} |
|
500
|
|
|
); |
|
501
|
|
|
/** @var ClassWithMixedPropertiesAndAccessorMethods $proxy2 */ |
|
502
|
|
|
$proxy2 = $proxyName::staticProxyConstructor( |
|
503
|
|
|
function ($proxy, $method, $params, & $initializer) : void { |
|
504
|
|
|
$initializer = null; |
|
505
|
|
|
} |
|
506
|
|
|
); |
|
507
|
|
|
|
|
508
|
|
|
$proxy1->set('privateProperty', 'private1'); |
|
509
|
|
|
$proxy2->set('privateProperty', 'private2'); |
|
510
|
|
|
self::assertSame('private1', $proxy1->get('privateProperty')); |
|
511
|
|
|
self::assertSame('private2', $proxy2->get('privateProperty')); |
|
512
|
|
|
} |
|
513
|
|
|
|
|
514
|
|
|
/** |
|
515
|
|
|
* @group 159 |
|
516
|
|
|
* @group 192 |
|
517
|
|
|
* |
|
518
|
|
|
* Test designed to verify that the cached logic does take into account the fact that |
|
519
|
|
|
* proxies are different instances |
|
520
|
|
|
*/ |
|
521
|
|
|
public function testIssetPrivatePropertyOnDifferentProxyInstances() : void |
|
522
|
|
|
{ |
|
523
|
|
|
$class = ClassWithMixedPropertiesAndAccessorMethods::class; |
|
524
|
|
|
$proxyName = $this->generateProxy($class); |
|
525
|
|
|
|
|
526
|
|
|
/** @var ClassWithMixedPropertiesAndAccessorMethods $proxy1 */ |
|
527
|
|
|
$proxy1 = $proxyName::staticProxyConstructor( |
|
528
|
|
|
function ($proxy, $method, $params, & $initializer) : void { |
|
529
|
|
|
$initializer = null; |
|
530
|
|
|
} |
|
531
|
|
|
); |
|
532
|
|
|
/** @var ClassWithMixedPropertiesAndAccessorMethods $proxy2 */ |
|
533
|
|
|
$proxy2 = $proxyName::staticProxyConstructor( |
|
534
|
|
|
function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void { |
|
535
|
|
|
$initializer = null; |
|
536
|
|
|
$properties["\0" . $class . "\0privateProperty"] = null; |
|
537
|
|
|
} |
|
538
|
|
|
); |
|
539
|
|
|
|
|
540
|
|
|
self::assertTrue($proxy1->has('privateProperty')); |
|
541
|
|
|
self::assertFalse($proxy2->has('privateProperty')); |
|
542
|
|
|
self::assertTrue($proxy1->has('privateProperty')); |
|
543
|
|
|
self::assertFalse($proxy2->has('privateProperty')); |
|
544
|
|
|
} |
|
545
|
|
|
|
|
546
|
|
|
/** |
|
547
|
|
|
* @group 159 |
|
548
|
|
|
* @group 192 |
|
549
|
|
|
* |
|
550
|
|
|
* Test designed to verify that the cached logic does take into account the fact that |
|
551
|
|
|
* proxies are different instances |
|
552
|
|
|
*/ |
|
553
|
|
|
public function testUnsetPrivatePropertyOnDifferentProxyInstances() : void |
|
554
|
|
|
{ |
|
555
|
|
|
$class = ClassWithMixedPropertiesAndAccessorMethods::class; |
|
556
|
|
|
$proxyName = $this->generateProxy($class); |
|
557
|
|
|
|
|
558
|
|
|
/** @var ClassWithMixedPropertiesAndAccessorMethods $proxy1 */ |
|
559
|
|
|
$proxy1 = $proxyName::staticProxyConstructor( |
|
560
|
|
|
function ($proxy, $method, $params, & $initializer) : void { |
|
561
|
|
|
$initializer = null; |
|
562
|
|
|
} |
|
563
|
|
|
); |
|
564
|
|
|
/** @var ClassWithMixedPropertiesAndAccessorMethods $proxy2 */ |
|
565
|
|
|
$proxy2 = $proxyName::staticProxyConstructor( |
|
566
|
|
|
function ($proxy, $method, $params, & $initializer) : void { |
|
567
|
|
|
$initializer = null; |
|
568
|
|
|
} |
|
569
|
|
|
); |
|
570
|
|
|
|
|
571
|
|
|
self::assertTrue($proxy1->has('privateProperty')); |
|
572
|
|
|
$proxy2->remove('privateProperty'); |
|
573
|
|
|
self::assertFalse($proxy2->has('privateProperty')); |
|
574
|
|
|
self::assertTrue($proxy1->has('privateProperty')); |
|
575
|
|
|
$proxy1->remove('privateProperty'); |
|
576
|
|
|
self::assertFalse($proxy1->has('privateProperty')); |
|
577
|
|
|
self::assertFalse($proxy2->has('privateProperty')); |
|
578
|
|
|
} |
|
579
|
|
|
|
|
580
|
|
|
/** |
|
581
|
|
|
* @group 159 |
|
582
|
|
|
* @group 192 |
|
583
|
|
|
* |
|
584
|
|
|
* Test designed to verify that the cached logic does take into account the fact that |
|
585
|
|
|
* proxies are different instances |
|
586
|
|
|
*/ |
|
587
|
|
|
public function testIssetPrivateAndProtectedPropertiesDoesCheckAgainstBooleanFalse() : void |
|
588
|
|
|
{ |
|
589
|
|
|
$class = ClassWithMixedPropertiesAndAccessorMethods::class; |
|
590
|
|
|
$proxyName = $this->generateProxy($class); |
|
591
|
|
|
|
|
592
|
|
|
/** @var ClassWithMixedPropertiesAndAccessorMethods $proxy1 */ |
|
593
|
|
|
$proxy1 = $proxyName::staticProxyConstructor( |
|
594
|
|
|
function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void { |
|
595
|
|
|
$initializer = null; |
|
596
|
|
|
$properties['publicProperty'] = false; |
|
597
|
|
|
$properties["\0*\0protectedProperty"] = false; |
|
598
|
|
|
$properties["\0" . $class . "\0privateProperty"] = false; |
|
599
|
|
|
} |
|
600
|
|
|
); |
|
601
|
|
|
/** @var ClassWithMixedPropertiesAndAccessorMethods $proxy2 */ |
|
602
|
|
|
$proxy2 = $proxyName::staticProxyConstructor( |
|
603
|
|
|
function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void { |
|
604
|
|
|
$initializer = null; |
|
605
|
|
|
$properties['publicProperty'] = null; |
|
606
|
|
|
$properties["\0*\0protectedProperty"] = null; |
|
607
|
|
|
$properties["\0" . $class . "\0privateProperty"] = null; |
|
608
|
|
|
} |
|
609
|
|
|
); |
|
610
|
|
|
|
|
611
|
|
|
self::assertTrue($proxy1->has('protectedProperty')); |
|
612
|
|
|
self::assertTrue($proxy1->has('publicProperty')); |
|
613
|
|
|
self::assertTrue($proxy1->has('privateProperty')); |
|
614
|
|
|
|
|
615
|
|
|
self::assertFalse($proxy2->has('protectedProperty')); |
|
616
|
|
|
self::assertFalse($proxy2->has('publicProperty')); |
|
617
|
|
|
self::assertFalse($proxy2->has('privateProperty')); |
|
618
|
|
|
} |
|
619
|
|
|
|
|
620
|
|
|
public function testByRefInitialization() : void |
|
621
|
|
|
{ |
|
622
|
|
|
$proxyName = $this->generateProxy(ClassWithMixedProperties::class); |
|
623
|
|
|
/** @var ClassWithPrivateProperties $proxy */ |
|
624
|
|
|
$proxy = $proxyName::staticProxyConstructor( |
|
625
|
|
|
function ($proxy, $method, $params, & $initializer, array $properties) : void { |
|
626
|
|
|
$initializer = null; |
|
627
|
|
|
$properties["\0" . ClassWithMixedProperties::class . "\0privateProperty0"] = 'private0'; |
|
628
|
|
|
$properties["\0" . ClassWithMixedProperties::class . "\0privateProperty1"] = 'private1'; |
|
629
|
|
|
$properties["\0" . ClassWithMixedProperties::class . "\0privateProperty2"] = 'private2'; |
|
630
|
|
|
$properties["\0*\0protectedProperty0"] = 'protected0'; |
|
631
|
|
|
$properties["\0*\0protectedProperty1"] = 'protected1'; |
|
632
|
|
|
$properties["\0*\0protectedProperty2"] = 'protected2'; |
|
633
|
|
|
$properties['publicProperty0'] = 'public0'; |
|
634
|
|
|
$properties['publicProperty1'] = 'public1'; |
|
635
|
|
|
$properties['publicProperty2'] = 'public2'; |
|
636
|
|
|
} |
|
637
|
|
|
); |
|
638
|
|
|
|
|
639
|
|
|
$reflectionClass = new ReflectionClass(ClassWithMixedProperties::class); |
|
640
|
|
|
|
|
641
|
|
|
foreach (Properties::fromReflectionClass($reflectionClass)->getInstanceProperties() as $property) { |
|
642
|
|
|
$property->setAccessible(true); |
|
643
|
|
|
|
|
644
|
|
|
self::assertSame(str_replace('Property', '', $property->getName()), $property->getValue($proxy)); |
|
645
|
|
|
} |
|
646
|
|
|
} |
|
647
|
|
|
|
|
648
|
|
|
public function testByRefInitializationOfTypedProperties() : void |
|
649
|
|
|
{ |
|
650
|
|
|
$proxyName = $this->generateProxy(ClassWithMixedTypedProperties::class); |
|
651
|
|
|
/* @var $proxy ClassWithMixedTypedProperties */ |
|
652
|
|
|
$proxy = $proxyName::staticProxyConstructor( |
|
653
|
|
|
function ($proxy, $method, $params, & $initializer, array $properties) { |
|
654
|
|
|
$initializer = null; |
|
655
|
|
|
$properties["\0" . ClassWithMixedTypedProperties::class . "\0privateStringProperty"] = 'private0'; |
|
656
|
|
|
$properties["\0*\0protectedStringProperty"] = 'protected0'; |
|
657
|
|
|
$properties['publicStringProperty'] = 'public0'; |
|
658
|
|
|
} |
|
659
|
|
|
); |
|
660
|
|
|
|
|
661
|
|
|
$reflectionClass = new ReflectionClass(ClassWithMixedTypedProperties::class); |
|
662
|
|
|
|
|
663
|
|
|
$properties = Properties::fromReflectionClass($reflectionClass)->getInstanceProperties(); |
|
664
|
|
|
|
|
665
|
|
|
$privateProperty = $properties["\0" . ClassWithMixedTypedProperties::class . "\0privateStringProperty"]; |
|
666
|
|
|
$protectedProperty = $properties["\0*\0protectedStringProperty"]; |
|
667
|
|
|
|
|
668
|
|
|
$privateProperty->setAccessible(true); |
|
669
|
|
|
$protectedProperty->setAccessible(true); |
|
670
|
|
|
|
|
671
|
|
|
self::assertSame('private0', $privateProperty->getValue($proxy)); |
|
672
|
|
|
self::assertSame('protected0', $properties["\0*\0protectedStringProperty"]->getValue($proxy)); |
|
673
|
|
|
self::assertSame('public0', $proxy->publicStringProperty); |
|
674
|
|
|
} |
|
675
|
|
|
|
|
676
|
|
|
/** |
|
677
|
|
|
* @group 115 |
|
678
|
|
|
* @group 175 |
|
679
|
|
|
*/ |
|
680
|
|
|
public function testWillBehaveLikeObjectWithNormalConstructor() : void |
|
681
|
|
|
{ |
|
682
|
|
|
$instance = new ClassWithCounterConstructor(10); |
|
683
|
|
|
|
|
684
|
|
|
self::assertSame(10, $instance->amount, 'Verifying that test asset works as expected'); |
|
685
|
|
|
self::assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected'); |
|
686
|
|
|
$instance->__construct(3); |
|
687
|
|
|
self::assertSame(13, $instance->amount, 'Verifying that test asset works as expected'); |
|
688
|
|
|
self::assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected'); |
|
689
|
|
|
|
|
690
|
|
|
$proxyName = $this->generateProxy(get_class($instance)); |
|
691
|
|
|
|
|
692
|
|
|
/** @var ClassWithCounterConstructor $proxy */ |
|
693
|
|
|
$proxy = new $proxyName(15); |
|
694
|
|
|
|
|
695
|
|
|
self::assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected'); |
|
696
|
|
|
self::assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected'); |
|
697
|
|
|
$proxy->__construct(5); |
|
698
|
|
|
self::assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected'); |
|
699
|
|
|
self::assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected'); |
|
700
|
|
|
} |
|
701
|
|
|
|
|
702
|
|
|
public function testInitializeProxyWillReturnTrueOnSuccessfulInitialization() : void |
|
703
|
|
|
{ |
|
704
|
|
|
$proxyName = $this->generateProxy(ClassWithMixedTypedProperties::class); |
|
705
|
|
|
|
|
706
|
|
|
/** @var GhostObjectInterface $proxy */ |
|
707
|
|
|
$proxy = $proxyName::staticProxyConstructor($this->createInitializer( |
|
708
|
|
|
ClassWithMixedTypedProperties::class, |
|
709
|
|
|
new ClassWithMixedTypedProperties() |
|
710
|
|
|
)); |
|
711
|
|
|
|
|
712
|
|
|
self::assertTrue($proxy->initializeProxy()); |
|
713
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
714
|
|
|
self::assertFalse($proxy->initializeProxy()); |
|
715
|
|
|
} |
|
716
|
|
|
|
|
717
|
|
|
/** |
|
718
|
|
|
* Generates a proxy for the given class name, and retrieves its class name |
|
719
|
|
|
* |
|
720
|
|
|
* @param mixed[] $proxyOptions |
|
721
|
|
|
*/ |
|
722
|
|
|
private function generateProxy(string $parentClassName, array $proxyOptions = []) : string |
|
723
|
|
|
{ |
|
724
|
|
|
$generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); |
|
725
|
|
|
$generatedClass = new ClassGenerator($generatedClassName); |
|
726
|
|
|
|
|
727
|
|
|
(new LazyLoadingGhostGenerator())->generate( |
|
728
|
|
|
new ReflectionClass($parentClassName), |
|
729
|
|
|
$generatedClass, |
|
730
|
|
|
$proxyOptions |
|
731
|
|
|
); |
|
732
|
|
|
(new EvaluatingGeneratorStrategy())->generate($generatedClass); |
|
733
|
|
|
|
|
734
|
|
|
return $generatedClassName; |
|
735
|
|
|
} |
|
736
|
|
|
|
|
737
|
|
|
private function createInitializer(string $className, object $realInstance, ?Mock $initializerMatcher = null |
|
738
|
|
|
) : callable |
|
739
|
|
|
{ |
|
740
|
|
|
/** @var callable|Mock $initializerMatcher */ |
|
741
|
|
|
if (! $initializerMatcher) { |
|
742
|
|
|
$initializerMatcher = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock(); |
|
743
|
|
|
|
|
744
|
|
|
$initializerMatcher |
|
745
|
|
|
->expects(self::once()) |
|
746
|
|
|
->method('__invoke') |
|
747
|
|
|
->with(self::logicalAnd( |
|
748
|
|
|
self::isInstanceOf(GhostObjectInterface::class), |
|
749
|
|
|
self::isInstanceOf($className) |
|
750
|
|
|
)); |
|
751
|
|
|
; |
|
752
|
|
|
} |
|
753
|
|
|
|
|
754
|
|
|
self::assertInternalType('callable', $initializerMatcher); |
|
|
|
|
|
|
755
|
|
|
|
|
756
|
|
|
return function ( |
|
757
|
|
|
GhostObjectInterface $proxy, |
|
758
|
|
|
$method, |
|
759
|
|
|
$params, |
|
760
|
|
|
& $initializer |
|
761
|
|
|
) use ( |
|
762
|
|
|
$initializerMatcher, |
|
763
|
|
|
$realInstance |
|
764
|
|
|
) : bool { |
|
765
|
|
|
$initializer = null; |
|
766
|
|
|
$reflectionClass = new ReflectionClass($realInstance); |
|
767
|
|
|
|
|
768
|
|
|
foreach (Properties::fromReflectionClass($reflectionClass)->getInstanceProperties() as $property) { |
|
769
|
|
|
if (! self::isPropertyInitialized($realInstance, $property)) { |
|
770
|
|
|
continue; |
|
771
|
|
|
} |
|
772
|
|
|
|
|
773
|
|
|
$property->setAccessible(true); |
|
774
|
|
|
$property->setValue($proxy, $property->getValue($realInstance)); |
|
775
|
|
|
} |
|
776
|
|
|
|
|
777
|
|
|
$initializerMatcher($proxy, $method, $params); |
|
778
|
|
|
|
|
779
|
|
|
return true; |
|
780
|
|
|
}; |
|
781
|
|
|
} |
|
782
|
|
|
|
|
783
|
|
|
/** |
|
784
|
|
|
* Generates a list of object | invoked method | parameters | expected result |
|
785
|
|
|
* |
|
786
|
|
|
* @return null[][]|string[][]|object[][]|mixed[][][] |
|
787
|
|
|
*/ |
|
788
|
|
|
public function getProxyMethods() : array |
|
789
|
|
|
{ |
|
790
|
|
|
$selfHintParam = new ClassWithSelfHint(); |
|
791
|
|
|
$empty = new EmptyClass(); |
|
792
|
|
|
|
|
793
|
|
|
return [ |
|
794
|
|
|
[ |
|
795
|
|
|
BaseClass::class, |
|
796
|
|
|
new BaseClass(), |
|
797
|
|
|
'publicMethod', |
|
798
|
|
|
[], |
|
799
|
|
|
'publicMethodDefault', |
|
800
|
|
|
], |
|
801
|
|
|
[ |
|
802
|
|
|
BaseClass::class, |
|
803
|
|
|
new BaseClass(), |
|
804
|
|
|
'publicTypeHintedMethod', |
|
805
|
|
|
[new stdClass()], |
|
806
|
|
|
'publicTypeHintedMethodDefault', |
|
807
|
|
|
], |
|
808
|
|
|
[ |
|
809
|
|
|
BaseClass::class, |
|
810
|
|
|
new BaseClass(), |
|
811
|
|
|
'publicByReferenceMethod', |
|
812
|
|
|
[], |
|
813
|
|
|
'publicByReferenceMethodDefault', |
|
814
|
|
|
], |
|
815
|
|
|
[ |
|
816
|
|
|
ClassWithSelfHint::class, |
|
817
|
|
|
new ClassWithSelfHint(), |
|
818
|
|
|
'selfHintMethod', |
|
819
|
|
|
['parameter' => $selfHintParam], |
|
820
|
|
|
$selfHintParam, |
|
821
|
|
|
], |
|
822
|
|
|
[ |
|
823
|
|
|
ClassWithParentHint::class, |
|
824
|
|
|
new ClassWithParentHint(), |
|
825
|
|
|
'parentHintMethod', |
|
826
|
|
|
['parameter' => $empty], |
|
827
|
|
|
$empty, |
|
828
|
|
|
], |
|
829
|
|
|
[ |
|
830
|
|
|
ClassWithAbstractPublicMethod::class, |
|
831
|
|
|
new EmptyClass(), // EmptyClass just used to not make reflection explode when synchronizing properties |
|
832
|
|
|
'publicAbstractMethod', |
|
833
|
|
|
[], |
|
834
|
|
|
null, |
|
835
|
|
|
], |
|
836
|
|
|
[ |
|
837
|
|
|
ClassWithMethodWithByRefVariadicFunction::class, |
|
838
|
|
|
new ClassWithMethodWithByRefVariadicFunction(), |
|
839
|
|
|
'tuz', |
|
840
|
|
|
['Ocramius', 'Malukenho'], |
|
841
|
|
|
['Ocramius', 'changed'], |
|
842
|
|
|
], |
|
843
|
|
|
]; |
|
844
|
|
|
} |
|
845
|
|
|
|
|
846
|
|
|
/** |
|
847
|
|
|
* Generates a list of object | invoked method | parameters | expected result for methods that cause lazy-loading |
|
848
|
|
|
* of a ghost object |
|
849
|
|
|
* |
|
850
|
|
|
* @return string[][]|object[][]|mixed[][][]|null[][] |
|
851
|
|
|
*/ |
|
852
|
|
|
public function getProxyInitializingMethods() : array |
|
853
|
|
|
{ |
|
854
|
|
|
return [ |
|
855
|
|
|
[ |
|
856
|
|
|
BaseClass::class, |
|
857
|
|
|
new BaseClass(), |
|
858
|
|
|
'publicPropertyGetter', |
|
859
|
|
|
[], |
|
860
|
|
|
'publicPropertyDefault', |
|
861
|
|
|
], |
|
862
|
|
|
[ |
|
863
|
|
|
BaseClass::class, |
|
864
|
|
|
new BaseClass(), |
|
865
|
|
|
'protectedPropertyGetter', |
|
866
|
|
|
[], |
|
867
|
|
|
'protectedPropertyDefault', |
|
868
|
|
|
], |
|
869
|
|
|
[ |
|
870
|
|
|
BaseClass::class, |
|
871
|
|
|
new BaseClass(), |
|
872
|
|
|
'privatePropertyGetter', |
|
873
|
|
|
[], |
|
874
|
|
|
'privatePropertyDefault', |
|
875
|
|
|
], |
|
876
|
|
|
[ |
|
877
|
|
|
ClassWithMethodWithVariadicFunction::class, |
|
878
|
|
|
new ClassWithMethodWithVariadicFunction(), |
|
879
|
|
|
'foo', |
|
880
|
|
|
['Ocramius', 'Malukenho'], |
|
881
|
|
|
null, |
|
882
|
|
|
], |
|
883
|
|
|
]; |
|
884
|
|
|
} |
|
885
|
|
|
|
|
886
|
|
|
/** |
|
887
|
|
|
* Generates a list of object | invoked method | parameters | expected result for methods DON'T cause lazy-loading |
|
888
|
|
|
* |
|
889
|
|
|
* @return null[][]|string[][]|object[][]|mixed[][][] |
|
890
|
|
|
*/ |
|
891
|
|
|
public function getProxyNonInitializingMethods() : array |
|
892
|
|
|
{ |
|
893
|
|
|
return $this->getProxyMethods(); |
|
894
|
|
|
} |
|
895
|
|
|
|
|
896
|
|
|
/** |
|
897
|
|
|
* Generates proxies and instances with a public property to feed to the property accessor methods |
|
898
|
|
|
* |
|
899
|
|
|
* @return string[][]|object[][] |
|
900
|
|
|
*/ |
|
901
|
|
|
public function getPropertyAccessProxies() : array |
|
902
|
|
|
{ |
|
903
|
|
|
$instance1 = new BaseClass(); |
|
904
|
|
|
$proxyName1 = $this->generateProxy(get_class($instance1)); |
|
905
|
|
|
$instance2 = new BaseClass(); |
|
906
|
|
|
$proxyName2 = $this->generateProxy(get_class($instance2)); |
|
907
|
|
|
|
|
908
|
|
|
return [ |
|
909
|
|
|
[ |
|
910
|
|
|
$instance1, |
|
911
|
|
|
new $proxyName1($this->createInitializer(BaseClass::class, $instance1)), |
|
|
|
|
|
|
912
|
|
|
'publicProperty', |
|
913
|
|
|
'publicPropertyDefault', |
|
914
|
|
|
], |
|
915
|
|
|
[ |
|
916
|
|
|
$instance2, |
|
917
|
|
|
unserialize( |
|
918
|
|
|
serialize(new $proxyName2($this->createInitializer(BaseClass::class, $instance2))) |
|
|
|
|
|
|
919
|
|
|
), |
|
920
|
|
|
'publicProperty', |
|
921
|
|
|
'publicPropertyDefault', |
|
922
|
|
|
], |
|
923
|
|
|
]; |
|
924
|
|
|
} |
|
925
|
|
|
|
|
926
|
|
|
/** |
|
927
|
|
|
* @dataProvider skipPropertiesFixture |
|
928
|
|
|
* |
|
929
|
|
|
* @param mixed $expected |
|
930
|
|
|
* @param mixed[] $proxyOptions |
|
931
|
|
|
*/ |
|
932
|
|
|
public function testInitializationIsSkippedForSkippedProperties( |
|
933
|
|
|
string $className, |
|
934
|
|
|
string $propertyClass, |
|
935
|
|
|
string $propertyName, |
|
936
|
|
|
array $proxyOptions, |
|
937
|
|
|
$expected |
|
938
|
|
|
) : void { |
|
939
|
|
|
$proxy = $this->generateProxy($className, $proxyOptions); |
|
940
|
|
|
$ghostObject = $proxy::staticProxyConstructor(function () use ($propertyName) : void { |
|
941
|
|
|
self::fail(sprintf('The Property "%s" was not expected to be lazy-loaded', $propertyName)); |
|
942
|
|
|
}); |
|
943
|
|
|
|
|
944
|
|
|
$property = new ReflectionProperty($propertyClass, $propertyName); |
|
945
|
|
|
$property->setAccessible(true); |
|
946
|
|
|
|
|
947
|
|
|
self::assertSame($expected, $property->getValue($ghostObject)); |
|
948
|
|
|
} |
|
949
|
|
|
|
|
950
|
|
|
/** |
|
951
|
|
|
* @dataProvider skipPropertiesFixture |
|
952
|
|
|
* |
|
953
|
|
|
* @param mixed[] $proxyOptions |
|
954
|
|
|
*/ |
|
955
|
|
|
public function testSkippedPropertiesAreNotOverwrittenOnInitialization( |
|
956
|
|
|
string $className, |
|
957
|
|
|
string $propertyClass, |
|
958
|
|
|
string $propertyName, |
|
959
|
|
|
array $proxyOptions |
|
960
|
|
|
) : void { |
|
961
|
|
|
$proxyName = $this->generateProxy($className, $proxyOptions); |
|
962
|
|
|
/** @var GhostObjectInterface $ghostObject */ |
|
963
|
|
|
$ghostObject = $proxyName::staticProxyConstructor( |
|
964
|
|
|
function ($proxy, string $method, $params, & $initializer) : bool { |
|
965
|
|
|
$initializer = null; |
|
966
|
|
|
|
|
967
|
|
|
return true; |
|
968
|
|
|
} |
|
969
|
|
|
); |
|
970
|
|
|
|
|
971
|
|
|
$property = new ReflectionProperty($propertyClass, $propertyName); |
|
972
|
|
|
|
|
973
|
|
|
$property->setAccessible(true); |
|
974
|
|
|
|
|
975
|
|
|
$value = uniqid('', true); |
|
976
|
|
|
|
|
977
|
|
|
$property->setValue($ghostObject, $value); |
|
978
|
|
|
|
|
979
|
|
|
self::assertTrue($ghostObject->initializeProxy()); |
|
980
|
|
|
|
|
981
|
|
|
self::assertSame( |
|
982
|
|
|
$value, |
|
983
|
|
|
$property->getValue($ghostObject), |
|
984
|
|
|
'Property should not be changed by proxy initialization' |
|
985
|
|
|
); |
|
986
|
|
|
} |
|
987
|
|
|
|
|
988
|
|
|
/** |
|
989
|
|
|
* @group 265 |
|
990
|
|
|
*/ |
|
991
|
|
|
public function testWillForwardVariadicByRefArguments() : void |
|
992
|
|
|
{ |
|
993
|
|
|
$proxyName = $this->generateProxy(ClassWithMethodWithByRefVariadicFunction::class); |
|
994
|
|
|
/** @var ClassWithMethodWithByRefVariadicFunction $object */ |
|
995
|
|
|
$object = $proxyName::staticProxyConstructor(function ($proxy, string $method, $params, & $initializer) : bool { |
|
996
|
|
|
$initializer = null; |
|
997
|
|
|
|
|
998
|
|
|
return true; |
|
999
|
|
|
}); |
|
1000
|
|
|
|
|
1001
|
|
|
$parameters = ['a', 'b', 'c']; |
|
1002
|
|
|
|
|
1003
|
|
|
// first, testing normal variadic behavior (verifying we didn't screw up in the test asset) |
|
1004
|
|
|
self::assertSame(['a', 'changed', 'c'], (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$parameters)); |
|
1005
|
|
|
self::assertSame(['a', 'changed', 'c'], $object->tuz(...$parameters)); |
|
1006
|
|
|
self::assertSame(['a', 'changed', 'c'], $parameters, 'by-ref variadic parameter was changed'); |
|
1007
|
|
|
} |
|
1008
|
|
|
|
|
1009
|
|
|
/** |
|
1010
|
|
|
* @group 265 |
|
1011
|
|
|
*/ |
|
1012
|
|
|
public function testWillForwardDynamicArguments() : void |
|
1013
|
|
|
{ |
|
1014
|
|
|
$proxyName = $this->generateProxy(ClassWithDynamicArgumentsMethod::class); |
|
1015
|
|
|
/** @var ClassWithDynamicArgumentsMethod $object */ |
|
1016
|
|
|
$object = $proxyName::staticProxyConstructor(function () : void { |
|
1017
|
|
|
}); |
|
1018
|
|
|
|
|
1019
|
|
|
// first, testing normal variadic behavior (verifying we didn't screw up in the test asset) |
|
1020
|
|
|
self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b')); |
|
1021
|
|
|
self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b')); |
|
1022
|
|
|
} |
|
1023
|
|
|
|
|
1024
|
|
|
/** |
|
1025
|
|
|
* @return mixed[] in order: |
|
1026
|
|
|
* - the class to be proxied |
|
1027
|
|
|
* - the class owning the property to be checked |
|
1028
|
|
|
* - the property name |
|
1029
|
|
|
* - the options to be passed to the generator |
|
1030
|
|
|
* - the expected value of the property |
|
1031
|
|
|
*/ |
|
1032
|
|
|
public function skipPropertiesFixture() : array |
|
1033
|
|
|
{ |
|
1034
|
|
|
return [ |
|
1035
|
|
|
[ |
|
1036
|
|
|
ClassWithPublicProperties::class, |
|
1037
|
|
|
ClassWithPublicProperties::class, |
|
1038
|
|
|
'property9', |
|
1039
|
|
|
[ |
|
1040
|
|
|
'skippedProperties' => ['property9'], |
|
1041
|
|
|
], |
|
1042
|
|
|
'property9', |
|
1043
|
|
|
], |
|
1044
|
|
|
[ |
|
1045
|
|
|
ClassWithProtectedProperties::class, |
|
1046
|
|
|
ClassWithProtectedProperties::class, |
|
1047
|
|
|
'property9', |
|
1048
|
|
|
[ |
|
1049
|
|
|
'skippedProperties' => ["\0*\0property9"], |
|
1050
|
|
|
], |
|
1051
|
|
|
'property9', |
|
1052
|
|
|
], |
|
1053
|
|
|
[ |
|
1054
|
|
|
ClassWithPrivateProperties::class, |
|
1055
|
|
|
ClassWithPrivateProperties::class, |
|
1056
|
|
|
'property9', |
|
1057
|
|
|
[ |
|
1058
|
|
|
'skippedProperties' => ["\0ProxyManagerTestAsset\\ClassWithPrivateProperties\0property9"], |
|
1059
|
|
|
], |
|
1060
|
|
|
'property9', |
|
1061
|
|
|
], |
|
1062
|
|
|
[ |
|
1063
|
|
|
ClassWithCollidingPrivateInheritedProperties::class, |
|
1064
|
|
|
ClassWithCollidingPrivateInheritedProperties::class, |
|
1065
|
|
|
'property0', |
|
1066
|
|
|
[ |
|
1067
|
|
|
'skippedProperties' => ["\0ProxyManagerTestAsset\\ClassWithCollidingPrivateInheritedProperties\0property0"], |
|
1068
|
|
|
], |
|
1069
|
|
|
'childClassProperty0', |
|
1070
|
|
|
], |
|
1071
|
|
|
[ |
|
1072
|
|
|
ClassWithCollidingPrivateInheritedProperties::class, |
|
1073
|
|
|
ClassWithPrivateProperties::class, |
|
1074
|
|
|
'property0', |
|
1075
|
|
|
[ |
|
1076
|
|
|
'skippedProperties' => ["\0ProxyManagerTestAsset\\ClassWithPrivateProperties\0property0"], |
|
1077
|
|
|
], |
|
1078
|
|
|
'property0', |
|
1079
|
|
|
], |
|
1080
|
|
|
]; |
|
1081
|
|
|
} |
|
1082
|
|
|
|
|
1083
|
|
|
/** |
|
1084
|
|
|
* @group 276 |
|
1085
|
|
|
* |
|
1086
|
|
|
* @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope |
|
1087
|
|
|
* |
|
1088
|
|
|
*/ |
|
1089
|
|
|
public function testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope( |
|
1090
|
|
|
object $callerObject, |
|
1091
|
|
|
string $method, |
|
1092
|
|
|
string $propertyIndex, |
|
1093
|
|
|
string $expectedValue |
|
1094
|
|
|
) : void { |
|
1095
|
|
|
$proxyName = $this->generateProxy(get_class($callerObject)); |
|
1096
|
|
|
/** @var OtherObjectAccessClass|LazyLoadingInterface $proxy */ |
|
1097
|
|
|
$proxy = $proxyName::staticProxyConstructor( |
|
1098
|
|
|
function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue |
|
1099
|
|
|
) : void { |
|
1100
|
|
|
$initializer = null; |
|
1101
|
|
|
|
|
1102
|
|
|
$props[$propertyIndex] = $expectedValue; |
|
1103
|
|
|
} |
|
1104
|
|
|
); |
|
1105
|
|
|
|
|
1106
|
|
|
self::assertInstanceOf(LazyLoadingInterface::class, $proxy); |
|
1107
|
|
|
|
|
1108
|
|
|
$accessor = [$callerObject, $method]; |
|
1109
|
|
|
|
|
1110
|
|
|
self::assertInternalType('callable', $accessor); |
|
|
|
|
|
|
1111
|
|
|
|
|
1112
|
|
|
self::assertFalse($proxy->isProxyInitialized()); |
|
1113
|
|
|
self::assertSame($expectedValue, $accessor($proxy)); |
|
1114
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
1115
|
|
|
} |
|
1116
|
|
|
|
|
1117
|
|
|
/** |
|
1118
|
|
|
* @group 276 |
|
1119
|
|
|
* |
|
1120
|
|
|
* @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope |
|
1121
|
|
|
* |
|
1122
|
|
|
*/ |
|
1123
|
|
|
public function testWillAccessMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope( |
|
1124
|
|
|
object $callerObject, |
|
1125
|
|
|
string $method, |
|
1126
|
|
|
string $propertyIndex, |
|
1127
|
|
|
string $expectedValue |
|
1128
|
|
|
) : void { |
|
1129
|
|
|
$proxyName = $this->generateProxy(get_class($callerObject)); |
|
1130
|
|
|
/** @var OtherObjectAccessClass|LazyLoadingInterface $proxy */ |
|
1131
|
|
|
$proxy = unserialize(serialize($proxyName::staticProxyConstructor( |
|
1132
|
|
|
function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue |
|
1133
|
|
|
) : void { |
|
1134
|
|
|
$initializer = null; |
|
1135
|
|
|
|
|
1136
|
|
|
$props[$propertyIndex] = $expectedValue; |
|
1137
|
|
|
} |
|
1138
|
|
|
))); |
|
1139
|
|
|
|
|
1140
|
|
|
self::assertInstanceOf(LazyLoadingInterface::class, $proxy); |
|
1141
|
|
|
|
|
1142
|
|
|
$accessor = [$callerObject, $method]; |
|
1143
|
|
|
|
|
1144
|
|
|
self::assertInternalType('callable', $accessor); |
|
|
|
|
|
|
1145
|
|
|
|
|
1146
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
1147
|
|
|
self::assertSame($expectedValue, $accessor($proxy)); |
|
1148
|
|
|
} |
|
1149
|
|
|
|
|
1150
|
|
|
/** |
|
1151
|
|
|
* @group 276 |
|
1152
|
|
|
* |
|
1153
|
|
|
* @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope |
|
1154
|
|
|
* |
|
1155
|
|
|
*/ |
|
1156
|
|
|
public function testWillAccessMembersOfOtherClonedProxiesWithTheSamePrivateScope( |
|
1157
|
|
|
object $callerObject, |
|
1158
|
|
|
string $method, |
|
1159
|
|
|
string $propertyIndex, |
|
1160
|
|
|
string $expectedValue |
|
1161
|
|
|
) : void { |
|
1162
|
|
|
$proxyName = $this->generateProxy(get_class($callerObject)); |
|
1163
|
|
|
/** @var OtherObjectAccessClass|LazyLoadingInterface $proxy */ |
|
1164
|
|
|
$proxy = clone $proxyName::staticProxyConstructor( |
|
1165
|
|
|
function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue |
|
1166
|
|
|
) : void { |
|
1167
|
|
|
$initializer = null; |
|
1168
|
|
|
|
|
1169
|
|
|
$props[$propertyIndex] = $expectedValue; |
|
1170
|
|
|
} |
|
1171
|
|
|
); |
|
1172
|
|
|
|
|
1173
|
|
|
self::assertInstanceOf(LazyLoadingInterface::class, $proxy); |
|
1174
|
|
|
|
|
1175
|
|
|
$accessor = [$callerObject, $method]; |
|
1176
|
|
|
|
|
1177
|
|
|
self::assertInternalType('callable', $accessor); |
|
|
|
|
|
|
1178
|
|
|
|
|
1179
|
|
|
self::assertTrue($proxy->isProxyInitialized()); |
|
1180
|
|
|
self::assertSame($expectedValue, $accessor($proxy)); |
|
1181
|
|
|
} |
|
1182
|
|
|
|
|
1183
|
|
|
/** @return string[][]|object[][] */ |
|
1184
|
|
|
public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : array |
|
1185
|
|
|
{ |
|
1186
|
|
|
$proxyClass = $this->generateProxy(OtherObjectAccessClass::class); |
|
1187
|
|
|
|
|
1188
|
|
|
return [ |
|
1189
|
|
|
OtherObjectAccessClass::class . '#$privateProperty' => [ |
|
1190
|
|
|
new OtherObjectAccessClass(), |
|
1191
|
|
|
'getPrivateProperty', |
|
1192
|
|
|
"\0" . OtherObjectAccessClass::class . "\0privateProperty", |
|
1193
|
|
|
uniqid('', true), |
|
1194
|
|
|
], |
|
1195
|
|
|
OtherObjectAccessClass::class . '#$protectedProperty' => [ |
|
1196
|
|
|
new OtherObjectAccessClass(), |
|
1197
|
|
|
'getProtectedProperty', |
|
1198
|
|
|
"\0*\0protectedProperty", |
|
1199
|
|
|
uniqid('', true), |
|
1200
|
|
|
], |
|
1201
|
|
|
OtherObjectAccessClass::class . '#$publicProperty' => [ |
|
1202
|
|
|
new OtherObjectAccessClass(), |
|
1203
|
|
|
'getPublicProperty', |
|
1204
|
|
|
'publicProperty', |
|
1205
|
|
|
uniqid('', true), |
|
1206
|
|
|
], |
|
1207
|
|
|
'(proxy) ' . OtherObjectAccessClass::class . '#$privateProperty' => [ |
|
1208
|
|
|
$proxyClass::staticProxyConstructor(function () : void { |
|
1209
|
|
|
self::fail('Should never be initialized, as its values aren\'t accessed'); |
|
1210
|
|
|
}), |
|
1211
|
|
|
'getPrivateProperty', |
|
1212
|
|
|
"\0" . OtherObjectAccessClass::class . "\0privateProperty", |
|
1213
|
|
|
uniqid('', true), |
|
1214
|
|
|
], |
|
1215
|
|
|
'(proxy) ' . OtherObjectAccessClass::class . '#$protectedProperty' => [ |
|
1216
|
|
|
$proxyClass::staticProxyConstructor(function () : void { |
|
1217
|
|
|
self::fail('Should never be initialized, as its values aren\'t accessed'); |
|
1218
|
|
|
}), |
|
1219
|
|
|
'getProtectedProperty', |
|
1220
|
|
|
"\0*\0protectedProperty", |
|
1221
|
|
|
uniqid('', true), |
|
1222
|
|
|
], |
|
1223
|
|
|
'(proxy) ' . OtherObjectAccessClass::class . '#$publicProperty' => [ |
|
1224
|
|
|
$proxyClass::staticProxyConstructor(function () : void { |
|
1225
|
|
|
self::fail('Should never be initialized, as its values aren\'t accessed'); |
|
1226
|
|
|
}), |
|
1227
|
|
|
'getPublicProperty', |
|
1228
|
|
|
'publicProperty', |
|
1229
|
|
|
uniqid('', true), |
|
1230
|
|
|
], |
|
1231
|
|
|
]; |
|
1232
|
|
|
} |
|
1233
|
|
|
|
|
1234
|
|
|
/** |
|
1235
|
|
|
* @group 276 |
|
1236
|
|
|
*/ |
|
1237
|
|
|
public function testFriendObjectWillNotCauseLazyLoadingOnSkippedProperty() : void |
|
1238
|
|
|
{ |
|
1239
|
|
|
$proxyName = $this->generateProxy( |
|
1240
|
|
|
OtherObjectAccessClass::class, |
|
1241
|
|
|
[ |
|
1242
|
|
|
'skippedProperties' => [ |
|
1243
|
|
|
"\0" . OtherObjectAccessClass::class . "\0privateProperty", |
|
1244
|
|
|
"\0*\0protectedProperty", |
|
1245
|
|
|
'publicProperty', |
|
1246
|
|
|
], |
|
1247
|
|
|
] |
|
1248
|
|
|
); |
|
1249
|
|
|
|
|
1250
|
|
|
/** @var OtherObjectAccessClass|LazyLoadingInterface $proxy */ |
|
1251
|
|
|
$proxy = $proxyName::staticProxyConstructor(function () : void { |
|
1252
|
|
|
throw new \BadMethodCallException('The proxy should never be initialized, as all properties are skipped'); |
|
1253
|
|
|
}); |
|
1254
|
|
|
|
|
1255
|
|
|
self::assertInstanceOf(OtherObjectAccessClass::class, $proxy); |
|
1256
|
|
|
self::assertInstanceOf(LazyLoadingInterface::class, $proxy); |
|
1257
|
|
|
|
|
1258
|
|
|
$privatePropertyValue = uniqid('', true); |
|
1259
|
|
|
$protectedPropertyValue = uniqid('', true); |
|
1260
|
|
|
$publicPropertyValue = uniqid('', true); |
|
1261
|
|
|
|
|
1262
|
|
|
$reflectionPrivateProperty = new \ReflectionProperty(OtherObjectAccessClass::class, 'privateProperty'); |
|
1263
|
|
|
|
|
1264
|
|
|
$reflectionPrivateProperty->setAccessible(true); |
|
1265
|
|
|
$reflectionPrivateProperty->setValue($proxy, $privatePropertyValue); |
|
1266
|
|
|
|
|
1267
|
|
|
$reflectionProtectedProperty = new \ReflectionProperty(OtherObjectAccessClass::class, 'protectedProperty'); |
|
1268
|
|
|
|
|
1269
|
|
|
$reflectionProtectedProperty->setAccessible(true); |
|
1270
|
|
|
$reflectionProtectedProperty->setValue($proxy, $protectedPropertyValue); |
|
1271
|
|
|
|
|
1272
|
|
|
$proxy->publicProperty = $publicPropertyValue; |
|
1273
|
|
|
|
|
1274
|
|
|
$friendObject = new OtherObjectAccessClass(); |
|
1275
|
|
|
|
|
1276
|
|
|
self::assertInstanceOf(OtherObjectAccessClass::class, $proxy); |
|
1277
|
|
|
|
|
1278
|
|
|
if (! ($proxy instanceof OtherObjectAccessClass)) { |
|
1279
|
|
|
return; |
|
1280
|
|
|
} |
|
1281
|
|
|
|
|
1282
|
|
|
self::assertSame($privatePropertyValue, $friendObject->getPrivateProperty($proxy)); |
|
|
|
|
|
|
1283
|
|
|
self::assertSame($protectedPropertyValue, $friendObject->getProtectedProperty($proxy)); |
|
|
|
|
|
|
1284
|
|
|
self::assertSame($publicPropertyValue, $friendObject->getPublicProperty($proxy)); |
|
|
|
|
|
|
1285
|
|
|
} |
|
1286
|
|
|
|
|
1287
|
|
|
public function testClonedSkippedPropertiesArePreserved() : void |
|
1288
|
|
|
{ |
|
1289
|
|
|
$proxyName = $this->generateProxy( |
|
1290
|
|
|
BaseClass::class, |
|
1291
|
|
|
[ |
|
1292
|
|
|
'skippedProperties' => [ |
|
1293
|
|
|
"\0" . BaseClass::class . "\0privateProperty", |
|
1294
|
|
|
"\0*\0protectedProperty", |
|
1295
|
|
|
'publicProperty', |
|
1296
|
|
|
], |
|
1297
|
|
|
] |
|
1298
|
|
|
); |
|
1299
|
|
|
|
|
1300
|
|
|
/** @var BaseClass|GhostObjectInterface $proxy */ |
|
1301
|
|
|
$proxy = $proxyName::staticProxyConstructor(function ($proxy) : void { |
|
1302
|
|
|
$proxy->setProxyInitializer(null); |
|
1303
|
|
|
}); |
|
1304
|
|
|
|
|
1305
|
|
|
self::assertInstanceOf(BaseClass::class, $proxy); |
|
1306
|
|
|
self::assertInstanceOf(LazyLoadingInterface::class, $proxy); |
|
1307
|
|
|
|
|
1308
|
|
|
$reflectionPrivate = new \ReflectionProperty(BaseClass::class, 'privateProperty'); |
|
1309
|
|
|
$reflectionProtected = new \ReflectionProperty(BaseClass::class, 'protectedProperty'); |
|
1310
|
|
|
|
|
1311
|
|
|
$reflectionPrivate->setAccessible(true); |
|
1312
|
|
|
$reflectionProtected->setAccessible(true); |
|
1313
|
|
|
|
|
1314
|
|
|
$privateValue = uniqid('', true); |
|
1315
|
|
|
$protectedValue = uniqid('', true); |
|
1316
|
|
|
$publicValue = uniqid('', true); |
|
1317
|
|
|
|
|
1318
|
|
|
$reflectionPrivate->setValue($proxy, $privateValue); |
|
1319
|
|
|
$reflectionProtected->setValue($proxy, $protectedValue); |
|
1320
|
|
|
$proxy->publicProperty = $publicValue; |
|
1321
|
|
|
|
|
1322
|
|
|
self::assertFalse($proxy->isProxyInitialized()); |
|
|
|
|
|
|
1323
|
|
|
|
|
1324
|
|
|
$clone = clone $proxy; |
|
1325
|
|
|
|
|
1326
|
|
|
self::assertFalse($proxy->isProxyInitialized()); |
|
1327
|
|
|
self::assertTrue($clone->isProxyInitialized()); |
|
1328
|
|
|
|
|
1329
|
|
|
self::assertSame($privateValue, $reflectionPrivate->getValue($proxy)); |
|
1330
|
|
|
self::assertSame($privateValue, $reflectionPrivate->getValue($clone)); |
|
1331
|
|
|
self::assertSame($protectedValue, $reflectionProtected->getValue($proxy)); |
|
1332
|
|
|
self::assertSame($protectedValue, $reflectionProtected->getValue($clone)); |
|
1333
|
|
|
self::assertSame($publicValue, $proxy->publicProperty); |
|
1334
|
|
|
self::assertSame($publicValue, $clone->publicProperty); |
|
1335
|
|
|
} |
|
1336
|
|
|
|
|
1337
|
|
|
/** |
|
1338
|
|
|
* @group 327 |
|
1339
|
|
|
*/ |
|
1340
|
|
|
public function testWillExecuteLogicInAVoidMethod() : void |
|
1341
|
|
|
{ |
|
1342
|
|
|
$proxyName = $this->generateProxy(VoidCounter::class); |
|
1343
|
|
|
|
|
1344
|
|
|
$initialCounter = random_int(10, 1000); |
|
1345
|
|
|
|
|
1346
|
|
|
/** @var VoidCounter|LazyLoadingInterface $proxy */ |
|
1347
|
|
|
$proxy = $proxyName::staticProxyConstructor( |
|
1348
|
|
|
function (VoidCounter $proxy, $method, $params, & $initializer, array $props) use ($initialCounter) : bool { |
|
1349
|
|
|
$initializer = null; |
|
1350
|
|
|
|
|
1351
|
|
|
$props['counter'] = $initialCounter; |
|
1352
|
|
|
|
|
1353
|
|
|
return true; |
|
1354
|
|
|
} |
|
1355
|
|
|
); |
|
1356
|
|
|
|
|
1357
|
|
|
self::assertInstanceOf(VoidCounter::class, $proxy); |
|
1358
|
|
|
self::assertInstanceOf(LazyLoadingInterface::class, $proxy); |
|
1359
|
|
|
|
|
1360
|
|
|
$increment = random_int(1001, 10000); |
|
1361
|
|
|
|
|
1362
|
|
|
$proxy->increment($increment); |
|
|
|
|
|
|
1363
|
|
|
|
|
1364
|
|
|
self::assertSame($initialCounter + $increment, $proxy->counter); |
|
1365
|
|
|
} |
|
1366
|
|
|
|
|
1367
|
|
|
private static function isPropertyInitialized(object $object, ReflectionProperty $property) : bool |
|
1368
|
|
|
{ |
|
1369
|
|
|
return array_key_exists( |
|
1370
|
|
|
($property->isProtected() ? "\0*\0" : '') |
|
1371
|
|
|
. ($property->isPrivate() ? "\0" . $property->getDeclaringClass()->getName() . "\0" : '') |
|
1372
|
|
|
. $property->getName(), |
|
1373
|
|
|
(array) $object |
|
1374
|
|
|
); |
|
1375
|
|
|
} |
|
1376
|
|
|
} |
|
1377
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.