Completed
Push — master ( 9f8061...f01068 )
by Marco
13s
created

Functional/LazyLoadingGhostFunctionalTest.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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