Completed
Pull Request — master (#277)
by Marco
04:04 queued 17s
created

getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 30
rs 8.8571
cc 2
eloc 18
nc 2
nop 0
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::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...
553
        self::assertSame($expectedValue, $accessor($proxy));
554
        self::assertTrue($proxy->isProxyInitialized());
555
    }
556
557
    /**
558
     * @group 276
559
     *
560
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
561
     *
562
     * @param object $callerObject
563
     * @param object $realInstance
564
     * @param string $method
565
     * @param string $expectedValue
566
     */
567
    public function testWillFetchMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope(
568
        $callerObject,
569
        $realInstance,
570
        string $method,
571
        string $expectedValue
572
    ) {
573
        $proxyName = $this->generateProxy(get_class($realInstance));
574
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
575
        $proxy = unserialize(serialize(
576
            $proxyName::staticProxyConstructor($this->createInitializer(get_class($realInstance), $realInstance))
577
        ));
578
579
        /* @var $accessor callable */
580
        $accessor = [$callerObject, $method];
581
582
        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...
583
        self::assertSame($expectedValue, $accessor($proxy));
584
    }
585
586
    /**
587
     * @group 276
588
     *
589
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
590
     *
591
     * @param object $callerObject
592
     * @param object $realInstance
593
     * @param string $method
594
     * @param string $expectedValue
595
     */
596
    public function testWillFetchMembersOfOtherClonedProxiesWithTheSamePrivateScope(
597
        $callerObject,
598
        $realInstance,
599
        string $method,
600
        string $expectedValue
601
    ) {
602
        $proxyName = $this->generateProxy(get_class($realInstance));
603
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
604
        $proxy = clone $proxyName::staticProxyConstructor(
605
            $this->createInitializer(get_class($realInstance), $realInstance)
606
        );
607
608
        /* @var $accessor callable */
609
        $accessor = [$callerObject, $method];
610
611
        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...
612
        self::assertSame($expectedValue, $accessor($proxy));
613
    }
614
615
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : \Generator
616
    {
617
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
618
619
        foreach ((new \ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
620
            $propertyName  = $property->getName();
621
            $expectedValue = uniqid('', true);
622
623
            // callee is an actual object
624
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
625
                new OtherObjectAccessClass(),
626
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
627
                'get' . ucfirst($propertyName),
628
                $expectedValue,
629
            ];
630
631
            $expectedValue = uniqid('', true);
632
633
            // callee is a proxy (not to be lazy-loaded!)
634
            yield '(proxy) ' . OtherObjectAccessClass::class . '#$' . $propertyName => [
635
                $proxyClass::staticProxyConstructor($this->createInitializer(
636
                    OtherObjectAccessClass::class,
637
                    new OtherObjectAccessClass()
638
                )),
639
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
640
                'get' . ucfirst($propertyName),
641
                $expectedValue,
642
            ];
643
        }
644
    }
645
646
    /**
647
     * @param object $instance
648
     * @param array  $values
649
     *
650
     * @return object
651
     */
652
    private function buildInstanceWithValues($instance, array $values)
653
    {
654
        foreach ($values as $property => $value) {
655
            $property = new \ReflectionProperty($instance, $property);
656
657
            $property->setAccessible(true);
658
659
            $property->setValue($instance, $value);
660
        }
661
662
        return $instance;
663
    }
664
}
665