Completed
Pull Request — master (#359)
by Jefersson
06:36
created

testMethodCalls()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 39
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 22
nc 1
nop 5
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
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
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,
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...
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
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
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
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