Completed
Push — master ( aaf507...671bd1 )
by Jefersson
03:34
created

testClonedSkippedPropertiesArePreserved()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 47
Code Lines 29

Duplication

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1288
        self::assertSame($protectedPropertyValue, $friendObject->getProtectedProperty($proxy));
0 ignored issues
show
Documentation introduced by
$proxy is of type object<ProxyManagerTestA...y\LazyLoadingInterface>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1289
        self::assertSame($publicPropertyValue, $friendObject->getPublicProperty($proxy));
0 ignored issues
show
Documentation introduced by
$proxy is of type object<ProxyManagerTestA...y\LazyLoadingInterface>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1290
    }
1291
1292
    public function testClonedSkippedPropertiesArePreserved()
1293
    {
1294
1295
        $proxyName = $this->generateProxy(
1296
            BaseClass::class,
1297
            [
1298
                'skippedProperties' => [
1299
                    "\0" . BaseClass::class . "\0privateProperty",
1300
                    "\0*\0protectedProperty",
1301
                    'publicProperty'
1302
                ],
1303
            ]
1304
        );
1305
1306
        /* @var $proxy BaseClass|GhostObjectInterface */
1307
        $proxy = $proxyName::staticProxyConstructor(function ($proxy) {
1308
            $proxy->setProxyInitializer(null);
1309
        });
1310
1311
        $reflectionPrivate   = new \ReflectionProperty(BaseClass::class, 'privateProperty');
1312
        $reflectionProtected = new \ReflectionProperty(BaseClass::class, 'protectedProperty');
1313
1314
        $reflectionPrivate->setAccessible(true);
1315
        $reflectionProtected->setAccessible(true);
1316
1317
        $privateValue   = uniqid('', true);
1318
        $protectedValue = uniqid('', true);
1319
        $publicValue    = uniqid('', true);
1320
1321
        $reflectionPrivate->setValue($proxy, $privateValue);
1322
        $reflectionProtected->setValue($proxy, $protectedValue);
1323
        $proxy->publicProperty = $publicValue;
1324
1325
        self::assertFalse($proxy->isProxyInitialized());
0 ignored issues
show
Bug introduced by
The method isProxyInitialized does only exist in ProxyManager\Proxy\GhostObjectInterface, but not in ProxyManagerTestAsset\BaseClass.

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...
1326
1327
        $clone = clone $proxy;
1328
1329
        self::assertFalse($proxy->isProxyInitialized());
1330
        self::assertTrue($clone->isProxyInitialized());
1331
1332
        self::assertSame($privateValue, $reflectionPrivate->getValue($proxy));
1333
        self::assertSame($privateValue, $reflectionPrivate->getValue($clone));
1334
        self::assertSame($protectedValue, $reflectionProtected->getValue($proxy));
1335
        self::assertSame($protectedValue, $reflectionProtected->getValue($clone));
1336
        self::assertSame($publicValue, $proxy->publicProperty);
1337
        self::assertSame($publicValue, $clone->publicProperty);
1338
    }
1339
}
1340