Completed
Push — master ( 1a9936...b9df3b )
by Marco
23:13
created

testPropertyExistence()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

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