Completed
Push — master ( 5d17d5...24b1e3 )
by Marco
9s
created

generateProxy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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