Completed
Push — master ( bfa4db...ac38d0 )
by Marco
12s
created

testPropertyAbsence()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 3
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\LazyLoadingInterface;
13
use ProxyManager\Proxy\VirtualProxyInterface;
14
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator;
15
use ProxyManagerTestAsset\BaseClass;
16
use ProxyManagerTestAsset\BaseInterface;
17
use ProxyManagerTestAsset\ClassWithCounterConstructor;
18
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod;
19
use ProxyManagerTestAsset\ClassWithMagicMethods;
20
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction;
21
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction;
22
use ProxyManagerTestAsset\ClassWithParentHint;
23
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
24
use ProxyManagerTestAsset\ClassWithPublicProperties;
25
use ProxyManagerTestAsset\ClassWithSelfHint;
26
use ProxyManagerTestAsset\EmptyClass;
27
use ProxyManagerTestAsset\OtherObjectAccessClass;
28
use ProxyManagerTestAsset\VoidCounter;
29
use ReflectionClass;
30
use stdClass;
31
32
/**
33
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator} produced objects
34
 *
35
 * @author Marco Pivetta <[email protected]>
36
 * @license MIT
37
 *
38
 * @group Functional
39
 * @coversNothing
40
 */
41
class LazyLoadingValueHolderFunctionalTest extends PHPUnit_Framework_TestCase
42
{
43
    /**
44
     * @dataProvider getProxyMethods
45
     *
46
     * @param string  $className
47
     * @param object  $instance
48
     * @param string  $method
49
     * @param mixed[] $params
50
     * @param mixed   $expectedValue
51
     */
52
    public function testMethodCalls(string $className, $instance, string $method, array $params, $expectedValue) : void
53
    {
54
        $proxyName = $this->generateProxy($className);
55
56
        /* @var $proxy VirtualProxyInterface */
57
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
58
59
        self::assertFalse($proxy->isProxyInitialized());
60
61
        /* @var $callProxyMethod callable */
62
        $callProxyMethod = [$proxy, $method];
63
        $parameterValues = array_values($params);
64
65
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
66
        self::assertTrue($proxy->isProxyInitialized());
67
        self::assertSame($instance, $proxy->getWrappedValueHolderValue());
68
    }
69
70
    /**
71
     * @dataProvider getProxyMethods
72
     *
73
     * @param string  $className
74
     * @param object  $instance
75
     * @param string  $method
76
     * @param mixed[] $params
77
     * @param mixed   $expectedValue
78
     */
79
    public function testMethodCallsAfterUnSerialization(
80
        string $className,
81
        $instance,
82
        string $method,
83
        array $params,
84
        $expectedValue
85
    ) : void {
86
        $proxyName = $this->generateProxy($className);
87
88
        /* @var $proxy VirtualProxyInterface */
89
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
90
            $this->createInitializer($className, $instance)
91
        )));
92
93
        self::assertTrue($proxy->isProxyInitialized());
94
95
        /* @var $callProxyMethod callable */
96
        $callProxyMethod = [$proxy, $method];
97
        $parameterValues = array_values($params);
98
99
        self::assertInternalType('callable', $callProxyMethod);
100
101
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
102
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
103
    }
104
105
    /**
106
     * @dataProvider getProxyMethods
107
     *
108
     * @param string  $className
109
     * @param object  $instance
110
     * @param string  $method
111
     * @param mixed[] $params
112
     * @param mixed   $expectedValue
113
     */
114
    public function testMethodCallsAfterCloning(
115
        string $className,
116
        $instance,
117
        string $method,
118
        array $params,
119
        $expectedValue
120
    ) : void {
121
        $proxyName = $this->generateProxy($className);
122
123
        /* @var $proxy VirtualProxyInterface */
124
        $proxy  = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
125
        $cloned = clone $proxy;
126
127
        self::assertTrue($cloned->isProxyInitialized());
128
        self::assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue());
129
130
        /* @var $callProxyMethod callable */
131
        $callProxyMethod = [$cloned, $method];
132
        $parameterValues = array_values($params);
133
134
        self::assertInternalType('callable', $callProxyMethod);
135
136
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
137
        self::assertEquals($instance, $cloned->getWrappedValueHolderValue());
138
    }
139
140
    /**
141
     * @dataProvider getPropertyAccessProxies
142
     *
143
     * @param object                $instance
144
     * @param VirtualProxyInterface $proxy
145
     * @param string                $publicProperty
146
     * @param mixed                 $propertyValue
147
     */
148
    public function testPropertyReadAccess(
149
        $instance,
150
        VirtualProxyInterface $proxy,
151
        string $publicProperty,
152
        $propertyValue
153
    ) : void {
154
        self::assertSame($propertyValue, $proxy->$publicProperty);
155
        self::assertTrue($proxy->isProxyInitialized());
156
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
157
    }
158
159
    /**
160
     * @dataProvider getPropertyAccessProxies
161
     *
162
     * @param object                $instance
163
     * @param VirtualProxyInterface $proxy
164
     * @param string                $publicProperty
165
     */
166
    public function testPropertyWriteAccess($instance, VirtualProxyInterface $proxy, string $publicProperty) : void
0 ignored issues
show
Unused Code introduced by
The parameter $instance is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

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