Completed
Pull Request — master (#385)
by Marco
10:52
created

testPropertyUnset()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 3
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license.
17
 */
18
19
declare(strict_types=1);
20
21
namespace ProxyManagerTest\Functional;
22
23
use PHPUnit_Framework_TestCase;
24
use ProxyManager\Factory\AccessInterceptorValueHolderFactory;
25
use ProxyManager\Generator\ClassGenerator;
26
use ProxyManager\Generator\Util\UniqueIdentifierGenerator;
27
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
28
use ProxyManager\Proxy\AccessInterceptorValueHolderInterface;
29
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolderGenerator;
30
use ProxyManagerTestAsset\BaseClass;
31
use ProxyManagerTestAsset\BaseInterface;
32
use ProxyManagerTestAsset\ClassWithCounterConstructor;
33
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod;
34
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction;
35
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction;
36
use ProxyManagerTestAsset\ClassWithParentHint;
37
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
38
use ProxyManagerTestAsset\ClassWithPublicProperties;
39
use ProxyManagerTestAsset\ClassWithSelfHint;
40
use ProxyManagerTestAsset\EmptyClass;
41
use ProxyManagerTestAsset\OtherObjectAccessClass;
42
use ProxyManagerTestAsset\VoidCounter;
43
use ReflectionClass;
44
use stdClass;
45
46
/**
47
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator} produced objects
48
 *
49
 * @author Marco Pivetta <[email protected]>
50
 * @license MIT
51
 *
52
 * @group Functional
53
 * @coversNothing
54
 */
55
class AccessInterceptorValueHolderFunctionalTest extends PHPUnit_Framework_TestCase
56
{
57
    /**
58
     * @dataProvider getProxyMethods
59
     *
60
     * @param string  $className
61
     * @param object  $instance
62
     * @param string  $method
63
     * @param mixed[] $params
64
     * @param mixed   $expectedValue
65
     */
66
    public function testMethodCalls(string $className, $instance, string $method, $params, $expectedValue) : void
67
    {
68
        $proxyName = $this->generateProxy($className);
69
70
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorValueHolderInterface */
71
        $proxy     = $proxyName::staticProxyConstructor($instance);
72
73
        self::assertSame($instance, $proxy->getWrappedValueHolderValue());
74
        self::assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
75
76
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
77
        $listener = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
78
        $listener
0 ignored issues
show
Bug introduced by
The method expects cannot be called on $listener (of type callable).

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

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

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

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