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

LazyLoadingGhostFunctionalTest   F

Complexity

Total Complexity 46

Size/Duplication

Total Lines 1169
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 46
c 1
b 0
f 0
lcom 1
cbo 22
dl 0
loc 1169
rs 3.6553

42 Methods

Rating   Name   Duplication   Size   Complexity  
A testMethodCallsThatLazyLoadTheObject() 0 17 1
B testMethodCallsThatDoNotLazyLoadTheObject() 0 27 1
A testMethodCallsAfterUnSerialization() 0 18 1
A testMethodCallsAfterCloning() 0 17 1
A testPropertyReadAccess() 0 5 1
A testPropertyWriteAccess() 0 8 1
A testPropertyExistence() 0 5 1
A testPropertyAbsence() 0 6 1
A testPropertyUnset() 0 8 1
A testCanWriteToArrayKeysInPublicProperty() 0 17 1
A testWillNotModifyRetrievedPublicProperties() 0 17 1
A testWillModifyByRefRetrievedPublicProperties() 0 17 1
A testKeepsInitializerWhenNotOverwitten() 0 13 1
A testKeepsInitializedPublicProperties() 0 20 1
A testPublicPropertyDefaultWillBePreserved() 0 10 1
A testProtectedPropertyDefaultWillBePreserved() 0 14 1
A testPrivatePropertyDefaultWillBePreserved() 0 14 1
A testMultiLevelPrivatePropertiesDefaultsWillBePreserved() 0 17 1
A testMultiLevelPrivatePropertiesByRefInitialization() 0 22 1
B testGetPropertyFromDifferentProxyInstances() 0 34 1
A testSetPrivatePropertyOnDifferentProxyInstances() 0 23 1
B testIssetPrivatePropertyOnDifferentProxyInstances() 0 24 1
B testUnsetPrivatePropertyOnDifferentProxyInstances() 0 26 1
B testIssetPrivateAndProtectedPropertiesDoesCheckAgainstBooleanFalse() 0 32 1
B testByRefInitialization() 0 27 2
A testWillBehaveLikeObjectWithNormalConstructor() 0 21 1
A testInitializeProxyWillReturnTrueOnSuccessfulInitialization() 0 14 1
A generateProxy() 0 14 1
B createInitializer() 0 41 4
A getProxyMethods() 0 49 1
B getProxyInitializingMethods() 0 35 1
A getProxyNonInitializingMethods() 0 4 1
B getPropertyAccessProxies() 0 24 1
A testInitializationIsSkippedForSkippedProperties() 0 17 1
B testSkippedPropertiesAreNotOverwrittenOnInitialization() 0 30 1
A testWillForwardVariadicByRefArguments() 0 17 1
A testWillForwardDynamicArguments() 0 11 1
A skipPropertiesFixture() 0 56 1
A testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope() 0 23 1
A testWillAccessMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope() 0 22 1
A testWillAccessMembersOfOtherClonedProxiesWithTheSamePrivateScope() 0 22 1
A getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() 0 49 1

How to fix   Complexity   

Complex Class

Complex classes like LazyLoadingGhostFunctionalTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LazyLoadingGhostFunctionalTest, and based on these observations, apply Extract Interface, too.

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\GhostObjectInterface;
27
use ProxyManager\Proxy\LazyLoadingInterface;
28
use ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator;
29
use ProxyManager\ProxyGenerator\Util\Properties;
30
use ProxyManagerTestAsset\BaseClass;
31
use ProxyManagerTestAsset\ClassWithAbstractPublicMethod;
32
use ProxyManagerTestAsset\ClassWithCollidingPrivateInheritedProperties;
33
use ProxyManagerTestAsset\ClassWithCounterConstructor;
34
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod;
35
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction;
36
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction;
37
use ProxyManagerTestAsset\ClassWithMixedProperties;
38
use ProxyManagerTestAsset\ClassWithMixedPropertiesAndAccessorMethods;
39
use ProxyManagerTestAsset\ClassWithPrivateProperties;
40
use ProxyManagerTestAsset\ClassWithProtectedProperties;
41
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
42
use ProxyManagerTestAsset\ClassWithPublicProperties;
43
use ProxyManagerTestAsset\ClassWithSelfHint;
44
use ProxyManagerTestAsset\EmptyClass;
45
use ProxyManagerTestAsset\OtherObjectAccessClass;
46
use ReflectionClass;
47
use ReflectionProperty;
48
49
/**
50
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator} produced objects
51
 *
52
 * @author Marco Pivetta <[email protected]>
53
 * @license MIT
54
 *
55
 * @group Functional
56
 * @coversNothing
57
 */
58
class LazyLoadingGhostFunctionalTest extends PHPUnit_Framework_TestCase
59
{
60
    /**
61
     * @dataProvider getProxyInitializingMethods
62
     *
63
     * @param string  $className
64
     * @param object  $instance
65
     * @param string  $method
66
     * @param mixed[] $params
67
     * @param mixed   $expectedValue
68
     */
69
    public function testMethodCallsThatLazyLoadTheObject($className, $instance, $method, array $params, $expectedValue)
70
    {
71
        $proxyName = $this->generateProxy($className);
72
73
        /* @var $proxy GhostObjectInterface */
74
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
75
76
        $this->assertFalse($proxy->isProxyInitialized());
77
78
        /* @var $callProxyMethod callable */
79
        $callProxyMethod = [$proxy, $method];
80
        $parameterValues = array_values($params);
81
82
        self::assertInternalType('callable', $callProxyMethod);
83
        $this->assertSame($expectedValue, $callProxyMethod(...$parameterValues));
84
        $this->assertTrue($proxy->isProxyInitialized());
85
    }
86
87
    /**
88
     * @dataProvider getProxyNonInitializingMethods
89
     *
90
     * @param string  $className
91
     * @param object  $instance
92
     * @param string  $method
93
     * @param mixed[] $params
94
     * @param mixed   $expectedValue
95
     */
96
    public function testMethodCallsThatDoNotLazyLoadTheObject(
97
        $className,
98
        $instance,
99
        $method,
100
        array $params,
101
        $expectedValue
102
    ) {
103
        $proxyName         = $this->generateProxy($className);
104
        $initializeMatcher = $this->getMock('stdClass', ['__invoke']);
105
106
        $initializeMatcher->expects($this->never())->method('__invoke'); // should not initialize the proxy
107
108
        /* @var $proxy GhostObjectInterface */
109
        $proxy = $proxyName::staticProxyConstructor(
110
            $this->createInitializer($className, $instance, $initializeMatcher)
111
        );
112
113
        self::assertFalse($proxy->isProxyInitialized());
114
115
        /* @var $callProxyMethod callable */
116
        $callProxyMethod = [$proxy, $method];
117
        $parameterValues = array_values($params);
118
119
        self::assertInternalType('callable', $callProxyMethod);
120
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
121
        self::assertFalse($proxy->isProxyInitialized());
122
    }
123
124
    /**
125
     * @dataProvider getProxyMethods
126
     *
127
     * @param string  $className
128
     * @param object  $instance
129
     * @param string  $method
130
     * @param mixed[] $params
131
     * @param mixed   $expectedValue
132
     */
133
    public function testMethodCallsAfterUnSerialization($className, $instance, $method, array $params, $expectedValue)
134
    {
135
        $proxyName = $this->generateProxy($className);
136
137
        /* @var $proxy GhostObjectInterface */
138
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
139
            $this->createInitializer($className, $instance)
140
        )));
141
142
        $this->assertTrue($proxy->isProxyInitialized());
143
144
        /* @var $callProxyMethod callable */
145
        $callProxyMethod = [$proxy, $method];
146
        $parameterValues = array_values($params);
147
148
        self::assertInternalType('callable', $callProxyMethod);
149
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
150
    }
151
152
    /**
153
     * @dataProvider getProxyMethods
154
     *
155
     * @param string  $className
156
     * @param object  $instance
157
     * @param string  $method
158
     * @param mixed[] $params
159
     * @param mixed   $expectedValue
160
     */
161
    public function testMethodCallsAfterCloning($className, $instance, $method, array $params, $expectedValue)
162
    {
163
        $proxyName = $this->generateProxy($className);
164
165
        /* @var $proxy GhostObjectInterface */
166
        $proxy  = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
167
        $cloned = clone $proxy;
168
169
        $this->assertTrue($cloned->isProxyInitialized());
170
171
        /* @var $callProxyMethod callable */
172
        $callProxyMethod = [$proxy, $method];
173
        $parameterValues = array_values($params);
174
175
        self::assertInternalType('callable', $callProxyMethod);
176
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
177
    }
178
179
    /**
180
     * @dataProvider getPropertyAccessProxies
181
     *
182
     * @param object               $instance
183
     * @param GhostObjectInterface $proxy
184
     * @param string               $publicProperty
185
     * @param mixed                $propertyValue
186
     */
187
    public function testPropertyReadAccess($instance, GhostObjectInterface $proxy, $publicProperty, $propertyValue)
188
    {
189
        $this->assertSame($propertyValue, $proxy->$publicProperty);
190
        $this->assertTrue($proxy->isProxyInitialized());
191
    }
192
193
    /**
194
     * @dataProvider getPropertyAccessProxies
195
     *
196
     * @param object               $instance
197
     * @param GhostObjectInterface $proxy
198
     * @param string               $publicProperty
199
     */
200
    public function testPropertyWriteAccess($instance, GhostObjectInterface $proxy, $publicProperty)
201
    {
202
        $newValue               = uniqid();
203
        $proxy->$publicProperty = $newValue;
204
205
        $this->assertTrue($proxy->isProxyInitialized());
206
        $this->assertSame($newValue, $proxy->$publicProperty);
207
    }
208
209
    /**
210
     * @dataProvider getPropertyAccessProxies
211
     *
212
     * @param object               $instance
213
     * @param GhostObjectInterface $proxy
214
     * @param string               $publicProperty
215
     */
216
    public function testPropertyExistence($instance, GhostObjectInterface $proxy, $publicProperty)
217
    {
218
        $this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
219
        $this->assertTrue($proxy->isProxyInitialized());
220
    }
221
222
    /**
223
     * @dataProvider getPropertyAccessProxies
224
     *
225
     * @param object               $instance
226
     * @param GhostObjectInterface $proxy
227
     * @param string               $publicProperty
228
     */
229
    public function testPropertyAbsence($instance, GhostObjectInterface $proxy, $publicProperty)
230
    {
231
        $proxy->$publicProperty = null;
232
        $this->assertFalse(isset($proxy->$publicProperty));
233
        $this->assertTrue($proxy->isProxyInitialized());
234
    }
235
236
    /**
237
     * @dataProvider getPropertyAccessProxies
238
     *
239
     * @param object               $instance
240
     * @param GhostObjectInterface $proxy
241
     * @param string               $publicProperty
242
     */
243
    public function testPropertyUnset($instance, GhostObjectInterface $proxy, $publicProperty)
244
    {
245
        unset($proxy->$publicProperty);
246
247
        $this->assertTrue($proxy->isProxyInitialized());
248
        $this->assertTrue(isset($instance->$publicProperty));
249
        $this->assertFalse(isset($proxy->$publicProperty));
250
    }
251
252
    /**
253
     * Verifies that accessing a public property containing an array behaves like in a normal context
254
     */
255
    public function testCanWriteToArrayKeysInPublicProperty()
256
    {
257
        $instance    = new ClassWithPublicArrayProperty();
258
        $className   = get_class($instance);
259
        $initializer = $this->createInitializer($className, $instance);
260
        $proxyName   = $this->generateProxy($className);
261
        /* @var $proxy ClassWithPublicArrayProperty */
262
        $proxy       = $proxyName::staticProxyConstructor($initializer);
263
264
        $proxy->arrayProperty['foo'] = 'bar';
265
266
        $this->assertSame('bar', $proxy->arrayProperty['foo']);
267
268
        $proxy->arrayProperty = ['tab' => 'taz'];
269
270
        $this->assertSame(['tab' => 'taz'], $proxy->arrayProperty);
271
    }
272
273
    /**
274
     * Verifies that public properties retrieved via `__get` don't get modified in the object itself
275
     */
276
    public function testWillNotModifyRetrievedPublicProperties()
277
    {
278
        $instance    = new ClassWithPublicProperties();
279
        $className   = get_class($instance);
280
        $initializer = $this->createInitializer($className, $instance);
281
        $proxyName   = $this->generateProxy($className);
282
        /* @var $proxy ClassWithPublicProperties */
283
        $proxy       = $proxyName::staticProxyConstructor($initializer);
284
        $variable    = $proxy->property0;
285
286
        $this->assertSame('property0', $variable);
287
288
        $variable = 'foo';
289
290
        $this->assertSame('property0', $proxy->property0);
291
        $this->assertSame('foo', $variable);
292
    }
293
294
    /**
295
     * Verifies that public properties references retrieved via `__get` modify in the object state
296
     */
297
    public function testWillModifyByRefRetrievedPublicProperties()
298
    {
299
        $instance    = new ClassWithPublicProperties();
300
        $className   = get_class($instance);
301
        $initializer = $this->createInitializer($className, $instance);
302
        $proxyName   = $this->generateProxy($className);
303
        /* @var $proxy ClassWithPublicProperties */
304
        $proxy       = $proxyName::staticProxyConstructor($initializer);
305
        $variable    = & $proxy->property0;
306
307
        $this->assertSame('property0', $variable);
308
309
        $variable = 'foo';
310
311
        $this->assertSame('foo', $proxy->property0);
312
        $this->assertSame('foo', $variable);
313
    }
314
315
    public function testKeepsInitializerWhenNotOverwitten()
316
    {
317
        $instance    = new BaseClass();
318
        $proxyName   = $this->generateProxy(get_class($instance));
319
        $initializer = function () {
320
        };
321
        /* @var $proxy GhostObjectInterface */
322
        $proxy       = $proxyName::staticProxyConstructor($initializer);
323
324
        $proxy->initializeProxy();
325
326
        $this->assertSame($initializer, $proxy->getProxyInitializer());
327
    }
328
329
    /**
330
     * Verifies that public properties are not being initialized multiple times
331
     */
332
    public function testKeepsInitializedPublicProperties()
333
    {
334
        $instance    = new BaseClass();
335
        $proxyName   = $this->generateProxy(get_class($instance));
336
        $initializer = function (BaseClass $proxy, $method, $parameters, & $initializer) {
337
            $initializer           = null;
338
            $proxy->publicProperty = 'newValue';
339
        };
340
        /* @var $proxy GhostObjectInterface|BaseClass */
341
        $proxy       = $proxyName::staticProxyConstructor($initializer);
342
343
        $proxy->initializeProxy();
344
        $this->assertSame('newValue', $proxy->publicProperty);
345
346
        $proxy->publicProperty = 'otherValue';
347
348
        $proxy->initializeProxy();
349
350
        $this->assertSame('otherValue', $proxy->publicProperty);
351
    }
352
353
    /**
354
     * Verifies that properties' default values are preserved
355
     */
356
    public function testPublicPropertyDefaultWillBePreserved()
357
    {
358
        $instance    = new ClassWithPublicProperties();
359
        $proxyName   = $this->generateProxy(get_class($instance));
360
        /* @var $proxy ClassWithPublicProperties */
361
        $proxy       = $proxyName::staticProxyConstructor(function () {
362
        });
363
364
        $this->assertSame('property0', $proxy->property0);
365
    }
366
367
    /**
368
     * Verifies that protected properties' default values are preserved
369
     */
370
    public function testProtectedPropertyDefaultWillBePreserved()
371
    {
372
        $instance    = new ClassWithProtectedProperties();
373
        $proxyName   = $this->generateProxy(get_class($instance));
374
        /* @var $proxy ClassWithProtectedProperties */
375
        $proxy       = $proxyName::staticProxyConstructor(function () {
376
        });
377
378
        // Check protected property via reflection
379
        $reflectionProperty = new ReflectionProperty($instance, 'property0');
380
        $reflectionProperty->setAccessible(true);
381
382
        $this->assertSame('property0', $reflectionProperty->getValue($proxy));
383
    }
384
385
    /**
386
     * Verifies that private properties' default values are preserved
387
     */
388
    public function testPrivatePropertyDefaultWillBePreserved()
389
    {
390
        $instance  = new ClassWithPrivateProperties();
391
        $proxyName = $this->generateProxy(get_class($instance));
392
        /* @var $proxy ClassWithPrivateProperties */
393
        $proxy     = $proxyName::staticProxyConstructor(function () {
394
        });
395
396
        // Check protected property via reflection
397
        $reflectionProperty = new ReflectionProperty($instance, 'property0');
398
        $reflectionProperty->setAccessible(true);
399
400
        $this->assertSame('property0', $reflectionProperty->getValue($proxy));
401
    }
402
403
    /**
404
     * @group 159
405
     * @group 192
406
     */
407
    public function testMultiLevelPrivatePropertiesDefaultsWillBePreserved()
408
    {
409
        $instance  = new ClassWithCollidingPrivateInheritedProperties();
410
        $proxyName = $this->generateProxy(ClassWithCollidingPrivateInheritedProperties::class);
411
        /* @var $proxy ClassWithPrivateProperties */
412
        $proxy     = $proxyName::staticProxyConstructor(function () {
413
        });
414
415
        $childProperty  = new ReflectionProperty($instance, 'property0');
416
        $parentProperty = new ReflectionProperty(get_parent_class($instance), 'property0');
417
418
        $childProperty->setAccessible(true);
419
        $parentProperty->setAccessible(true);
420
421
        $this->assertSame('childClassProperty0', $childProperty->getValue($proxy));
422
        $this->assertSame('property0', $parentProperty->getValue($proxy));
423
    }
424
425
    /**
426
     * @group 159
427
     * @group 192
428
     */
429
    public function testMultiLevelPrivatePropertiesByRefInitialization()
430
    {
431
        $class     = ClassWithCollidingPrivateInheritedProperties::class;
432
        $proxyName = $this->generateProxy($class);
433
        /* @var $proxy ClassWithPrivateProperties */
434
        $proxy     = $proxyName::staticProxyConstructor(
435
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) {
436
                $initializer = null;
437
                $properties["\0" . $class . "\0property0"] = 'foo';
438
                $properties["\0" . get_parent_class($class) . "\0property0"] = 'bar';
439
            }
440
        );
441
442
        $childProperty  = new ReflectionProperty($class, 'property0');
443
        $parentProperty = new ReflectionProperty(get_parent_class($class), 'property0');
444
445
        $childProperty->setAccessible(true);
446
        $parentProperty->setAccessible(true);
447
448
        $this->assertSame('foo', $childProperty->getValue($proxy));
449
        $this->assertSame('bar', $parentProperty->getValue($proxy));
450
    }
451
452
    /**
453
     * @group 159
454
     * @group 192
455
     *
456
     * Test designed to verify that the cached logic does take into account the fact that
457
     * proxies are different instances
458
     */
459
    public function testGetPropertyFromDifferentProxyInstances()
460
    {
461
        $class     = ClassWithCollidingPrivateInheritedProperties::class;
462
        $proxyName = $this->generateProxy($class);
463
464
        /* @var $proxy1 ClassWithPrivateProperties */
465
        $proxy1    = $proxyName::staticProxyConstructor(
466
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) {
467
                $initializer = null;
468
                $properties["\0" . $class . "\0property0"] = 'foo';
469
                $properties["\0" . get_parent_class($class) . "\0property0"] = 'bar';
470
            }
471
        );
472
        /* @var $proxy2 ClassWithPrivateProperties */
473
        $proxy2    = $proxyName::staticProxyConstructor(
474
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) {
475
                $initializer = null;
476
                $properties["\0" . $class . "\0property0"] = 'baz';
477
                $properties["\0" . get_parent_class($class) . "\0property0"] = 'tab';
478
            }
479
        );
480
481
        $childProperty  = new ReflectionProperty($class, 'property0');
482
        $parentProperty = new ReflectionProperty(get_parent_class($class), 'property0');
483
484
        $childProperty->setAccessible(true);
485
        $parentProperty->setAccessible(true);
486
487
        $this->assertSame('foo', $childProperty->getValue($proxy1));
488
        $this->assertSame('bar', $parentProperty->getValue($proxy1));
489
490
        $this->assertSame('baz', $childProperty->getValue($proxy2));
491
        $this->assertSame('tab', $parentProperty->getValue($proxy2));
492
    }
493
494
    /**
495
     * @group 159
496
     * @group 192
497
     *
498
     * Test designed to verify that the cached logic does take into account the fact that
499
     * proxies are different instances
500
     */
501
    public function testSetPrivatePropertyOnDifferentProxyInstances()
502
    {
503
        $class     = ClassWithMixedPropertiesAndAccessorMethods::class;
504
        $proxyName = $this->generateProxy($class);
505
506
        /* @var $proxy1 ClassWithMixedPropertiesAndAccessorMethods */
507
        $proxy1    = $proxyName::staticProxyConstructor(
508
            function ($proxy, $method, $params, & $initializer) {
509
                $initializer = null;
510
            }
511
        );
512
        /* @var $proxy2 ClassWithMixedPropertiesAndAccessorMethods */
513
        $proxy2    = $proxyName::staticProxyConstructor(
514
            function ($proxy, $method, $params, & $initializer) {
515
                $initializer = null;
516
            }
517
        );
518
519
        $proxy1->set('privateProperty', 'private1');
520
        $proxy2->set('privateProperty', 'private2');
521
        $this->assertSame('private1', $proxy1->get('privateProperty'));
522
        $this->assertSame('private2', $proxy2->get('privateProperty'));
523
    }
524
525
    /**
526
     * @group 159
527
     * @group 192
528
     *
529
     * Test designed to verify that the cached logic does take into account the fact that
530
     * proxies are different instances
531
     */
532
    public function testIssetPrivatePropertyOnDifferentProxyInstances()
533
    {
534
        $class     = ClassWithMixedPropertiesAndAccessorMethods::class;
535
        $proxyName = $this->generateProxy($class);
536
537
        /* @var $proxy1 ClassWithMixedPropertiesAndAccessorMethods */
538
        $proxy1    = $proxyName::staticProxyConstructor(
539
            function ($proxy, $method, $params, & $initializer) {
540
                $initializer = null;
541
            }
542
        );
543
        /* @var $proxy2 ClassWithMixedPropertiesAndAccessorMethods */
544
        $proxy2    = $proxyName::staticProxyConstructor(
545
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) {
546
                $initializer = null;
547
                $properties["\0" . $class . "\0" . "privateProperty"] = null;
548
            }
549
        );
550
551
        $this->assertTrue($proxy1->has('privateProperty'));
552
        $this->assertFalse($proxy2->has('privateProperty'));
553
        $this->assertTrue($proxy1->has('privateProperty'));
554
        $this->assertFalse($proxy2->has('privateProperty'));
555
    }
556
557
    /**
558
     * @group 159
559
     * @group 192
560
     *
561
     * Test designed to verify that the cached logic does take into account the fact that
562
     * proxies are different instances
563
     */
564
    public function testUnsetPrivatePropertyOnDifferentProxyInstances()
565
    {
566
        $class     = ClassWithMixedPropertiesAndAccessorMethods::class;
567
        $proxyName = $this->generateProxy($class);
568
569
        /* @var $proxy1 ClassWithMixedPropertiesAndAccessorMethods */
570
        $proxy1    = $proxyName::staticProxyConstructor(
571
            function ($proxy, $method, $params, & $initializer) {
572
                $initializer = null;
573
            }
574
        );
575
        /* @var $proxy2 ClassWithMixedPropertiesAndAccessorMethods */
576
        $proxy2    = $proxyName::staticProxyConstructor(
577
            function ($proxy, $method, $params, & $initializer) {
578
                $initializer = null;
579
            }
580
        );
581
582
        $this->assertTrue($proxy1->has('privateProperty'));
583
        $proxy2->remove('privateProperty');
584
        $this->assertFalse($proxy2->has('privateProperty'));
585
        $this->assertTrue($proxy1->has('privateProperty'));
586
        $proxy1->remove('privateProperty');
587
        $this->assertFalse($proxy1->has('privateProperty'));
588
        $this->assertFalse($proxy2->has('privateProperty'));
589
    }
590
591
    /**
592
     * @group 159
593
     * @group 192
594
     *
595
     * Test designed to verify that the cached logic does take into account the fact that
596
     * proxies are different instances
597
     */
598
    public function testIssetPrivateAndProtectedPropertiesDoesCheckAgainstBooleanFalse()
599
    {
600
        $class     = ClassWithMixedPropertiesAndAccessorMethods::class;
601
        $proxyName = $this->generateProxy($class);
602
603
        /* @var $proxy1 ClassWithMixedPropertiesAndAccessorMethods */
604
        $proxy1    = $proxyName::staticProxyConstructor(
605
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) {
606
                $initializer = null;
607
                $properties["publicProperty"] = false;
608
                $properties["\0*\0" . "protectedProperty"] = false;
609
                $properties["\0" . $class . "\0" . "privateProperty"] = false;
610
            }
611
        );
612
        /* @var $proxy2 ClassWithMixedPropertiesAndAccessorMethods */
613
        $proxy2    = $proxyName::staticProxyConstructor(
614
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) {
615
                $initializer = null;
616
                $properties["publicProperty"] = null;
617
                $properties["\0*\0" . "protectedProperty"] = null;
618
                $properties["\0" . $class . "\0" . "privateProperty"] = null;
619
            }
620
        );
621
622
        $this->assertTrue($proxy1->has('protectedProperty'));
623
        $this->assertTrue($proxy1->has('publicProperty'));
624
        $this->assertTrue($proxy1->has('privateProperty'));
625
626
        $this->assertFalse($proxy2->has('protectedProperty'));
627
        $this->assertFalse($proxy2->has('publicProperty'));
628
        $this->assertFalse($proxy2->has('privateProperty'));
629
    }
630
631
    public function testByRefInitialization()
632
    {
633
        $proxyName = $this->generateProxy(ClassWithMixedProperties::class);
634
        /* @var $proxy ClassWithPrivateProperties */
635
        $proxy     = $proxyName::staticProxyConstructor(
636
            function ($proxy, $method, $params, & $initializer, array $properties) {
637
                $initializer = null;
638
                $properties["\0" . ClassWithMixedProperties::class . "\0privateProperty0"] = 'private0';
639
                $properties["\0" . ClassWithMixedProperties::class . "\0privateProperty1"] = 'private1';
640
                $properties["\0" . ClassWithMixedProperties::class . "\0privateProperty2"] = 'private2';
641
                $properties["\0*\0protectedProperty0"] = 'protected0';
642
                $properties["\0*\0protectedProperty1"] = 'protected1';
643
                $properties["\0*\0protectedProperty2"] = 'protected2';
644
                $properties["publicProperty0"] = 'public0';
645
                $properties["publicProperty1"] = 'public1';
646
                $properties["publicProperty2"] = 'public2';
647
            }
648
        );
649
650
        $reflectionClass = new ReflectionClass(ClassWithMixedProperties::class);
651
652
        foreach (Properties::fromReflectionClass($reflectionClass)->getInstanceProperties() as $property) {
653
            $property->setAccessible(true);
654
655
            $this->assertSame(str_replace('Property', '', $property->getName()), $property->getValue($proxy));
656
        }
657
    }
658
659
    /**
660
     * @group 115
661
     * @group 175
662
     */
663
    public function testWillBehaveLikeObjectWithNormalConstructor()
664
    {
665
        $instance = new ClassWithCounterConstructor(10);
666
667
        $this->assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
668
        $this->assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
669
        $instance->__construct(3);
670
        $this->assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
671
        $this->assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected');
672
673
        $proxyName = $this->generateProxy(get_class($instance));
674
675
        /* @var $proxy ClassWithCounterConstructor */
676
        $proxy = new $proxyName(15);
677
678
        $this->assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
679
        $this->assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
680
        $proxy->__construct(5);
681
        $this->assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
682
        $this->assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
683
    }
684
685
    public function testInitializeProxyWillReturnTrueOnSuccessfulInitialization()
686
    {
687
        $proxyName = $this->generateProxy(ClassWithMixedProperties::class);
688
689
        /* @var $proxy GhostObjectInterface */
690
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer(
691
            ClassWithMixedProperties::class,
692
            new ClassWithMixedProperties()
693
        ));
694
695
        $this->assertTrue($proxy->initializeProxy());
696
        $this->assertTrue($proxy->isProxyInitialized());
697
        $this->assertFalse($proxy->initializeProxy());
698
    }
699
700
    /**
701
     * Generates a proxy for the given class name, and retrieves its class name
702
     *
703
     * @param string  $parentClassName
704
     * @param mixed[] $proxyOptions
705
     *
706
     * @return string
707
     */
708
    private function generateProxy($parentClassName, array $proxyOptions = [])
709
    {
710
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
711
        $generatedClass     = new ClassGenerator($generatedClassName);
712
713
        (new LazyLoadingGhostGenerator())->generate(
714
            new ReflectionClass($parentClassName),
715
            $generatedClass,
716
            $proxyOptions
717
        );
718
        (new EvaluatingGeneratorStrategy())->generate($generatedClass);
719
720
        return $generatedClassName;
721
    }
722
723
    /**
724
     * @param string $className
725
     * @param object $realInstance
726
     * @param Mock   $initializerMatcher
727
     *
728
     * @return \Closure
729
     */
730
    private function createInitializer($className, $realInstance, Mock $initializerMatcher = null)
731
    {
732
        if (null === $initializerMatcher) {
733
            $initializerMatcher = $this->getMock('stdClass', ['__invoke']);
734
735
            $initializerMatcher
736
                ->expects($this->once())
737
                ->method('__invoke')
738
                ->with(
739
                    $this->logicalAnd(
740
                        $this->isInstanceOf(GhostObjectInterface::class),
741
                        $this->isInstanceOf($className)
742
                    )
743
                );
744
        }
745
746
        /* @var $initializerMatcher callable */
747
        $initializerMatcher = $initializerMatcher ?: $this->getMock('stdClass', ['__invoke']);
748
749
        return function (
750
            GhostObjectInterface $proxy,
751
            $method,
752
            $params,
753
            & $initializer
754
        ) use (
755
            $initializerMatcher,
756
            $realInstance
757
        ) {
758
            $initializer     = null;
759
            $reflectionClass = new ReflectionClass($realInstance);
760
761
            foreach (Properties::fromReflectionClass($reflectionClass)->getInstanceProperties() as $property) {
762
                $property->setAccessible(true);
763
                $property->setValue($proxy, $property->getValue($realInstance));
764
            }
765
766
            $initializerMatcher($proxy, $method, $params);
767
768
            return true;
769
        };
770
    }
771
772
    /**
773
     * Generates a list of object | invoked method | parameters | expected result
774
     *
775
     * @return array
776
     */
777
    public function getProxyMethods()
778
    {
779
        $selfHintParam = new ClassWithSelfHint();
780
781
        return [
782
            [
783
                BaseClass::class,
784
                new BaseClass(),
785
                'publicMethod',
786
                [],
787
                'publicMethodDefault'
788
            ],
789
            [
790
                BaseClass::class,
791
                new BaseClass(),
792
                'publicTypeHintedMethod',
793
                [new \stdClass()],
794
                'publicTypeHintedMethodDefault'
795
            ],
796
            [
797
                BaseClass::class,
798
                new BaseClass(),
799
                'publicByReferenceMethod',
800
                [],
801
                'publicByReferenceMethodDefault'
802
            ],
803
            [
804
                ClassWithSelfHint::class,
805
                new ClassWithSelfHint(),
806
                'selfHintMethod',
807
                ['parameter' => $selfHintParam],
808
                $selfHintParam
809
            ],
810
            [
811
                ClassWithAbstractPublicMethod::class,
812
                new EmptyClass(), // EmptyClass just used to not make reflection explode when synchronizing properties
813
                'publicAbstractMethod',
814
                [],
815
                null
816
            ],
817
            [
818
                ClassWithMethodWithByRefVariadicFunction::class,
819
                new ClassWithMethodWithByRefVariadicFunction(),
820
                'tuz',
821
                ['Ocramius', 'Malukenho'],
822
                ['Ocramius', 'changed']
823
            ],
824
        ];
825
    }
826
827
    /**
828
     * Generates a list of object | invoked method | parameters | expected result for methods that cause lazy-loading
829
     * of a ghost object
830
     *
831
     * @return array
832
     */
833
    public function getProxyInitializingMethods()
834
    {
835
        $methods = [
836
            [
837
                BaseClass::class,
838
                new BaseClass(),
839
                'publicPropertyGetter',
840
                [],
841
                'publicPropertyDefault'
842
            ],
843
            [
844
                BaseClass::class,
845
                new BaseClass(),
846
                'protectedPropertyGetter',
847
                [],
848
                'protectedPropertyDefault'
849
            ],
850
            [
851
                BaseClass::class,
852
                new BaseClass(),
853
                'privatePropertyGetter',
854
                [],
855
                'privatePropertyDefault'
856
            ],
857
            [
858
                ClassWithMethodWithVariadicFunction::class,
859
                new ClassWithMethodWithVariadicFunction(),
860
                'foo',
861
                ['Ocramius', 'Malukenho'],
862
                null
863
            ],
864
        ];
865
866
        return $methods;
867
    }
868
869
    /**
870
     * Generates a list of object | invoked method | parameters | expected result for methods DON'T cause lazy-loading
871
     *
872
     * @return array
873
     */
874
    public function getProxyNonInitializingMethods()
875
    {
876
        return $this->getProxyMethods();
877
    }
878
879
    /**
880
     * Generates proxies and instances with a public property to feed to the property accessor methods
881
     *
882
     * @return array
883
     */
884
    public function getPropertyAccessProxies()
885
    {
886
        $instance1 = new BaseClass();
887
        $proxyName1 = $this->generateProxy(get_class($instance1));
888
        $instance2 = new BaseClass();
889
        $proxyName2 = $this->generateProxy(get_class($instance2));
890
891
        return [
892
            [
893
                $instance1,
894
                new $proxyName1($this->createInitializer(BaseClass::class, $instance1)),
895
                'publicProperty',
896
                'publicPropertyDefault',
897
            ],
898
            [
899
                $instance2,
900
                unserialize(
901
                    serialize(new $proxyName2($this->createInitializer(BaseClass::class, $instance2)))
902
                ),
903
                'publicProperty',
904
                'publicPropertyDefault',
905
            ],
906
        ];
907
    }
908
909
    /**
910
     * @dataProvider skipPropertiesFixture
911
     *
912
     * @param string  $className
913
     * @param string  $propertyClass
914
     * @param string  $propertyName
915
     * @param mixed   $expected
916
     * @param mixed[] $proxyOptions
917
     */
918
    public function testInitializationIsSkippedForSkippedProperties(
919
        $className,
920
        $propertyClass,
921
        $propertyName,
922
        array $proxyOptions,
923
        $expected
924
    ) {
925
        $proxy       = $this->generateProxy($className, $proxyOptions);
926
        $ghostObject = $proxy::staticProxyConstructor(function () use ($propertyName) {
927
            $this->fail(sprintf('The Property "%s" was not expected to be lazy-loaded', $propertyName));
928
        });
929
930
        $property = new ReflectionProperty($propertyClass, $propertyName);
931
        $property->setAccessible(true);
932
933
        $this->assertSame($expected, $property->getValue($ghostObject));
934
    }
935
936
    /**
937
     * @dataProvider skipPropertiesFixture
938
     *
939
     * @param string  $className
940
     * @param string  $propertyClass
941
     * @param string  $propertyName
942
     * @param mixed[] $proxyOptions
943
     */
944
    public function testSkippedPropertiesAreNotOverwrittenOnInitialization(
945
        $className,
946
        $propertyClass,
947
        $propertyName,
948
        array $proxyOptions
949
    ) {
950
        $proxyName   = $this->generateProxy($className, $proxyOptions);
951
        /* @var $ghostObject GhostObjectInterface */
952
        $ghostObject = $proxyName::staticProxyConstructor(function ($proxy, $method, $params, & $initializer) {
953
            $initializer = null;
954
955
            return true;
956
        });
957
958
        $property = new ReflectionProperty($propertyClass, $propertyName);
959
960
        $property->setAccessible(true);
961
962
        $value = uniqid('', true);
963
964
        $property->setValue($ghostObject, $value);
965
966
        $this->assertTrue($ghostObject->initializeProxy());
967
968
        $this->assertSame(
969
            $value,
970
            $property->getValue($ghostObject),
971
            'Property should not be changed by proxy initialization'
972
        );
973
    }
974
975
    /**
976
     * @group 265
977
     */
978
    public function testWillForwardVariadicByRefArguments()
979
    {
980
        $proxyName   = $this->generateProxy(ClassWithMethodWithByRefVariadicFunction::class);
981
        /* @var $object ClassWithMethodWithByRefVariadicFunction */
982
        $object = $proxyName::staticProxyConstructor(function ($proxy, $method, $params, & $initializer) {
983
            $initializer = null;
984
985
            return true;
986
        });
987
988
        $parameters = ['a', 'b', 'c'];
989
990
        // first, testing normal variadic behavior (verifying we didn't screw up in the test asset)
991
        self::assertSame(['a', 'changed', 'c'], (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$parameters));
992
        self::assertSame(['a', 'changed', 'c'], $object->tuz(...$parameters));
993
        self::assertSame(['a', 'changed', 'c'], $parameters, 'by-ref variadic parameter was changed');
994
    }
995
996
    /**
997
     * @group 265
998
     */
999
    public function testWillForwardDynamicArguments()
1000
    {
1001
        $proxyName   = $this->generateProxy(ClassWithDynamicArgumentsMethod::class);
1002
        /* @var $object ClassWithDynamicArgumentsMethod */
1003
        $object = $proxyName::staticProxyConstructor(function () {
1004
        });
1005
1006
        // first, testing normal variadic behavior (verifying we didn't screw up in the test asset)
1007
        self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b'));
1008
        self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b'));
1009
    }
1010
1011
1012
    /**
1013
     * @return mixed[] in order:
1014
     *                  - the class to be proxied
1015
     *                  - the class owning the property to be checked
1016
     *                  - the property name
1017
     *                  - the options to be passed to the generator
1018
     *                  - the expected value of the property
1019
     */
1020
    public function skipPropertiesFixture()
1021
    {
1022
        return [
1023
            [
1024
                ClassWithPublicProperties::class,
1025
                ClassWithPublicProperties::class,
1026
                'property9',
1027
                [
1028
                    'skippedProperties' => ["property9"]
1029
                ],
1030
                'property9',
1031
            ],
1032
            [
1033
                ClassWithProtectedProperties::class,
1034
                ClassWithProtectedProperties::class,
1035
                'property9',
1036
                [
1037
                    'skippedProperties' => ["\0*\0property9"]
1038
                ],
1039
                'property9',
1040
            ],
1041
            [
1042
                ClassWithPrivateProperties::class,
1043
                ClassWithPrivateProperties::class,
1044
                'property9',
1045
                [
1046
                    'skippedProperties' => [
1047
                        "\0ProxyManagerTestAsset\\ClassWithPrivateProperties\0property9"
1048
                    ]
1049
                ],
1050
                'property9',
1051
            ],
1052
            [
1053
                ClassWithCollidingPrivateInheritedProperties::class,
1054
                ClassWithCollidingPrivateInheritedProperties::class,
1055
                'property0',
1056
                [
1057
                    'skippedProperties' => [
1058
                        "\0ProxyManagerTestAsset\\ClassWithCollidingPrivateInheritedProperties\0property0"
1059
                    ]
1060
                ],
1061
                'childClassProperty0',
1062
            ],
1063
            [
1064
                ClassWithCollidingPrivateInheritedProperties::class,
1065
                ClassWithPrivateProperties::class,
1066
                'property0',
1067
                [
1068
                    'skippedProperties' => [
1069
                        "\0ProxyManagerTestAsset\\ClassWithPrivateProperties\0property0"
1070
                    ]
1071
                ],
1072
                'property0',
1073
            ],
1074
        ];
1075
    }
1076
1077
    /**
1078
     * @group 276
1079
     *
1080
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
1081
     *
1082
     * @param object $callerObject
1083
     * @param string $method
1084
     * @param string $propertyIndex
1085
     * @param string $expectedValue
1086
     */
1087
    public function testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope(
1088
        $callerObject,
1089
        string $method,
1090
        string $propertyIndex,
1091
        string $expectedValue
1092
    ) {
1093
        $proxyName = $this->generateProxy(get_class($callerObject));
1094
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
1095
        $proxy = $proxyName::staticProxyConstructor(
1096
            function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue) {
1097
                $initializer = null;
1098
1099
                $props[$propertyIndex] = $expectedValue;
1100
            }
1101
        );
1102
1103
        /* @var $accessor callable */
1104
        $accessor = [$callerObject, $method];
1105
1106
        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...
1107
        self::assertSame($expectedValue, $accessor($proxy));
1108
        self::assertTrue($proxy->isProxyInitialized());
1109
    }
1110
1111
    /**
1112
     * @group 276
1113
     *
1114
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
1115
     *
1116
     * @param object $callerObject
1117
     * @param string $method
1118
     * @param string $propertyIndex
1119
     * @param string $expectedValue
1120
     */
1121
    public function testWillAccessMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope(
1122
        $callerObject,
1123
        string $method,
1124
        string $propertyIndex,
1125
        string $expectedValue
1126
    ) {
1127
        $proxyName = $this->generateProxy(get_class($callerObject));
1128
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
1129
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
1130
            function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue) {
1131
                $initializer = null;
1132
1133
                $props[$propertyIndex] = $expectedValue;
1134
            }
1135
        )));
1136
1137
        /* @var $accessor callable */
1138
        $accessor = [$callerObject, $method];
1139
1140
        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...
1141
        self::assertSame($expectedValue, $accessor($proxy));
1142
    }
1143
1144
    /**
1145
     * @group 276
1146
     *
1147
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
1148
     *
1149
     * @param object $callerObject
1150
     * @param string $method
1151
     * @param string $propertyIndex
1152
     * @param string $expectedValue
1153
     */
1154
    public function testWillAccessMembersOfOtherClonedProxiesWithTheSamePrivateScope(
1155
        $callerObject,
1156
        string $method,
1157
        string $propertyIndex,
1158
        string $expectedValue
1159
    ) {
1160
        $proxyName = $this->generateProxy(get_class($callerObject));
1161
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
1162
        $proxy = clone $proxyName::staticProxyConstructor(
1163
            function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue) {
1164
                $initializer = null;
1165
1166
                $props[$propertyIndex] = $expectedValue;
1167
            }
1168
        );
1169
1170
        /* @var $accessor callable */
1171
        $accessor = [$callerObject, $method];
1172
1173
        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...
1174
        self::assertSame($expectedValue, $accessor($proxy));
1175
    }
1176
1177
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : array
1178
    {
1179
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
1180
1181
        return [
1182
            OtherObjectAccessClass::class . '#$privateProperty' => [
1183
                new OtherObjectAccessClass(),
1184
                'getPrivateProperty',
1185
                "\0" . OtherObjectAccessClass::class . "\0privateProperty",
1186
                uniqid('', true),
1187
            ],
1188
            OtherObjectAccessClass::class . '#$protectedProperty' => [
1189
                new OtherObjectAccessClass(),
1190
                'getProtectedProperty',
1191
                "\0*\0protectedProperty",
1192
                uniqid('', true),
1193
            ],
1194
            OtherObjectAccessClass::class . '#$publicProperty' => [
1195
                new OtherObjectAccessClass(),
1196
                'getPublicProperty',
1197
                'publicProperty',
1198
                uniqid('', true),
1199
            ],
1200
            '(proxy) ' . OtherObjectAccessClass::class . '#$privateProperty' => [
1201
                $proxyClass::staticProxyConstructor(function () {
1202
                    self::fail('Should never be initialized, as its values aren\'t accessed');
1203
                }),
1204
                'getPrivateProperty',
1205
                "\0" . OtherObjectAccessClass::class . "\0privateProperty",
1206
                uniqid('', true),
1207
            ],
1208
            '(proxy) ' . OtherObjectAccessClass::class . '#$protectedProperty' => [
1209
                $proxyClass::staticProxyConstructor(function () {
1210
                    self::fail('Should never be initialized, as its values aren\'t accessed');
1211
                }),
1212
                'getProtectedProperty',
1213
                "\0*\0protectedProperty",
1214
                uniqid('', true),
1215
            ],
1216
            '(proxy) ' . OtherObjectAccessClass::class . '#$publicProperty' => [
1217
                $proxyClass::staticProxyConstructor(function () {
1218
                    self::fail('Should never be initialized, as its values aren\'t accessed');
1219
                }),
1220
                'getPublicProperty',
1221
                'publicProperty',
1222
                uniqid('', true),
1223
            ],
1224
        ];
1225
    }
1226
}
1227