Completed
Push — master ( 9f8061...f01068 )
by Marco
13s
created

LazyLoadingValueHolderFunctionalTest   B

Complexity

Total Complexity 30

Size/Duplication

Total Lines 679
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 0
Metric Value
dl 0
loc 679
c 0
b 0
f 0
wmc 30
lcom 1
cbo 18
rs 7.3866

25 Methods

Rating   Name   Duplication   Size   Complexity  
A testMethodCalls() 0 17 1
B testMethodCallsAfterUnSerialization() 0 25 1
B testMethodCallsAfterCloning() 0 25 1
A testPropertyReadAccess() 0 10 1
A testPropertyWriteAccess() 0 9 1
A testPropertyExistence() 0 6 1
A testPropertyAbsence() 0 7 2
A testPropertyUnset() 0 10 2
A testCanWriteToArrayKeysInPublicProperty() 0 17 1
A testWillNotModifyRetrievedPublicProperties() 0 17 1
A testWillModifyByRefRetrievedPublicProperties() 0 17 1
A testWillAllowMultipleProxyInitialization() 0 16 1
A testWillBehaveLikeObjectWithNormalConstructor() 0 21 1
A testWillForwardVariadicByRefArguments() 0 15 1
A testWillNotForwardDynamicArguments() 0 15 1
A generateProxy() 0 12 1
B createInitializer() 0 34 2
B getProxyMethods() 0 99 1
B getPropertyAccessProxies() 0 26 1
A testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope() 0 17 1
A testWillFetchMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope() 0 18 1
A testWillFetchMembersOfOtherClonedProxiesWithTheSamePrivateScope() 0 18 1
A testWillExecuteLogicInAVoidMethod() 0 12 1
B getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() 0 30 2
A buildInstanceWithValues() 0 12 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ProxyManagerTest\Functional;
6
7
use PHPUnit\Framework\ExpectationFailedException;
8
use PHPUnit_Framework_MockObject_MockObject as Mock;
9
use PHPUnit\Framework\TestCase;
10
use ProxyManager\Generator\ClassGenerator;
11
use ProxyManager\Generator\Util\UniqueIdentifierGenerator;
12
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
13
use ProxyManager\Proxy\LazyLoadingInterface;
14
use ProxyManager\Proxy\VirtualProxyInterface;
15
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator;
16
use ProxyManagerTestAsset\BaseClass;
17
use ProxyManagerTestAsset\BaseInterface;
18
use ProxyManagerTestAsset\ClassWithCounterConstructor;
19
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod;
20
use ProxyManagerTestAsset\ClassWithMagicMethods;
21
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction;
22
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction;
23
use ProxyManagerTestAsset\ClassWithParentHint;
24
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
25
use ProxyManagerTestAsset\ClassWithPublicProperties;
26
use ProxyManagerTestAsset\ClassWithSelfHint;
27
use ProxyManagerTestAsset\EmptyClass;
28
use ProxyManagerTestAsset\OtherObjectAccessClass;
29
use ProxyManagerTestAsset\VoidCounter;
30
use ReflectionClass;
31
use stdClass;
32
33
/**
34
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator} produced objects
35
 *
36
 * @author Marco Pivetta <[email protected]>
37
 * @license MIT
38
 *
39
 * @group Functional
40
 * @coversNothing
41
 */
42
class LazyLoadingValueHolderFunctionalTest extends TestCase
43
{
44
    /**
45
     * @dataProvider getProxyMethods
46
     *
47
     * @param string  $className
48
     * @param object  $instance
49
     * @param string  $method
50
     * @param mixed[] $params
51
     * @param mixed   $expectedValue
52
     */
53
    public function testMethodCalls(string $className, $instance, string $method, array $params, $expectedValue) : void
54
    {
55
        $proxyName = $this->generateProxy($className);
56
57
        /* @var $proxy VirtualProxyInterface */
58
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
59
60
        self::assertFalse($proxy->isProxyInitialized());
61
62
        /* @var $callProxyMethod callable */
63
        $callProxyMethod = [$proxy, $method];
64
        $parameterValues = array_values($params);
65
66
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
67
        self::assertTrue($proxy->isProxyInitialized());
68
        self::assertSame($instance, $proxy->getWrappedValueHolderValue());
69
    }
70
71
    /**
72
     * @dataProvider getProxyMethods
73
     *
74
     * @param string  $className
75
     * @param object  $instance
76
     * @param string  $method
77
     * @param mixed[] $params
78
     * @param mixed   $expectedValue
79
     */
80
    public function testMethodCallsAfterUnSerialization(
81
        string $className,
82
        $instance,
83
        string $method,
84
        array $params,
85
        $expectedValue
86
    ) : void {
87
        $proxyName = $this->generateProxy($className);
88
89
        /* @var $proxy VirtualProxyInterface */
90
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
91
            $this->createInitializer($className, $instance)
92
        )));
93
94
        self::assertTrue($proxy->isProxyInitialized());
95
96
        /* @var $callProxyMethod callable */
97
        $callProxyMethod = [$proxy, $method];
98
        $parameterValues = array_values($params);
99
100
        self::assertInternalType('callable', $callProxyMethod);
101
102
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
103
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
104
    }
105
106
    /**
107
     * @dataProvider getProxyMethods
108
     *
109
     * @param string  $className
110
     * @param object  $instance
111
     * @param string  $method
112
     * @param mixed[] $params
113
     * @param mixed   $expectedValue
114
     */
115
    public function testMethodCallsAfterCloning(
116
        string $className,
117
        $instance,
118
        string $method,
119
        array $params,
120
        $expectedValue
121
    ) : void {
122
        $proxyName = $this->generateProxy($className);
123
124
        /* @var $proxy VirtualProxyInterface */
125
        $proxy  = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
126
        $cloned = clone $proxy;
127
128
        self::assertTrue($cloned->isProxyInitialized());
129
        self::assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue());
130
131
        /* @var $callProxyMethod callable */
132
        $callProxyMethod = [$cloned, $method];
133
        $parameterValues = array_values($params);
134
135
        self::assertInternalType('callable', $callProxyMethod);
136
137
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
138
        self::assertEquals($instance, $cloned->getWrappedValueHolderValue());
139
    }
140
141
    /**
142
     * @dataProvider getPropertyAccessProxies
143
     *
144
     * @param object                $instance
145
     * @param VirtualProxyInterface $proxy
146
     * @param string                $publicProperty
147
     * @param mixed                 $propertyValue
148
     */
149
    public function testPropertyReadAccess(
150
        $instance,
151
        VirtualProxyInterface $proxy,
152
        string $publicProperty,
153
        $propertyValue
154
    ) : void {
155
        self::assertSame($propertyValue, $proxy->$publicProperty);
156
        self::assertTrue($proxy->isProxyInitialized());
157
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
158
    }
159
160
    /**
161
     * @dataProvider getPropertyAccessProxies
162
     *
163
     * @param object                $instance
164
     * @param VirtualProxyInterface $proxy
165
     * @param string                $publicProperty
166
     */
167
    public function testPropertyWriteAccess($instance, VirtualProxyInterface $proxy, string $publicProperty) : void
168
    {
169
        $newValue               = uniqid();
170
        $proxy->$publicProperty = $newValue;
171
172
        self::assertTrue($proxy->isProxyInitialized());
173
        self::assertSame($newValue, $proxy->$publicProperty);
174
        self::assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty);
175
    }
176
177
    /**
178
     * @dataProvider getPropertyAccessProxies
179
     *
180
     * @param object                $instance
181
     * @param VirtualProxyInterface $proxy
182
     * @param string                $publicProperty
183
     */
184
    public function testPropertyExistence($instance, VirtualProxyInterface $proxy, string $publicProperty) : void
185
    {
186
        self::assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
187
        self::assertTrue($proxy->isProxyInitialized());
188
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
189
    }
190
191
    /**
192
     * @dataProvider getPropertyAccessProxies
193
     *
194
     * @param object                $instance
195
     * @param VirtualProxyInterface $proxy
196
     * @param string                $publicProperty
197
     */
198
    public function testPropertyAbsence($instance, VirtualProxyInterface $proxy, string $publicProperty) : void
199
    {
200
        $instance = $proxy->getWrappedValueHolderValue() ?: $instance;
201
        $instance->$publicProperty = null;
202
        self::assertFalse(isset($proxy->$publicProperty));
203
        self::assertTrue($proxy->isProxyInitialized());
204
    }
205
206
    /**
207
     * @dataProvider getPropertyAccessProxies
208
     *
209
     * @param object                $instance
210
     * @param VirtualProxyInterface $proxy
211
     * @param string                $publicProperty
212
     */
213
    public function testPropertyUnset($instance, VirtualProxyInterface $proxy, string $publicProperty) : void
214
    {
215
        $instance = $proxy->getWrappedValueHolderValue() ?: $instance;
216
        unset($proxy->$publicProperty);
217
218
        self::assertTrue($proxy->isProxyInitialized());
219
220
        self::assertFalse(isset($instance->$publicProperty));
221
        self::assertFalse(isset($proxy->$publicProperty));
222
    }
223
224
    /**
225
     * Verifies that accessing a public property containing an array behaves like in a normal context
226
     */
227
    public function testCanWriteToArrayKeysInPublicProperty() : void
228
    {
229
        $instance    = new ClassWithPublicArrayProperty();
230
        $className   = get_class($instance);
231
        $initializer = $this->createInitializer($className, $instance);
232
        $proxyName   = $this->generateProxy($className);
233
        /* @var $proxy ClassWithPublicArrayProperty */
234
        $proxy       = $proxyName::staticProxyConstructor($initializer);
235
236
        $proxy->arrayProperty['foo'] = 'bar';
237
238
        self::assertSame('bar', $proxy->arrayProperty['foo']);
239
240
        $proxy->arrayProperty = ['tab' => 'taz'];
241
242
        self::assertSame(['tab' => 'taz'], $proxy->arrayProperty);
243
    }
244
245
    /**
246
     * Verifies that public properties retrieved via `__get` don't get modified in the object itself
247
     */
248
    public function testWillNotModifyRetrievedPublicProperties() : void
249
    {
250
        $instance    = new ClassWithPublicProperties();
251
        $className   = get_class($instance);
252
        $initializer = $this->createInitializer($className, $instance);
253
        $proxyName   = $this->generateProxy($className);
254
        /* @var $proxy ClassWithPublicProperties */
255
        $proxy       = $proxyName::staticProxyConstructor($initializer);
256
        $variable    = $proxy->property0;
257
258
        self::assertSame('property0', $variable);
259
260
        $variable = 'foo';
261
262
        self::assertSame('property0', $proxy->property0);
263
        self::assertSame('foo', $variable);
264
    }
265
266
    /**
267
     * Verifies that public properties references retrieved via `__get` modify in the object state
268
     */
269
    public function testWillModifyByRefRetrievedPublicProperties() : void
270
    {
271
        $instance    = new ClassWithPublicProperties();
272
        $className   = get_class($instance);
273
        $initializer = $this->createInitializer($className, $instance);
274
        $proxyName   = $this->generateProxy($className);
275
        /* @var $proxy ClassWithPublicProperties */
276
        $proxy       = $proxyName::staticProxyConstructor($initializer);
277
        $variable    = & $proxy->property0;
278
279
        self::assertSame('property0', $variable);
280
281
        $variable = 'foo';
282
283
        self::assertSame('foo', $proxy->property0);
284
        self::assertSame('foo', $variable);
285
    }
286
287
    /**
288
     * @group 16
289
     *
290
     * Verifies that initialization of a value holder proxy may happen multiple times
291
     */
292
    public function testWillAllowMultipleProxyInitialization() : void
293
    {
294
        $proxyClass  = $this->generateProxy(BaseClass::class);
295
        $counter     = 0;
296
297
        /* @var $proxy BaseClass */
298
        $proxy = $proxyClass::staticProxyConstructor(function (& $wrappedInstance) use (& $counter) {
299
            $wrappedInstance = new BaseClass();
300
301
            $wrappedInstance->publicProperty = (string) ($counter += 1);
302
        });
303
304
        self::assertSame('1', $proxy->publicProperty);
305
        self::assertSame('2', $proxy->publicProperty);
306
        self::assertSame('3', $proxy->publicProperty);
307
    }
308
309
    /**
310
     * @group 115
311
     * @group 175
312
     */
313
    public function testWillBehaveLikeObjectWithNormalConstructor() : void
314
    {
315
        $instance = new ClassWithCounterConstructor(10);
316
317
        self::assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
318
        self::assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
319
        $instance->__construct(3);
320
        self::assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
321
        self::assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected');
322
323
        $proxyName = $this->generateProxy(get_class($instance));
324
325
        /* @var $proxy ClassWithCounterConstructor */
326
        $proxy = new $proxyName(15);
327
328
        self::assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
329
        self::assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
330
        $proxy->__construct(5);
331
        self::assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
332
        self::assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
333
    }
334
335
    /**
336
     * @group 265
337
     */
338
    public function testWillForwardVariadicByRefArguments() : void
339
    {
340
        $proxyName   = $this->generateProxy(ClassWithMethodWithByRefVariadicFunction::class);
341
        /* @var $object ClassWithMethodWithByRefVariadicFunction */
342
        $object = $proxyName::staticProxyConstructor(function (& $wrappedInstance) {
343
            $wrappedInstance = new ClassWithMethodWithByRefVariadicFunction();
344
        });
345
346
        $parameters = ['a', 'b', 'c'];
347
348
        // first, testing normal variadic behavior (verifying we didn't screw up in the test asset)
349
        self::assertSame(['a', 'changed', 'c'], (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$parameters));
350
        self::assertSame(['a', 'changed', 'c'], $object->tuz(...$parameters));
351
        self::assertSame(['a', 'changed', 'c'], $parameters, 'by-ref variadic parameter was changed');
352
    }
353
354
    /**
355
     * This test documents a known limitation: `func_get_args()` (and similars) don't work in proxied APIs.
356
     * If you manage to make this test pass, then please do send a patch
357
     *
358
     * @group 265
359
     */
360
    public function testWillNotForwardDynamicArguments() : void
361
    {
362
        $proxyName = $this->generateProxy(ClassWithDynamicArgumentsMethod::class);
363
364
        /* @var $object ClassWithDynamicArgumentsMethod */
365
        $object = $proxyName::staticProxyConstructor(function (& $wrappedInstance) {
366
            $wrappedInstance = new ClassWithDynamicArgumentsMethod();
367
        });
368
369
        self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b'));
370
371
        $this->expectException(ExpectationFailedException::class);
372
373
        self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b'));
374
    }
375
376
    /**
377
     * Generates a proxy for the given class name, and retrieves its class name
378
     */
379
    private function generateProxy(string $parentClassName) : string
380
    {
381
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
382
        $generator          = new LazyLoadingValueHolderGenerator();
383
        $generatedClass     = new ClassGenerator($generatedClassName);
384
        $strategy           = new EvaluatingGeneratorStrategy();
385
386
        $generator->generate(new ReflectionClass($parentClassName), $generatedClass);
387
        $strategy->generate($generatedClass);
388
389
        return $generatedClassName;
390
    }
391
392
    /**
393
     * @param string $className
394
     * @param object $realInstance
395
     * @param Mock   $initializerMatcher
396
     *
397
     * @return callable
398
     */
399
    private function createInitializer(string $className, $realInstance, Mock $initializerMatcher = null) : callable
400
    {
401
        /* @var $initializerMatcher callable|Mock */
402
        if (! $initializerMatcher) {
403
            $initializerMatcher = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
404
405
            $initializerMatcher
406
                ->expects(self::once())
407
                ->method('__invoke')
408
                ->with(
409
                    self::logicalAnd(
410
                        self::isInstanceOf(VirtualProxyInterface::class),
411
                        self::isInstanceOf($className)
412
                    ),
413
                    $realInstance
414
                );
415
        }
416
417
        return function (
418
            & $wrappedObject,
419
            VirtualProxyInterface $proxy,
420
            $method,
421
            $params,
422
            & $initializer
423
        ) use (
424
            $initializerMatcher,
425
            $realInstance
426
        ) : void {
427
            $initializer   = null;
428
            $wrappedObject = $realInstance;
429
430
            $initializerMatcher($proxy, $wrappedObject, $method, $params);
431
        };
432
    }
433
434
    /**
435
     * Generates a list of object | invoked method | parameters | expected result
436
     */
437
    public function getProxyMethods() : array
438
    {
439
        $selfHintParam = new ClassWithSelfHint();
440
        $empty         = new EmptyClass();
441
442
        return [
443
            [
444
                BaseClass::class,
445
                new BaseClass(),
446
                'publicMethod',
447
                [],
448
                'publicMethodDefault'
449
            ],
450
            [
451
                BaseClass::class,
452
                new BaseClass(),
453
                'publicTypeHintedMethod',
454
                [new stdClass()],
455
                'publicTypeHintedMethodDefault'
456
            ],
457
            [
458
                BaseClass::class,
459
                new BaseClass(),
460
                'publicByReferenceMethod',
461
                [],
462
                'publicByReferenceMethodDefault'
463
            ],
464
            [
465
                BaseInterface::class,
466
                new BaseClass(),
467
                'publicMethod',
468
                [],
469
                'publicMethodDefault'
470
            ],
471
            [
472
                ClassWithSelfHint::class,
473
                new ClassWithSelfHint(),
474
                'selfHintMethod',
475
                ['parameter' => $selfHintParam],
476
                $selfHintParam
477
            ],
478
            [
479
                ClassWithParentHint::class,
480
                new ClassWithParentHint(),
481
                'parentHintMethod',
482
                ['parameter' => $empty],
483
                $empty
484
            ],
485
            [
486
                ClassWithMethodWithVariadicFunction::class,
487
                new ClassWithMethodWithVariadicFunction(),
488
                'buz',
489
                ['Ocramius', 'Malukenho'],
490
                ['Ocramius', 'Malukenho']
491
            ],
492
            [
493
                ClassWithMethodWithByRefVariadicFunction::class,
494
                new ClassWithMethodWithByRefVariadicFunction(),
495
                'tuz',
496
                ['Ocramius', 'Malukenho'],
497
                ['Ocramius', 'changed']
498
            ],
499
            [
500
                ClassWithMagicMethods::class,
501
                new ClassWithMagicMethods(),
502
                '__get',
503
                ['parameterName'],
504
                'parameterName',
505
            ],
506
            [
507
                ClassWithMagicMethods::class,
508
                new ClassWithMagicMethods(),
509
                '__set',
510
                ['foo', 'bar'],
511
                ['foo' => 'bar'],
512
            ],
513
            [
514
                ClassWithMagicMethods::class,
515
                new ClassWithMagicMethods(),
516
                '__isset',
517
                ['example'],
518
                true,
519
            ],
520
            [
521
                ClassWithMagicMethods::class,
522
                new ClassWithMagicMethods(),
523
                '__isset',
524
                [''],
525
                false,
526
            ],
527
            [
528
                ClassWithMagicMethods::class,
529
                new ClassWithMagicMethods(),
530
                '__unset',
531
                ['example'],
532
                true,
533
            ],
534
        ];
535
    }
536
537
    /**
538
     * Generates proxies and instances with a public property to feed to the property accessor methods
539
     *
540
     * @return array
541
     */
542
    public function getPropertyAccessProxies() : array
543
    {
544
        $instance1 = new BaseClass();
545
        $proxyName1 = $this->generateProxy(get_class($instance1));
546
        $instance2 = new BaseClass();
547
        $proxyName2 = $this->generateProxy(get_class($instance2));
548
549
        return [
550
            [
551
                $instance1,
552
                $proxyName1::staticProxyConstructor(
553
                    $this->createInitializer(BaseClass::class, $instance1)
554
                ),
555
                'publicProperty',
556
                'publicPropertyDefault',
557
            ],
558
            [
559
                $instance2,
560
                unserialize(serialize($proxyName2::staticProxyConstructor(
561
                    $this->createInitializer(BaseClass::class, $instance2)
562
                ))),
563
                'publicProperty',
564
                'publicPropertyDefault',
565
            ],
566
        ];
567
    }
568
569
    /**
570
     * @group 276
571
     *
572
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
573
     *
574
     * @param object $callerObject
575
     * @param object $realInstance
576
     * @param string $method
577
     * @param string $expectedValue
578
     */
579
    public function testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope(
580
        $callerObject,
581
        $realInstance,
582
        string $method,
583
        string $expectedValue
584
    ) : void {
585
        $proxyName = $this->generateProxy(get_class($realInstance));
586
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
587
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer(get_class($realInstance), $realInstance));
588
589
        /* @var $accessor callable */
590
        $accessor = [$callerObject, $method];
591
592
        self::assertFalse($proxy->isProxyInitialized());
593
        self::assertSame($expectedValue, $accessor($proxy));
594
        self::assertTrue($proxy->isProxyInitialized());
595
    }
596
597
    /**
598
     * @group 276
599
     *
600
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
601
     *
602
     * @param object $callerObject
603
     * @param object $realInstance
604
     * @param string $method
605
     * @param string $expectedValue
606
     */
607
    public function testWillFetchMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope(
608
        $callerObject,
609
        $realInstance,
610
        string $method,
611
        string $expectedValue
612
    ) : void {
613
        $proxyName = $this->generateProxy(get_class($realInstance));
614
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
615
        $proxy = unserialize(serialize(
616
            $proxyName::staticProxyConstructor($this->createInitializer(get_class($realInstance), $realInstance))
617
        ));
618
619
        /* @var $accessor callable */
620
        $accessor = [$callerObject, $method];
621
622
        self::assertTrue($proxy->isProxyInitialized());
623
        self::assertSame($expectedValue, $accessor($proxy));
624
    }
625
626
    /**
627
     * @group 276
628
     *
629
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
630
     *
631
     * @param object $callerObject
632
     * @param object $realInstance
633
     * @param string $method
634
     * @param string $expectedValue
635
     */
636
    public function testWillFetchMembersOfOtherClonedProxiesWithTheSamePrivateScope(
637
        $callerObject,
638
        $realInstance,
639
        string $method,
640
        string $expectedValue
641
    ) : void {
642
        $proxyName = $this->generateProxy(get_class($realInstance));
643
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
644
        $proxy = clone $proxyName::staticProxyConstructor(
645
            $this->createInitializer(get_class($realInstance), $realInstance)
646
        );
647
648
        /* @var $accessor callable */
649
        $accessor = [$callerObject, $method];
650
651
        self::assertTrue($proxy->isProxyInitialized());
652
        self::assertSame($expectedValue, $accessor($proxy));
653
    }
654
655
    /**
656
     * @group 327
657
     */
658
    public function testWillExecuteLogicInAVoidMethod() : void
659
    {
660
        $proxyName = $this->generateProxy(VoidCounter::class);
661
        /* @var $proxy VoidCounter */
662
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer(VoidCounter::class, new VoidCounter()));
663
664
        $increment = random_int(100, 1000);
665
666
        $proxy->increment($increment);
667
668
        self::assertSame($increment, $proxy->counter);
669
    }
670
671
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : \Generator
672
    {
673
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
674
675
        foreach ((new \ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
676
            $propertyName  = $property->getName();
677
            $expectedValue = uniqid('', true);
678
679
            // callee is an actual object
680
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
681
                new OtherObjectAccessClass(),
682
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
683
                'get' . ucfirst($propertyName),
684
                $expectedValue,
685
            ];
686
687
            $expectedValue = uniqid('', true);
688
689
            // callee is a proxy (not to be lazy-loaded!)
690
            yield '(proxy) ' . OtherObjectAccessClass::class . '#$' . $propertyName => [
691
                $proxyClass::staticProxyConstructor($this->createInitializer(
692
                    OtherObjectAccessClass::class,
693
                    new OtherObjectAccessClass()
694
                )),
695
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
696
                'get' . ucfirst($propertyName),
697
                $expectedValue,
698
            ];
699
        }
700
    }
701
702
    /**
703
     * @param object $instance
704
     * @param array  $values
705
     *
706
     * @return object
707
     */
708
    private function buildInstanceWithValues($instance, array $values)
709
    {
710
        foreach ($values as $property => $value) {
711
            $property = new \ReflectionProperty($instance, $property);
712
713
            $property->setAccessible(true);
714
715
            $property->setValue($instance, $value);
716
        }
717
718
        return $instance;
719
    }
720
}
721