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

testMethodCallsWithSuffixListener()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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