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