Completed
Push — master ( 1a9936...b9df3b )
by Marco
23:13
created

testMethodCallsAfterUnSerialization()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 25
rs 8.8571
cc 1
eloc 15
nc 1
nop 5
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
        $this->assertFalse($proxy->isProxyInitialized());
72
73
        /* @var $callProxyMethod callable */
74
        $callProxyMethod = [$proxy, $method];
75
        $parameterValues = array_values($params);
76
77
        $this->assertSame($expectedValue, $callProxyMethod(...$parameterValues));
78
        $this->assertTrue($proxy->isProxyInitialized());
79
        $this->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
        $this->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
        $this->assertSame($expectedValue, $callProxyMethod(...$parameterValues));
114
        $this->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
        $this->assertTrue($cloned->isProxyInitialized());
140
        $this->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
        $this->assertSame($expectedValue, $callProxyMethod(...$parameterValues));
149
        $this->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
        $this->assertSame($propertyValue, $proxy->$publicProperty);
167
        $this->assertTrue($proxy->isProxyInitialized());
168
        $this->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
        $this->assertTrue($proxy->isProxyInitialized());
184
        $this->assertSame($newValue, $proxy->$publicProperty);
185
        $this->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
        $this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
198
        $this->assertTrue($proxy->isProxyInitialized());
199
        $this->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() ? $proxy->getWrappedValueHolderValue() : $instance;
212
        $instance->$publicProperty = null;
213
        $this->assertFalse(isset($proxy->$publicProperty));
214
        $this->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() ? $proxy->getWrappedValueHolderValue() : $instance;
227
        unset($proxy->$publicProperty);
228
229
        $this->assertTrue($proxy->isProxyInitialized());
230
231
        $this->assertFalse(isset($instance->$publicProperty));
232
        $this->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
        $this->assertSame('bar', $proxy->arrayProperty['foo']);
250
251
        $proxy->arrayProperty = ['tab' => 'taz'];
252
253
        $this->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
        $this->assertSame('property0', $variable);
270
271
        $variable = 'foo';
272
273
        $this->assertSame('property0', $proxy->property0);
274
        $this->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
        $this->assertSame('property0', $variable);
291
292
        $variable = 'foo';
293
294
        $this->assertSame('foo', $proxy->property0);
295
        $this->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
        $this->assertSame('1', $proxy->publicProperty);
316
        $this->assertSame('2', $proxy->publicProperty);
317
        $this->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
        $this->assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
329
        $this->assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
330
        $instance->__construct(3);
331
        $this->assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
332
        $this->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
        $this->assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
340
        $this->assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
341
        $proxy->__construct(5);
342
        $this->assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
343
        $this->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->setExpectedException(\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
        if (null === $initializerMatcher) {
417
            $initializerMatcher = $this->getMock(stdClass::class, ['__invoke']);
418
419
            $initializerMatcher
420
                ->expects($this->once())
421
                ->method('__invoke')
422
                ->with(
423
                    $this->logicalAnd(
424
                        $this->isInstanceOf(VirtualProxyInterface::class),
425
                        $this->isInstanceOf($className)
426
                    ),
427
                    $realInstance
428
                );
429
        }
430
431
        /* @var $initializerMatcher callable */
432
        $initializerMatcher = $initializerMatcher ?: $this->getMock(stdClass::class, ['__invoke']);
433
434
        return function (
435
            & $wrappedObject,
436
            VirtualProxyInterface $proxy,
437
            $method,
438
            $params,
439
            & $initializer
440
        ) use (
441
            $initializerMatcher,
442
            $realInstance
443
        ) {
444
            $initializer   = null;
445
            $wrappedObject = $realInstance;
446
447
            $initializerMatcher($proxy, $wrappedObject, $method, $params);
448
        };
449
    }
450
451
    /**
452
     * Generates a list of object | invoked method | parameters | expected result
453
     *
454
     * @return array
455
     */
456
    public function getProxyMethods() : array
457
    {
458
        $selfHintParam = new ClassWithSelfHint();
459
460
        return [
461
            [
462
                BaseClass::class,
463
                new BaseClass(),
464
                'publicMethod',
465
                [],
466
                'publicMethodDefault'
467
            ],
468
            [
469
                BaseClass::class,
470
                new BaseClass(),
471
                'publicTypeHintedMethod',
472
                [new stdClass()],
473
                'publicTypeHintedMethodDefault'
474
            ],
475
            [
476
                BaseClass::class,
477
                new BaseClass(),
478
                'publicByReferenceMethod',
479
                [],
480
                'publicByReferenceMethodDefault'
481
            ],
482
            [
483
                BaseInterface::class,
484
                new BaseClass(),
485
                'publicMethod',
486
                [],
487
                'publicMethodDefault'
488
            ],
489
            [
490
                ClassWithSelfHint::class,
491
                new ClassWithSelfHint(),
492
                'selfHintMethod',
493
                ['parameter' => $selfHintParam],
494
                $selfHintParam
495
            ],
496
            [
497
                ClassWithMethodWithVariadicFunction::class,
498
                new ClassWithMethodWithVariadicFunction(),
499
                'buz',
500
                ['Ocramius', 'Malukenho'],
501
                ['Ocramius', 'Malukenho']
502
            ],
503
            [
504
                ClassWithMethodWithByRefVariadicFunction::class,
505
                new ClassWithMethodWithByRefVariadicFunction(),
506
                'tuz',
507
                ['Ocramius', 'Malukenho'],
508
                ['Ocramius', 'changed']
509
            ]
510
        ];
511
    }
512
513
    /**
514
     * Generates proxies and instances with a public property to feed to the property accessor methods
515
     *
516
     * @return array
517
     */
518
    public function getPropertyAccessProxies() : array
519
    {
520
        $instance1 = new BaseClass();
521
        $proxyName1 = $this->generateProxy(get_class($instance1));
522
        $instance2 = new BaseClass();
523
        $proxyName2 = $this->generateProxy(get_class($instance2));
524
525
        return [
526
            [
527
                $instance1,
528
                $proxyName1::staticProxyConstructor(
529
                    $this->createInitializer(BaseClass::class, $instance1)
530
                ),
531
                'publicProperty',
532
                'publicPropertyDefault',
533
            ],
534
            [
535
                $instance2,
536
                unserialize(serialize($proxyName2::staticProxyConstructor(
537
                    $this->createInitializer(BaseClass::class, $instance2)
538
                ))),
539
                'publicProperty',
540
                'publicPropertyDefault',
541
            ],
542
        ];
543
    }
544
545
    /**
546
     * @group 276
547
     *
548
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
549
     *
550
     * @param object $callerObject
551
     * @param object $realInstance
552
     * @param string $method
553
     * @param string $expectedValue
554
     */
555
    public function testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope(
556
        $callerObject,
557
        $realInstance,
558
        string $method,
559
        string $expectedValue
560
    ) {
561
        $proxyName = $this->generateProxy(get_class($realInstance));
562
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
563
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer(get_class($realInstance), $realInstance));
564
565
        /* @var $accessor callable */
566
        $accessor = [$callerObject, $method];
567
568
        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...
569
        self::assertSame($expectedValue, $accessor($proxy));
570
        self::assertTrue($proxy->isProxyInitialized());
571
    }
572
573
    /**
574
     * @group 276
575
     *
576
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
577
     *
578
     * @param object $callerObject
579
     * @param object $realInstance
580
     * @param string $method
581
     * @param string $expectedValue
582
     */
583
    public function testWillFetchMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope(
584
        $callerObject,
585
        $realInstance,
586
        string $method,
587
        string $expectedValue
588
    ) {
589
        $proxyName = $this->generateProxy(get_class($realInstance));
590
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
591
        $proxy = unserialize(serialize(
592
            $proxyName::staticProxyConstructor($this->createInitializer(get_class($realInstance), $realInstance))
593
        ));
594
595
        /* @var $accessor callable */
596
        $accessor = [$callerObject, $method];
597
598
        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...
599
        self::assertSame($expectedValue, $accessor($proxy));
600
    }
601
602
    /**
603
     * @group 276
604
     *
605
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
606
     *
607
     * @param object $callerObject
608
     * @param object $realInstance
609
     * @param string $method
610
     * @param string $expectedValue
611
     */
612
    public function testWillFetchMembersOfOtherClonedProxiesWithTheSamePrivateScope(
613
        $callerObject,
614
        $realInstance,
615
        string $method,
616
        string $expectedValue
617
    ) {
618
        $proxyName = $this->generateProxy(get_class($realInstance));
619
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
620
        $proxy = clone $proxyName::staticProxyConstructor(
621
            $this->createInitializer(get_class($realInstance), $realInstance)
622
        );
623
624
        /* @var $accessor callable */
625
        $accessor = [$callerObject, $method];
626
627
        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...
628
        self::assertSame($expectedValue, $accessor($proxy));
629
    }
630
631
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : \Generator
632
    {
633
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
634
635
        foreach ((new \ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
636
            $propertyName  = $property->getName();
637
            $expectedValue = uniqid('', true);
638
639
            // callee is an actual object
640
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
641
                new OtherObjectAccessClass(),
642
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
643
                'get' . ucfirst($propertyName),
644
                $expectedValue,
645
            ];
646
647
            $expectedValue = uniqid('', true);
648
649
            // callee is a proxy (not to be lazy-loaded!)
650
            yield '(proxy) ' . OtherObjectAccessClass::class . '#$' . $propertyName => [
651
                $proxyClass::staticProxyConstructor($this->createInitializer(
652
                    OtherObjectAccessClass::class,
653
                    new OtherObjectAccessClass()
654
                )),
655
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
656
                'get' . ucfirst($propertyName),
657
                $expectedValue,
658
            ];
659
        }
660
    }
661
662
    /**
663
     * @param object $instance
664
     * @param array  $values
665
     *
666
     * @return object
667
     */
668
    private function buildInstanceWithValues($instance, array $values)
669
    {
670
        foreach ($values as $property => $value) {
671
            $property = new \ReflectionProperty($instance, $property);
672
673
            $property->setAccessible(true);
674
675
            $property->setValue($instance, $value);
676
        }
677
678
        return $instance;
679
    }
680
}
681