Completed
Pull Request — master (#277)
by Marco
03:49
created

testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 12

Duplication

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