Completed
Push — master ( e9b934...8f3081 )
by Marco
18s
created

testPropertyReadAccess()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 4
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\ClassWithParentHint;
37
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
38
use ProxyManagerTestAsset\ClassWithPublicProperties;
39
use ProxyManagerTestAsset\ClassWithSelfHint;
40
use ProxyManagerTestAsset\EmptyClass;
41
use ProxyManagerTestAsset\OtherObjectAccessClass;
42
use ProxyManagerTestAsset\VoidCounter;
43
use ReflectionClass;
44
use stdClass;
45
46
/**
47
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator} produced objects
48
 *
49
 * @author Marco Pivetta <[email protected]>
50
 * @license MIT
51
 *
52
 * @group Functional
53
 * @coversNothing
54
 */
55
class AccessInterceptorValueHolderFunctionalTest extends PHPUnit_Framework_TestCase
56
{
57
    /**
58
     * @dataProvider getProxyMethods
59
     *
60
     * @param string  $className
61
     * @param object  $instance
62
     * @param string  $method
63
     * @param mixed[] $params
64
     * @param mixed   $expectedValue
65
     */
66
    public function testMethodCalls(string $className, $instance, string $method, $params, $expectedValue) : void
67
    {
68
        $proxyName = $this->generateProxy($className);
69
70
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorValueHolderInterface */
71
        $proxy     = $proxyName::staticProxyConstructor($instance);
72
73
        self::assertSame($instance, $proxy->getWrappedValueHolderValue());
74
        self::assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
75
76
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
77
        $listener = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
78
        $listener
0 ignored issues
show
Bug introduced by
The method expects cannot be called on $listener (of type callable).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
79
            ->expects(self::once())
80
            ->method('__invoke')
81
            ->with($proxy, $instance, $method, $params, false);
82
83
        $proxy->setMethodPrefixInterceptor(
84
            $method,
85
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
86
                $listener($proxy, $instance, $method, $params, $returnEarly);
87
            }
88
        );
89
90
        self::assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
91
92
        $random = uniqid('', true);
93
94
        $proxy->setMethodPrefixInterceptor(
95
            $method,
96
            function ($proxy, $instance, string $method, $params, & $returnEarly) use ($random) : string {
97
                $returnEarly = true;
98
99
                return $random;
100
            }
101
        );
102
103
        self::assertSame($random, call_user_func_array([$proxy, $method], $params));
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 testMethodCallsWithSuffixListener(
116
        string $className,
117
        $instance,
118
        string $method,
119
        $params,
120
        $expectedValue
121
    ) : void {
122
        $proxyName = $this->generateProxy($className);
123
124
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorValueHolderInterface */
125
        $proxy    = $proxyName::staticProxyConstructor($instance);
126
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
127
        $listener = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
128
        $listener
0 ignored issues
show
Bug introduced by
The method expects cannot be called on $listener (of type callable).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
129
            ->expects(self::once())
130
            ->method('__invoke')
131
            ->with($proxy, $instance, $method, $params, $expectedValue, false);
132
133
        $proxy->setMethodSuffixInterceptor(
134
            $method,
135
            function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($listener) {
136
                $listener($proxy, $instance, $method, $params, $returnValue, $returnEarly);
137
            }
138
        );
139
140
        self::assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
141
142
        $random = uniqid();
143
144
        $proxy->setMethodSuffixInterceptor(
145
            $method,
146
            function ($proxy, $instance, string $method, $params, $returnValue, & $returnEarly) use ($random) : string {
147
                $returnEarly = true;
148
149
                return $random;
150
            }
151
        );
152
153
        self::assertSame($random, call_user_func_array([$proxy, $method], $params));
154
    }
155
156
    /**
157
     * @dataProvider getProxyMethods
158
     *
159
     * @param string  $className
160
     * @param object  $instance
161
     * @param string  $method
162
     * @param mixed[] $params
163
     * @param mixed   $expectedValue
164
     */
165
    public function testMethodCallsAfterUnSerialization(
166
        string $className,
167
        $instance,
168
        string $method,
169
        $params,
170
        $expectedValue
171
    ) : void {
172
        $proxyName = $this->generateProxy($className);
173
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorValueHolderInterface */
174
        $proxy     = unserialize(serialize($proxyName::staticProxyConstructor($instance)));
175
176
        self::assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
177
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
178
    }
179
180
    /**
181
     * @dataProvider getProxyMethods
182
     *
183
     * @param string  $className
184
     * @param object  $instance
185
     * @param string  $method
186
     * @param mixed[] $params
187
     * @param mixed   $expectedValue
188
     */
189
    public function testMethodCallsAfterCloning(
190
        string $className,
191
        $instance,
192
        string $method,
193
        $params,
194
        $expectedValue
195
    ) : void {
196
        $proxyName = $this->generateProxy($className);
197
198
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorValueHolderInterface */
199
        $proxy     = $proxyName::staticProxyConstructor($instance);
200
        $cloned    = clone $proxy;
201
202
        self::assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue());
203
        self::assertSame($expectedValue, call_user_func_array([$cloned, $method], $params));
204
        self::assertEquals($instance, $cloned->getWrappedValueHolderValue());
205
    }
206
207
    /**
208
     * @dataProvider getPropertyAccessProxies
209
     *
210
     * @param object                                $instance
211
     * @param AccessInterceptorValueHolderInterface $proxy
212
     * @param string                                $publicProperty
213
     * @param mixed                                 $propertyValue
214
     */
215
    public function testPropertyReadAccess(
216
        $instance,
217
        AccessInterceptorValueHolderInterface $proxy,
218
        string $publicProperty,
219
        $propertyValue
220
    ) : void {
221
        self::assertSame($propertyValue, $proxy->$publicProperty);
222
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
223
    }
224
225
    /**
226
     * @dataProvider getPropertyAccessProxies
227
     *
228
     * @param object                                $instance
229
     * @param AccessInterceptorValueHolderInterface $proxy
230
     * @param string                                $publicProperty
231
     */
232
    public function testPropertyWriteAccess(
233
        $instance,
234
        AccessInterceptorValueHolderInterface $proxy,
235
        string $publicProperty
236
    ) : void {
237
        $newValue               = uniqid();
238
        $proxy->$publicProperty = $newValue;
239
240
        self::assertSame($newValue, $proxy->$publicProperty);
241
        self::assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty);
242
    }
243
244
    /**
245
     * @dataProvider getPropertyAccessProxies
246
     *
247
     * @param object                                $instance
248
     * @param AccessInterceptorValueHolderInterface $proxy
249
     * @param string                                $publicProperty
250
     */
251
    public function testPropertyExistence(
252
        $instance,
253
        AccessInterceptorValueHolderInterface $proxy,
254
        string $publicProperty
255
    ) : void {
256
        self::assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
257
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
258
259
        $proxy->getWrappedValueHolderValue()->$publicProperty = null;
260
        self::assertFalse(isset($proxy->$publicProperty));
261
    }
262
263
    /**
264
     * @dataProvider getPropertyAccessProxies
265
     *
266
     * @param object                                $instance
267
     * @param AccessInterceptorValueHolderInterface $proxy
268
     * @param string                                $publicProperty
269
     */
270
    public function testPropertyUnset(
271
        $instance,
272
        AccessInterceptorValueHolderInterface $proxy,
273
        string $publicProperty
274
    ) : void {
275
        $instance = $proxy->getWrappedValueHolderValue() ?: $instance;
276
        unset($proxy->$publicProperty);
277
278
        self::assertFalse(isset($instance->$publicProperty));
279
        self::assertFalse(isset($proxy->$publicProperty));
280
    }
281
282
    /**
283
     * Verifies that accessing a public property containing an array behaves like in a normal context
284
     */
285
    public function testCanWriteToArrayKeysInPublicProperty() : void
286
    {
287
        $instance    = new ClassWithPublicArrayProperty();
288
        $className   = get_class($instance);
289
        $proxyName   = $this->generateProxy($className);
290
        /* @var $proxy ClassWithPublicArrayProperty */
291
        $proxy       = $proxyName::staticProxyConstructor($instance);
292
293
        $proxy->arrayProperty['foo'] = 'bar';
294
295
        self::assertSame('bar', $proxy->arrayProperty['foo']);
296
297
        $proxy->arrayProperty = ['tab' => 'taz'];
298
299
        self::assertSame(['tab' => 'taz'], $proxy->arrayProperty);
300
    }
301
302
    /**
303
     * Verifies that public properties retrieved via `__get` don't get modified in the object state
304
     */
305
    public function testWillNotModifyRetrievedPublicProperties() : void
306
    {
307
        $instance    = new ClassWithPublicProperties();
308
        $className   = get_class($instance);
309
        $proxyName   = $this->generateProxy($className);
310
        /* @var $proxy ClassWithPublicProperties */
311
        $proxy       = $proxyName::staticProxyConstructor($instance);
312
        $variable    = $proxy->property0;
313
314
        self::assertSame('property0', $variable);
315
316
        $variable = 'foo';
317
318
        self::assertSame('property0', $proxy->property0);
319
        self::assertSame('foo', $variable);
320
    }
321
322
    /**
323
     * Verifies that public properties references retrieved via `__get` modify in the object state
324
     */
325
    public function testWillModifyByRefRetrievedPublicProperties() : void
326
    {
327
        $instance    = new ClassWithPublicProperties();
328
        $className   = get_class($instance);
329
        $proxyName   = $this->generateProxy($className);
330
        /* @var $proxy ClassWithPublicProperties */
331
        $proxy       = $proxyName::staticProxyConstructor($instance);
332
        $variable    = & $proxy->property0;
333
334
        self::assertSame('property0', $variable);
335
336
        $variable = 'foo';
337
338
        self::assertSame('foo', $proxy->property0);
339
        self::assertSame('foo', $variable);
340
    }
341
342
    /**
343
     * @group 115
344
     * @group 175
345
     */
346
    public function testWillBehaveLikeObjectWithNormalConstructor() : void
347
    {
348
        $instance = new ClassWithCounterConstructor(10);
349
350
        self::assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
351
        self::assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
352
        $instance->__construct(3);
353
        self::assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
354
        self::assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected');
355
356
        $proxyName = $this->generateProxy(get_class($instance));
357
358
        /* @var $proxy ClassWithCounterConstructor */
359
        $proxy = new $proxyName(15);
360
361
        self::assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
362
        self::assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
363
        $proxy->__construct(5);
364
        self::assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
365
        self::assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
366
    }
367
368
    public function testWillForwardVariadicArguments() : void
369
    {
370
        $factory       = new AccessInterceptorValueHolderFactory();
371
        $targetObject  = new ClassWithMethodWithVariadicFunction();
372
373
        /* @var $object ClassWithMethodWithVariadicFunction */
374
        $object = $factory->createProxy(
375
            $targetObject,
376
            [
377
                function () : string {
378
                    return 'Foo Baz';
379
                },
380
            ]
381
        );
382
383
        self::assertNull($object->bar);
384
        self::assertNull($object->baz);
385
386
        $object->foo('Ocramius', 'Malukenho', 'Danizord');
387
        self::assertSame('Ocramius', $object->bar);
388
        self::assertSame(['Malukenho', 'Danizord'], $object->baz);
389
    }
390
391
    /**
392
     * @group 265
393
     */
394
    public function testWillForwardVariadicByRefArguments() : void
395
    {
396
        $factory       = new AccessInterceptorValueHolderFactory();
397
        $targetObject  = new ClassWithMethodWithByRefVariadicFunction();
398
399
        /* @var $object ClassWithMethodWithByRefVariadicFunction */
400
        $object = $factory->createProxy(
401
            $targetObject,
402
            [
403
                function () : string {
404
                    return 'Foo Baz';
405
                },
406
            ]
407
        );
408
409
        $arguments = ['Ocramius', 'Malukenho', 'Danizord'];
410
411
        self::assertSame(
412
            ['Ocramius', 'changed', 'Danizord'],
413
            (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$arguments),
414
            'Verifying that the implementation of the test asset is correct before proceeding'
415
        );
416
        self::assertSame(['Ocramius', 'changed', 'Danizord'], $object->tuz(...$arguments));
417
        self::assertSame(['Ocramius', 'changed', 'Danizord'], $arguments, 'By-ref arguments were changed');
418
    }
419
420
    /**
421
     * This test documents a known limitation: `func_get_args()` (and similars) don't work in proxied APIs.
422
     * If you manage to make this test pass, then please do send a patch
423
     *
424
     * @group 265
425
     */
426
    public function testWillNotForwardDynamicArguments() : void
427
    {
428
        $proxyName = $this->generateProxy(ClassWithDynamicArgumentsMethod::class);
429
430
        /* @var $object ClassWithDynamicArgumentsMethod */
431
        $object = $proxyName::staticProxyConstructor(new ClassWithDynamicArgumentsMethod());
432
433
        self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b'));
434
435
        $this->expectException(\PHPUnit_Framework_ExpectationFailedException::class);
436
437
        self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b'));
438
    }
439
440
    /**
441
     * Generates a proxy for the given class name, and retrieves its class name
442
     *
443
     * @param string $parentClassName
444
     *
445
     * @return string
446
     */
447
    private function generateProxy(string $parentClassName) : string
448
    {
449
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
450
        $generator          = new AccessInterceptorValueHolderGenerator();
451
        $generatedClass     = new ClassGenerator($generatedClassName);
452
        $strategy           = new EvaluatingGeneratorStrategy();
453
454
        $generator->generate(new ReflectionClass($parentClassName), $generatedClass);
455
        $strategy->generate($generatedClass);
456
457
        return $generatedClassName;
458
    }
459
460
    /**
461
     * Generates a list of object | invoked method | parameters | expected result
462
     *
463
     * @return array
464
     */
465
    public function getProxyMethods() : array
466
    {
467
        $selfHintParam = new ClassWithSelfHint();
468
        $empty         = new EmptyClass();
469
470
        return [
471
            [
472
                BaseClass::class,
473
                new BaseClass(),
474
                'publicMethod',
475
                [],
476
                'publicMethodDefault'
477
            ],
478
            [
479
                BaseClass::class,
480
                new BaseClass(),
481
                'publicTypeHintedMethod',
482
                ['param' => new stdClass()],
483
                'publicTypeHintedMethodDefault'
484
            ],
485
            [
486
                BaseClass::class,
487
                new BaseClass(),
488
                'publicByReferenceMethod',
489
                [],
490
                'publicByReferenceMethodDefault'
491
            ],
492
            [
493
                BaseInterface::class,
494
                new BaseClass(),
495
                'publicMethod',
496
                [],
497
                'publicMethodDefault'
498
            ],
499
            [
500
                ClassWithSelfHint::class,
501
                new ClassWithSelfHint(),
502
                'selfHintMethod',
503
                ['parameter' => $selfHintParam],
504
                $selfHintParam
505
            ],
506
            [
507
                ClassWithParentHint::class,
508
                new ClassWithParentHint(),
509
                'parentHintMethod',
510
                ['parameter' => $empty],
511
                $empty
512
            ],
513
        ];
514
    }
515
516
    /**
517
     * Generates proxies and instances with a public property to feed to the property accessor methods
518
     *
519
     * @return array
520
     */
521
    public function getPropertyAccessProxies() : array
522
    {
523
        $instance1  = new BaseClass();
524
        $proxyName1 = $this->generateProxy(get_class($instance1));
525
        $instance2  = new BaseClass();
526
        $proxyName2 = $this->generateProxy(get_class($instance2));
527
528
        return [
529
            [
530
                $instance1,
531
                $proxyName1::staticProxyConstructor($instance1),
532
                'publicProperty',
533
                'publicPropertyDefault',
534
            ],
535
            [
536
                $instance2,
537
                unserialize(serialize($proxyName2::staticProxyConstructor($instance2))),
538
                'publicProperty',
539
                'publicPropertyDefault',
540
            ],
541
        ];
542
    }
543
544
    /**
545
     * @group 276
546
     *
547
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
548
     *
549
     * @param object $callerObject
550
     * @param object $realInstance
551
     * @param string $method
552
     * @param string $expectedValue
553
     * @param string $propertyName
554
     */
555
    public function testWillInterceptAccessToPropertiesViaFriendClassAccess(
556
        $callerObject,
557
        $realInstance,
558
        string $method,
559
        string $expectedValue,
560
        string $propertyName
561
    ) : void {
562
        $proxyName = $this->generateProxy(get_class($realInstance));
563
        /* @var $proxy OtherObjectAccessClass|AccessInterceptorValueHolderInterface */
564
        $proxy = $proxyName::staticProxyConstructor($realInstance);
565
566
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
567
        $listener = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
568
569
        $listener
0 ignored issues
show
Bug introduced by
The method expects cannot be called on $listener (of type callable).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
570
            ->expects(self::once())
571
            ->method('__invoke')
572
            ->with($proxy, $realInstance, '__get', ['name' => $propertyName]);
573
574
        $proxy->setMethodPrefixInterceptor(
575
            '__get',
576
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
577
                $listener($proxy, $instance, $method, $params, $returnEarly);
578
            }
579
        );
580
581
        /* @var $accessor callable */
582
        $accessor = [$callerObject, $method];
583
584
        self::assertSame($expectedValue, $accessor($proxy));
585
    }
586
587
    /**
588
     * @group 276
589
     *
590
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
591
     *
592
     * @param object $callerObject
593
     * @param object $realInstance
594
     * @param string $method
595
     * @param string $expectedValue
596
     * @param string $propertyName
597
     */
598
    public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfDeSerialized(
599
        $callerObject,
600
        $realInstance,
601
        string $method,
602
        string $expectedValue,
603
        string $propertyName
604
    ) : void {
605
        $proxyName = $this->generateProxy(get_class($realInstance));
606
        /* @var $proxy OtherObjectAccessClass|AccessInterceptorValueHolderInterface */
607
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor($realInstance)));
608
609
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
610
        $listener = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
611
612
        $listener
0 ignored issues
show
Bug introduced by
The method expects cannot be called on $listener (of type callable).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
613
            ->expects(self::once())
614
            ->method('__invoke')
615
            ->with($proxy, $realInstance, '__get', ['name' => $propertyName]);
616
617
        $proxy->setMethodPrefixInterceptor(
618
            '__get',
619
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
620
                $listener($proxy, $instance, $method, $params, $returnEarly);
621
            }
622
        );
623
624
        /* @var $accessor callable */
625
        $accessor = [$callerObject, $method];
626
627
        self::assertSame($expectedValue, $accessor($proxy));
628
    }
629
630
631
    /**
632
     * @group 276
633
     *
634
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
635
     *
636
     * @param object $callerObject
637
     * @param object $realInstance
638
     * @param string $method
639
     * @param string $expectedValue
640
     * @param string $propertyName
641
     */
642
    public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfCloned(
643
        $callerObject,
644
        $realInstance,
645
        string $method,
646
        string $expectedValue,
647
        string $propertyName
648
    ) : void {
649
        $proxyName = $this->generateProxy(get_class($realInstance));
650
        /* @var $proxy OtherObjectAccessClass|AccessInterceptorValueHolderInterface */
651
        $proxy = clone $proxyName::staticProxyConstructor($realInstance);
652
653
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
654
        $listener = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
655
656
        $listener
0 ignored issues
show
Bug introduced by
The method expects cannot be called on $listener (of type callable).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
657
            ->expects(self::once())
658
            ->method('__invoke')
659
            ->with($proxy, $realInstance, '__get', ['name' => $propertyName]);
660
661
        $proxy->setMethodPrefixInterceptor(
662
            '__get',
663
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
664
                $listener($proxy, $instance, $method, $params, $returnEarly);
665
            }
666
        );
667
668
        /* @var $accessor callable */
669
        $accessor = [$callerObject, $method];
670
671
        self::assertSame($expectedValue, $accessor($proxy));
672
    }
673
674
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : \Generator
675
    {
676
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
677
678
        foreach ((new \ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
679
            $property->setAccessible(true);
680
681
            $propertyName  = $property->getName();
682
            $realInstance  = new OtherObjectAccessClass();
683
            $expectedValue = uniqid('', true);
684
685
            $property->setValue($realInstance, $expectedValue);
686
687
            // callee is an actual object
688
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
689
                new OtherObjectAccessClass(),
690
                $realInstance,
691
                'get' . ucfirst($propertyName),
692
                $expectedValue,
693
                $propertyName,
694
            ];
695
696
            $realInstance  = new OtherObjectAccessClass();
697
            $expectedValue = uniqid('', true);
698
699
            $property->setValue($realInstance, $expectedValue);
700
701
            // callee is a proxy (not to be lazy-loaded!)
702
            yield '(proxy) ' . OtherObjectAccessClass::class . '#$' . $propertyName => [
703
                $proxyClass::staticProxyConstructor(new OtherObjectAccessClass()),
704
                $realInstance,
705
                'get' . ucfirst($propertyName),
706
                $expectedValue,
707
                $propertyName,
708
            ];
709
        }
710
    }
711
712
    /**
713
     * @group 327
714
     */
715
    public function testWillInterceptAndReturnEarlyOnVoidMethod() : void
716
    {
717
        $skip      = random_int(100, 200);
718
        $addMore   = random_int(201, 300);
719
        $increment = random_int(301, 400);
720
721
        $proxyName = $this->generateProxy(VoidCounter::class);
722
723
        /* @var $object VoidCounter */
724
        $object = $proxyName::staticProxyConstructor(
725
            new VoidCounter(),
726
            [
727
                'increment' => function (
728
                    VoidCounter $proxy,
729
                    VoidCounter $instance,
730
                    string $method,
731
                    array $params,
732
                    ?bool & $returnEarly
733
                ) use ($skip) : void {
734
                    if ($skip === $params['amount']) {
735
                        $returnEarly = true;
736
                    }
737
                },
738
            ],
739
            [
740
                'increment' => function (
741
                    VoidCounter $proxy,
742
                    VoidCounter $instance,
743
                    string $method,
744
                    array $params,
745
                    ?bool & $returnEarly
0 ignored issues
show
Unused Code introduced by
The parameter $returnEarly 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...
746
                ) use ($addMore) : void {
747
                    if ($addMore === $params['amount']) {
748
                        $instance->counter += 1;
749
                    }
750
                },
751
            ]
752
        );
753
754
        $object->increment($skip);
755
        self::assertSame(0, $object->counter);
756
757
        $object->increment($increment);
758
        self::assertSame($increment, $object->counter);
759
760
        $object->increment($addMore);
761
        self::assertSame($increment + $addMore + 1, $object->counter);
762
    }
763
}
764