Completed
Pull Request — master (#278)
by Marco
40:37 queued 39s
created

AccessInterceptorValueHolderFunctionalTest   B

Complexity

Total Complexity 24

Size/Duplication

Total Lines 644
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 24
c 1
b 0
f 0
lcom 1
cbo 16
dl 0
loc 644
rs 8.437

22 Methods

Rating   Name   Duplication   Size   Complexity  
B testMethodCalls() 0 39 1
B testMethodCallsWithSuffixListener() 0 40 1
A testMethodCallsAfterUnSerialization() 0 14 1
A testMethodCallsAfterCloning() 0 12 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
B getProxyMethods() 0 42 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
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 52 and the first side effect is on line 19.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

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

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
559
            '__get',
560
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
561
                $listener($proxy, $instance, $method, $params, $returnEarly);
562
            }
563
        );
564
565
        /* @var $accessor callable */
566
        $accessor = [$callerObject, $method];
567
568
        self::assertSame($expectedValue, $accessor($proxy));
569
    }
570
571
    /**
572
     * @group 276
573
     *
574
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
575
     *
576
     * @param object $callerObject
577
     * @param object $realInstance
578
     * @param string $method
579
     * @param string $expectedValue
580
     * @param string $propertyName
581
     */
582
    public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfDeSerialized(
583
        $callerObject,
584
        $realInstance,
585
        string $method,
586
        string $expectedValue,
587
        string $propertyName
588
    ) {
589
        $proxyName = $this->generateProxy(get_class($realInstance));
590
        /* @var $proxy OtherObjectAccessClass|AccessInterceptorValueHolderInterface */
591
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor($realInstance)));
592
593
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
594
        $listener = $this->getMock(\stdClass::class, ['__invoke']);
595
596
        $listener
597
            ->expects(self::once())
598
            ->method('__invoke')
599
            ->with($proxy, $realInstance, '__get', ['name' => $propertyName]);
600
601
        $proxy->setMethodPrefixInterceptor(
1 ignored issue
show
Bug introduced by
The method setMethodPrefixInterceptor does only exist in ProxyManager\Proxy\Acces...torValueHolderInterface, but not in ProxyManagerTestAsset\OtherObjectAccessClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
602
            '__get',
603
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
604
                $listener($proxy, $instance, $method, $params, $returnEarly);
605
            }
606
        );
607
608
        /* @var $accessor callable */
609
        $accessor = [$callerObject, $method];
610
611
        self::assertSame($expectedValue, $accessor($proxy));
612
    }
613
614
615
    /**
616
     * @group 276
617
     *
618
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
619
     *
620
     * @param object $callerObject
621
     * @param object $realInstance
622
     * @param string $method
623
     * @param string $expectedValue
624
     * @param string $propertyName
625
     */
626
    public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfCloned(
627
        $callerObject,
628
        $realInstance,
629
        string $method,
630
        string $expectedValue,
631
        string $propertyName
632
    ) {
633
        $proxyName = $this->generateProxy(get_class($realInstance));
634
        /* @var $proxy OtherObjectAccessClass|AccessInterceptorValueHolderInterface */
635
        $proxy = clone $proxyName::staticProxyConstructor($realInstance);
636
637
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
638
        $listener = $this->getMock(\stdClass::class, ['__invoke']);
639
640
        $listener
641
            ->expects(self::once())
642
            ->method('__invoke')
643
            ->with($proxy, $realInstance, '__get', ['name' => $propertyName]);
644
645
        $proxy->setMethodPrefixInterceptor(
1 ignored issue
show
Bug introduced by
The method setMethodPrefixInterceptor does only exist in ProxyManager\Proxy\Acces...torValueHolderInterface, but not in ProxyManagerTestAsset\OtherObjectAccessClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
646
            '__get',
647
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
648
                $listener($proxy, $instance, $method, $params, $returnEarly);
649
            }
650
        );
651
652
        /* @var $accessor callable */
653
        $accessor = [$callerObject, $method];
654
655
        self::assertSame($expectedValue, $accessor($proxy));
656
    }
657
658
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : \Generator
659
    {
660
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
661
662
        foreach ((new \ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
663
            $property->setAccessible(true);
664
665
            $propertyName  = $property->getName();
666
            $realInstance  = new OtherObjectAccessClass();
667
            $expectedValue = uniqid('', true);
668
669
            $property->setValue($realInstance, $expectedValue);
670
671
            // callee is an actual object
672
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
673
                new OtherObjectAccessClass(),
674
                $realInstance,
675
                'get' . ucfirst($propertyName),
676
                $expectedValue,
677
                $propertyName,
678
            ];
679
680
            $realInstance  = new OtherObjectAccessClass();
681
            $expectedValue = uniqid('', true);
682
683
            $property->setValue($realInstance, $expectedValue);
684
685
            // callee is a proxy (not to be lazy-loaded!)
686
            yield '(proxy) ' . OtherObjectAccessClass::class . '#$' . $propertyName => [
687
                $proxyClass::staticProxyConstructor(new OtherObjectAccessClass()),
688
                $realInstance,
689
                'get' . ucfirst($propertyName),
690
                $expectedValue,
691
                $propertyName,
692
            ];
693
        }
694
    }
695
}
696