Completed
Push — master ( f1e5e5...9f8061 )
by Marco
07:19
created

AccessInterceptorValueHolderFunctionalTest   C

Complexity

Total Complexity 27

Size/Duplication

Total Lines 703
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 20
dl 0
loc 703
rs 6.6058
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
B testMethodCalls() 0 39 1
B testMethodCallsWithSuffixListener() 0 40 1
A testMethodCallsAfterUnSerialization() 0 14 1
A testMethodCallsAfterCloning() 0 17 1
A testPropertyReadAccess() 0 9 1
A testPropertyWriteAccess() 0 11 1
A testPropertyExistence() 0 11 1
A testPropertyUnset() 0 11 2
A testCanWriteToArrayKeysInPublicProperty() 0 16 1
A testWillNotModifyRetrievedPublicProperties() 0 16 1
A testWillModifyByRefRetrievedPublicProperties() 0 16 1
A testWillBehaveLikeObjectWithNormalConstructor() 0 21 1
A testWillForwardVariadicArguments() 0 22 1
B testWillForwardVariadicByRefArguments() 0 25 1
A testWillNotForwardDynamicArguments() 0 13 1
A generateProxy() 0 12 1
A getProxyMethods() 0 50 1
A getPropertyAccessProxies() 0 22 1
B testWillInterceptAccessToPropertiesViaFriendClassAccess() 0 31 1
B testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfDeSerialized() 0 31 1
B testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfCloned() 0 31 1
B getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() 0 37 2
A testWillInterceptAndReturnEarlyOnVoidMethod() 0 48 3
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
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
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,
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...
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
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
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
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