Completed
Pull Request — master (#423)
by Marco
07:12
created

testMethodCallsAfterUnSerialization()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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

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...
1387
1388
        self::assertSame($initialCounter + $increment, $proxy->counter);
1389
    }
1390
1391
    private static function isPropertyInitialized(object $object, ReflectionProperty $property) : bool
1392
    {
1393
        return array_key_exists(
1394
            ($property->isProtected() ? "\0*\0" : '')
1395
            . ($property->isPrivate() ? "\0" . $property->getDeclaringClass()->getName() . "\0" : '')
1396
            . $property->getName(),
1397
            (array) $object
1398
        );
1399
    }
1400
}
1401