Completed
Push — master ( 9af8a4...8336df )
by Marco
11s
created

skipPropertiesFixture()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 9.0909
c 0
b 0
f 0
cc 1
nc 1
nop 0
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\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
use function array_values;
38
use function get_class;
39
use function get_parent_class;
40
use function random_int;
41
use function serialize;
42
use function sprintf;
43
use function str_replace;
44
use function uniqid;
45
use function unserialize;
46
47
/**
48
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator} produced objects
49
 *
50
 * @group Functional
51
 * @coversNothing
52
 */
53
class LazyLoadingGhostFunctionalTest extends TestCase
54
{
55
    /**
56
     * @dataProvider getProxyInitializingMethods
57
     *
58
     * @param mixed[] $params
59
     * @param mixed   $expectedValue
60
     */
61
    public function testMethodCallsThatLazyLoadTheObject(
62
        string $className,
63
        object $instance,
64
        string $method,
65
        array $params,
66
        $expectedValue
67
    ) : void {
68
        $proxyName = $this->generateProxy($className);
69
70
        /** @var GhostObjectInterface $proxy */
71
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
72
73
        self::assertFalse($proxy->isProxyInitialized());
74
75
        /** @var callable $callProxyMethod */
76
        $callProxyMethod = [$proxy, $method];
77
        $parameterValues = array_values($params);
78
79
        self::assertInternalType('callable', $callProxyMethod);
80
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
81
        self::assertTrue($proxy->isProxyInitialized());
82
    }
83
84
    /**
85
     * @dataProvider getProxyNonInitializingMethods
86
     *
87
     * @param mixed[] $params
88
     * @param mixed   $expectedValue
89
     */
90
    public function testMethodCallsThatDoNotLazyLoadTheObject(
91
        string $className,
92
        object $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 GhostObjectInterface $proxy */
103
        $proxy = $proxyName::staticProxyConstructor(
104
            $this->createInitializer($className, $instance, $initializeMatcher)
105
        );
106
107
        self::assertFalse($proxy->isProxyInitialized());
108
109
        /** @var callable $callProxyMethod */
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 mixed[] $params
122
     * @param mixed   $expectedValue
123
     */
124
    public function testMethodCallsAfterUnSerialization(
125
        string $className,
126
        object $instance,
127
        string $method,
128
        array $params,
129
        $expectedValue
130
    ) : void {
131
        $proxyName = $this->generateProxy($className);
132
133
        /** @var GhostObjectInterface $proxy */
134
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
135
            $this->createInitializer($className, $instance)
136
        )));
137
138
        self::assertTrue($proxy->isProxyInitialized());
139
140
        /** @var callable $callProxyMethod */
141
        $callProxyMethod = [$proxy, $method];
142
        $parameterValues = array_values($params);
143
144
        self::assertInternalType('callable', $callProxyMethod);
145
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
146
    }
147
148
    /**
149
     * @dataProvider getProxyMethods
150
     *
151
     * @param mixed[] $params
152
     * @param mixed   $expectedValue
153
     */
154
    public function testMethodCallsAfterCloning(
155
        string $className,
156
        object $instance,
157
        string $method,
158
        array $params,
159
        $expectedValue
160
    ) : void {
161
        $proxyName = $this->generateProxy($className);
162
163
        /** @var GhostObjectInterface $proxy */
164
        $proxy  = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
165
        $cloned = clone $proxy;
166
167
        self::assertTrue($cloned->isProxyInitialized());
168
169
        /** @var callable $callProxyMethod */
170
        $callProxyMethod = [$proxy, $method];
171
        $parameterValues = array_values($params);
172
173
        self::assertInternalType('callable', $callProxyMethod);
174
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
175
    }
176
177
    /**
178
     * @dataProvider getPropertyAccessProxies
179
     *
180
     * @param mixed $propertyValue
181
     */
182
    public function testPropertyReadAccess(
183
        object $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...
184
        GhostObjectInterface $proxy,
185
        string $publicProperty,
186
        $propertyValue
187
    ) : void {
188
        self::assertSame($propertyValue, $proxy->$publicProperty);
189
        self::assertTrue($proxy->isProxyInitialized());
190
    }
191
192
    /**
193
     * @dataProvider getPropertyAccessProxies
194
     *
195
     */
196
    public function testPropertyWriteAccess(object $instance, GhostObjectInterface $proxy, string $publicProperty
0 ignored issues
show
Unused Code introduced by
The parameter $instance is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
197
    ) : void
198
    {
199
        $newValue               = uniqid('', true);
200
        $proxy->$publicProperty = $newValue;
201
202
        self::assertTrue($proxy->isProxyInitialized());
203
        self::assertSame($newValue, $proxy->$publicProperty);
204
    }
205
206
    /**
207
     * @dataProvider getPropertyAccessProxies
208
     *
209
     */
210
    public function testPropertyExistence(object $instance, GhostObjectInterface $proxy, string $publicProperty) : void
211
    {
212
        self::assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
213
        self::assertTrue($proxy->isProxyInitialized());
214
    }
215
216
    /**
217
     * @dataProvider getPropertyAccessProxies
218
     *
219
     */
220
    public function testPropertyAbsence(object $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...
221
    {
222
        $proxy->$publicProperty = null;
223
        self::assertFalse(isset($proxy->$publicProperty));
224
        self::assertTrue($proxy->isProxyInitialized());
225
    }
226
227
    /**
228
     * @dataProvider getPropertyAccessProxies
229
     *
230
     */
231
    public function testPropertyUnset(object $instance, GhostObjectInterface $proxy, string $publicProperty) : void
232
    {
233
        unset($proxy->$publicProperty);
234
235
        self::assertTrue($proxy->isProxyInitialized());
236
        self::assertTrue(isset($instance->$publicProperty));
237
        self::assertFalse(isset($proxy->$publicProperty));
238
    }
239
240
    /**
241
     * Verifies that accessing a public property containing an array behaves like in a normal context
242
     */
243
    public function testCanWriteToArrayKeysInPublicProperty() : void
244
    {
245
        $instance    = new ClassWithPublicArrayProperty();
246
        $className   = get_class($instance);
247
        $initializer = $this->createInitializer($className, $instance);
0 ignored issues
show
Documentation introduced by
$instance is of type object<ProxyManagerTestA...ithPublicArrayProperty>, but the function expects a object<ProxyManagerTest\Functional\object>.

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...
248
        $proxyName   = $this->generateProxy($className);
249
        /** @var ClassWithPublicArrayProperty $proxy */
250
        $proxy = $proxyName::staticProxyConstructor($initializer);
251
252
        $proxy->arrayProperty['foo'] = 'bar';
253
254
        self::assertSame('bar', $proxy->arrayProperty['foo']);
255
256
        $proxy->arrayProperty = ['tab' => 'taz'];
257
258
        self::assertSame(['tab' => 'taz'], $proxy->arrayProperty);
259
    }
260
261
    /**
262
     * Verifies that public properties retrieved via `__get` don't get modified in the object itself
263
     */
264
    public function testWillNotModifyRetrievedPublicProperties() : void
265
    {
266
        $instance    = new ClassWithPublicProperties();
267
        $className   = get_class($instance);
268
        $initializer = $this->createInitializer($className, $instance);
0 ignored issues
show
Documentation introduced by
$instance is of type object<ProxyManagerTestA...ssWithPublicProperties>, but the function expects a object<ProxyManagerTest\Functional\object>.

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...
269
        $proxyName   = $this->generateProxy($className);
270
        /** @var ClassWithPublicProperties $proxy */
271
        $proxy    = $proxyName::staticProxyConstructor($initializer);
272
        $variable = $proxy->property0;
273
274
        self::assertSame('property0', $variable);
275
276
        $variable = 'foo';
277
278
        self::assertSame('property0', $proxy->property0);
279
        self::assertSame('foo', $variable);
280
    }
281
282
    /**
283
     * Verifies that public properties references retrieved via `__get` modify in the object state
284
     */
285
    public function testWillModifyByRefRetrievedPublicProperties() : void
286
    {
287
        $instance    = new ClassWithPublicProperties();
288
        $className   = get_class($instance);
289
        $initializer = $this->createInitializer($className, $instance);
0 ignored issues
show
Documentation introduced by
$instance is of type object<ProxyManagerTestA...ssWithPublicProperties>, but the function expects a object<ProxyManagerTest\Functional\object>.

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...
290
        $proxyName   = $this->generateProxy($className);
291
        /** @var ClassWithPublicProperties $proxy */
292
        $proxy    = $proxyName::staticProxyConstructor($initializer);
293
        $variable = &$proxy->property0;
294
295
        self::assertSame('property0', $variable);
296
297
        $variable = 'foo';
298
299
        self::assertSame('foo', $proxy->property0);
300
        self::assertSame('foo', $variable);
301
    }
302
303
    public function testKeepsInitializerWhenNotOverwitten() : void
304
    {
305
        $instance    = new BaseClass();
306
        $proxyName   = $this->generateProxy(get_class($instance));
307
        $initializer = function () : void {
308
        };
309
        /** @var GhostObjectInterface $proxy */
310
        $proxy = $proxyName::staticProxyConstructor($initializer);
311
312
        $proxy->initializeProxy();
313
314
        self::assertSame($initializer, $proxy->getProxyInitializer());
315
    }
316
317
    /**
318
     * Verifies that public properties are not being initialized multiple times
319
     */
320
    public function testKeepsInitializedPublicProperties() : void
321
    {
322
        $instance    = new BaseClass();
323
        $proxyName   = $this->generateProxy(get_class($instance));
324
        $initializer = function (BaseClass $proxy, string $method, $parameters, & $initializer) : void {
325
            $initializer           = null;
326
            $proxy->publicProperty = 'newValue';
327
        };
328
        /** @var GhostObjectInterface&BaseClass $proxy */
0 ignored issues
show
Documentation introduced by
The doc-type GhostObjectInterface&BaseClass could not be parsed: Unknown type name "GhostObjectInterface&BaseClass" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
329
        $proxy = $proxyName::staticProxyConstructor($initializer);
330
331
        $proxy->initializeProxy();
332
        self::assertSame('newValue', $proxy->publicProperty);
333
334
        $proxy->publicProperty = 'otherValue';
335
336
        $proxy->initializeProxy();
337
338
        self::assertSame('otherValue', $proxy->publicProperty);
339
    }
340
341
    /**
342
     * Verifies that properties' default values are preserved
343
     */
344
    public function testPublicPropertyDefaultWillBePreserved() : void
345
    {
346
        $instance  = new ClassWithPublicProperties();
347
        $proxyName = $this->generateProxy(get_class($instance));
348
        /** @var ClassWithPublicProperties $proxy */
349
        $proxy = $proxyName::staticProxyConstructor(function () : void {
350
        });
351
352
        self::assertSame('property0', $proxy->property0);
353
    }
354
355
    /**
356
     * Verifies that protected properties' default values are preserved
357
     */
358
    public function testProtectedPropertyDefaultWillBePreserved() : void
359
    {
360
        $instance  = new ClassWithProtectedProperties();
361
        $proxyName = $this->generateProxy(get_class($instance));
362
        /** @var ClassWithProtectedProperties $proxy */
363
        $proxy = $proxyName::staticProxyConstructor(function () : void {
364
        });
365
366
        // Check protected property via reflection
367
        $reflectionProperty = new ReflectionProperty($instance, 'property0');
368
        $reflectionProperty->setAccessible(true);
369
370
        self::assertSame('property0', $reflectionProperty->getValue($proxy));
371
    }
372
373
    /**
374
     * Verifies that private properties' default values are preserved
375
     */
376
    public function testPrivatePropertyDefaultWillBePreserved() : void
377
    {
378
        $instance  = new ClassWithPrivateProperties();
379
        $proxyName = $this->generateProxy(get_class($instance));
380
        /** @var ClassWithPrivateProperties $proxy */
381
        $proxy = $proxyName::staticProxyConstructor(function () : void {
382
        });
383
384
        // Check protected property via reflection
385
        $reflectionProperty = new ReflectionProperty($instance, 'property0');
386
        $reflectionProperty->setAccessible(true);
387
388
        self::assertSame('property0', $reflectionProperty->getValue($proxy));
389
    }
390
391
    /**
392
     * @group 159
393
     * @group 192
394
     */
395
    public function testMultiLevelPrivatePropertiesDefaultsWillBePreserved() : void
396
    {
397
        $instance  = new ClassWithCollidingPrivateInheritedProperties();
398
        $proxyName = $this->generateProxy(ClassWithCollidingPrivateInheritedProperties::class);
399
        /** @var ClassWithPrivateProperties $proxy */
400
        $proxy = $proxyName::staticProxyConstructor(function () : void {
401
        });
402
403
        $childProperty  = new ReflectionProperty($instance, 'property0');
404
        $parentProperty = new ReflectionProperty(get_parent_class($instance), 'property0');
405
406
        $childProperty->setAccessible(true);
407
        $parentProperty->setAccessible(true);
408
409
        self::assertSame('childClassProperty0', $childProperty->getValue($proxy));
410
        self::assertSame('property0', $parentProperty->getValue($proxy));
411
    }
412
413
    /**
414
     * @group 159
415
     * @group 192
416
     */
417
    public function testMultiLevelPrivatePropertiesByRefInitialization() : void
418
    {
419
        $class     = ClassWithCollidingPrivateInheritedProperties::class;
420
        $proxyName = $this->generateProxy($class);
421
        /** @var ClassWithPrivateProperties $proxy */
422
        $proxy = $proxyName::staticProxyConstructor(
423
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void {
424
                $initializer                                                 = null;
425
                $properties["\0" . $class . "\0property0"]                   = 'foo';
426
                $properties["\0" . get_parent_class($class) . "\0property0"] = 'bar';
427
            }
428
        );
429
430
        $childProperty  = new ReflectionProperty($class, 'property0');
431
        $parentProperty = new ReflectionProperty(get_parent_class($class), 'property0');
432
433
        $childProperty->setAccessible(true);
434
        $parentProperty->setAccessible(true);
435
436
        self::assertSame('foo', $childProperty->getValue($proxy));
437
        self::assertSame('bar', $parentProperty->getValue($proxy));
438
    }
439
440
    /**
441
     * @group 159
442
     * @group 192
443
     *
444
     * Test designed to verify that the cached logic does take into account the fact that
445
     * proxies are different instances
446
     */
447
    public function testGetPropertyFromDifferentProxyInstances() : void
448
    {
449
        $class     = ClassWithCollidingPrivateInheritedProperties::class;
450
        $proxyName = $this->generateProxy($class);
451
452
        /** @var ClassWithPrivateProperties $proxy1 */
453
        $proxy1 = $proxyName::staticProxyConstructor(
454
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void {
455
                $initializer                                                 = null;
456
                $properties["\0" . $class . "\0property0"]                   = 'foo';
457
                $properties["\0" . get_parent_class($class) . "\0property0"] = 'bar';
458
            }
459
        );
460
        /** @var ClassWithPrivateProperties $proxy2 */
461
        $proxy2 = $proxyName::staticProxyConstructor(
462
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void {
463
                $initializer                                                 = null;
464
                $properties["\0" . $class . "\0property0"]                   = 'baz';
465
                $properties["\0" . get_parent_class($class) . "\0property0"] = 'tab';
466
            }
467
        );
468
469
        $childProperty  = new ReflectionProperty($class, 'property0');
470
        $parentProperty = new ReflectionProperty(get_parent_class($class), 'property0');
471
472
        $childProperty->setAccessible(true);
473
        $parentProperty->setAccessible(true);
474
475
        self::assertSame('foo', $childProperty->getValue($proxy1));
476
        self::assertSame('bar', $parentProperty->getValue($proxy1));
477
478
        self::assertSame('baz', $childProperty->getValue($proxy2));
479
        self::assertSame('tab', $parentProperty->getValue($proxy2));
480
    }
481
482
    /**
483
     * @group 159
484
     * @group 192
485
     *
486
     * Test designed to verify that the cached logic does take into account the fact that
487
     * proxies are different instances
488
     */
489
    public function testSetPrivatePropertyOnDifferentProxyInstances() : void
490
    {
491
        $class     = ClassWithMixedPropertiesAndAccessorMethods::class;
492
        $proxyName = $this->generateProxy($class);
493
494
        /** @var ClassWithMixedPropertiesAndAccessorMethods $proxy1 */
495
        $proxy1 = $proxyName::staticProxyConstructor(
496
            function ($proxy, $method, $params, & $initializer) : void {
497
                $initializer = null;
498
            }
499
        );
500
        /** @var ClassWithMixedPropertiesAndAccessorMethods $proxy2 */
501
        $proxy2 = $proxyName::staticProxyConstructor(
502
            function ($proxy, $method, $params, & $initializer) : void {
503
                $initializer = null;
504
            }
505
        );
506
507
        $proxy1->set('privateProperty', 'private1');
508
        $proxy2->set('privateProperty', 'private2');
509
        self::assertSame('private1', $proxy1->get('privateProperty'));
510
        self::assertSame('private2', $proxy2->get('privateProperty'));
511
    }
512
513
    /**
514
     * @group 159
515
     * @group 192
516
     *
517
     * Test designed to verify that the cached logic does take into account the fact that
518
     * proxies are different instances
519
     */
520
    public function testIssetPrivatePropertyOnDifferentProxyInstances() : void
521
    {
522
        $class     = ClassWithMixedPropertiesAndAccessorMethods::class;
523
        $proxyName = $this->generateProxy($class);
524
525
        /** @var ClassWithMixedPropertiesAndAccessorMethods $proxy1 */
526
        $proxy1 = $proxyName::staticProxyConstructor(
527
            function ($proxy, $method, $params, & $initializer) : void {
528
                $initializer = null;
529
            }
530
        );
531
        /** @var ClassWithMixedPropertiesAndAccessorMethods $proxy2 */
532
        $proxy2 = $proxyName::staticProxyConstructor(
533
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void {
534
                $initializer                                     = null;
535
                $properties["\0" . $class . "\0privateProperty"] = null;
536
            }
537
        );
538
539
        self::assertTrue($proxy1->has('privateProperty'));
540
        self::assertFalse($proxy2->has('privateProperty'));
541
        self::assertTrue($proxy1->has('privateProperty'));
542
        self::assertFalse($proxy2->has('privateProperty'));
543
    }
544
545
    /**
546
     * @group 159
547
     * @group 192
548
     *
549
     * Test designed to verify that the cached logic does take into account the fact that
550
     * proxies are different instances
551
     */
552
    public function testUnsetPrivatePropertyOnDifferentProxyInstances() : void
553
    {
554
        $class     = ClassWithMixedPropertiesAndAccessorMethods::class;
555
        $proxyName = $this->generateProxy($class);
556
557
        /** @var ClassWithMixedPropertiesAndAccessorMethods $proxy1 */
558
        $proxy1 = $proxyName::staticProxyConstructor(
559
            function ($proxy, $method, $params, & $initializer) : void {
560
                $initializer = null;
561
            }
562
        );
563
        /** @var ClassWithMixedPropertiesAndAccessorMethods $proxy2 */
564
        $proxy2 = $proxyName::staticProxyConstructor(
565
            function ($proxy, $method, $params, & $initializer) : void {
566
                $initializer = null;
567
            }
568
        );
569
570
        self::assertTrue($proxy1->has('privateProperty'));
571
        $proxy2->remove('privateProperty');
572
        self::assertFalse($proxy2->has('privateProperty'));
573
        self::assertTrue($proxy1->has('privateProperty'));
574
        $proxy1->remove('privateProperty');
575
        self::assertFalse($proxy1->has('privateProperty'));
576
        self::assertFalse($proxy2->has('privateProperty'));
577
    }
578
579
    /**
580
     * @group 159
581
     * @group 192
582
     *
583
     * Test designed to verify that the cached logic does take into account the fact that
584
     * proxies are different instances
585
     */
586
    public function testIssetPrivateAndProtectedPropertiesDoesCheckAgainstBooleanFalse() : void
587
    {
588
        $class     = ClassWithMixedPropertiesAndAccessorMethods::class;
589
        $proxyName = $this->generateProxy($class);
590
591
        /** @var ClassWithMixedPropertiesAndAccessorMethods $proxy1 */
592
        $proxy1 = $proxyName::staticProxyConstructor(
593
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void {
594
                $initializer                                     = null;
595
                $properties['publicProperty']                    = false;
596
                $properties["\0*\0protectedProperty"]            = false;
597
                $properties["\0" . $class . "\0privateProperty"] = false;
598
            }
599
        );
600
        /** @var ClassWithMixedPropertiesAndAccessorMethods $proxy2 */
601
        $proxy2 = $proxyName::staticProxyConstructor(
602
            function ($proxy, $method, $params, & $initializer, array $properties) use ($class) : void {
603
                $initializer                                     = null;
604
                $properties['publicProperty']                    = null;
605
                $properties["\0*\0protectedProperty"]            = null;
606
                $properties["\0" . $class . "\0privateProperty"] = null;
607
            }
608
        );
609
610
        self::assertTrue($proxy1->has('protectedProperty'));
611
        self::assertTrue($proxy1->has('publicProperty'));
612
        self::assertTrue($proxy1->has('privateProperty'));
613
614
        self::assertFalse($proxy2->has('protectedProperty'));
615
        self::assertFalse($proxy2->has('publicProperty'));
616
        self::assertFalse($proxy2->has('privateProperty'));
617
    }
618
619
    public function testByRefInitialization() : void
620
    {
621
        $proxyName = $this->generateProxy(ClassWithMixedProperties::class);
622
        /** @var ClassWithPrivateProperties $proxy */
623
        $proxy = $proxyName::staticProxyConstructor(
624
            function ($proxy, $method, $params, & $initializer, array $properties) : void {
625
                $initializer                                                               = null;
626
                $properties["\0" . ClassWithMixedProperties::class . "\0privateProperty0"] = 'private0';
627
                $properties["\0" . ClassWithMixedProperties::class . "\0privateProperty1"] = 'private1';
628
                $properties["\0" . ClassWithMixedProperties::class . "\0privateProperty2"] = 'private2';
629
                $properties["\0*\0protectedProperty0"]                                     = 'protected0';
630
                $properties["\0*\0protectedProperty1"]                                     = 'protected1';
631
                $properties["\0*\0protectedProperty2"]                                     = 'protected2';
632
                $properties['publicProperty0']                                             = 'public0';
633
                $properties['publicProperty1']                                             = 'public1';
634
                $properties['publicProperty2']                                             = 'public2';
635
            }
636
        );
637
638
        $reflectionClass = new ReflectionClass(ClassWithMixedProperties::class);
639
640
        foreach (Properties::fromReflectionClass($reflectionClass)->getInstanceProperties() as $property) {
641
            $property->setAccessible(true);
642
643
            self::assertSame(str_replace('Property', '', $property->getName()), $property->getValue($proxy));
644
        }
645
    }
646
647
    /**
648
     * @group 115
649
     * @group 175
650
     */
651
    public function testWillBehaveLikeObjectWithNormalConstructor() : void
652
    {
653
        $instance = new ClassWithCounterConstructor(10);
654
655
        self::assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
656
        self::assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
657
        $instance->__construct(3);
658
        self::assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
659
        self::assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected');
660
661
        $proxyName = $this->generateProxy(get_class($instance));
662
663
        /** @var ClassWithCounterConstructor $proxy */
664
        $proxy = new $proxyName(15);
665
666
        self::assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
667
        self::assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
668
        $proxy->__construct(5);
669
        self::assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
670
        self::assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
671
    }
672
673
    public function testInitializeProxyWillReturnTrueOnSuccessfulInitialization() : void
674
    {
675
        $proxyName = $this->generateProxy(ClassWithMixedProperties::class);
676
677
        /** @var GhostObjectInterface $proxy */
678
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer(
679
            ClassWithMixedProperties::class,
680
            new ClassWithMixedProperties()
0 ignored issues
show
Documentation introduced by
new \ProxyManagerTestAss...ssWithMixedProperties() is of type object<ProxyManagerTestA...assWithMixedProperties>, but the function expects a object<ProxyManagerTest\Functional\object>.

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...
681
        ));
682
683
        self::assertTrue($proxy->initializeProxy());
684
        self::assertTrue($proxy->isProxyInitialized());
685
        self::assertFalse($proxy->initializeProxy());
686
    }
687
688
    /**
689
     * Generates a proxy for the given class name, and retrieves its class name
690
     *
691
     * @param mixed[] $proxyOptions
692
     */
693
    private function generateProxy(string $parentClassName, array $proxyOptions = []) : string
694
    {
695
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
696
        $generatedClass     = new ClassGenerator($generatedClassName);
697
698
        (new LazyLoadingGhostGenerator())->generate(
699
            new ReflectionClass($parentClassName),
700
            $generatedClass,
701
            $proxyOptions
702
        );
703
        (new EvaluatingGeneratorStrategy())->generate($generatedClass);
704
705
        return $generatedClassName;
706
    }
707
708
    private function createInitializer(string $className, object $realInstance, ?Mock $initializerMatcher = null
709
    ) : callable
710
    {
711
        /** @var callable|Mock $initializerMatcher */
712
        if (! $initializerMatcher) {
713
            $initializerMatcher = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
714
715
            $initializerMatcher
716
                ->expects(self::once())
717
                ->method('__invoke')
718
                ->with(self::logicalAnd(
719
                    self::isInstanceOf(GhostObjectInterface::class),
720
                    self::isInstanceOf($className)
721
                ));
722
        }
723
724
        self::assertInternalType('callable', $initializerMatcher);
725
726
        return function (
727
            GhostObjectInterface $proxy,
728
            $method,
729
            $params,
730
            & $initializer
731
        ) use (
732
            $initializerMatcher,
733
            $realInstance
734
        ) : bool {
735
            $initializer     = null;
736
            $reflectionClass = new ReflectionClass($realInstance);
737
738
            foreach (Properties::fromReflectionClass($reflectionClass)->getInstanceProperties() as $property) {
739
                $property->setAccessible(true);
740
                $property->setValue($proxy, $property->getValue($realInstance));
741
            }
742
743
            $initializerMatcher($proxy, $method, $params);
744
745
            return true;
746
        };
747
    }
748
749
    /**
750
     * Generates a list of object | invoked method | parameters | expected result
751
     *
752
     * @return null[][]|string[][]|object[][]|mixed[][][]
753
     */
754
    public function getProxyMethods() : array
755
    {
756
        $selfHintParam = new ClassWithSelfHint();
757
        $empty         = new EmptyClass();
758
759
        return [
760
            [
761
                BaseClass::class,
762
                new BaseClass(),
763
                'publicMethod',
764
                [],
765
                'publicMethodDefault',
766
            ],
767
            [
768
                BaseClass::class,
769
                new BaseClass(),
770
                'publicTypeHintedMethod',
771
                [new stdClass()],
772
                'publicTypeHintedMethodDefault',
773
            ],
774
            [
775
                BaseClass::class,
776
                new BaseClass(),
777
                'publicByReferenceMethod',
778
                [],
779
                'publicByReferenceMethodDefault',
780
            ],
781
            [
782
                ClassWithSelfHint::class,
783
                new ClassWithSelfHint(),
784
                'selfHintMethod',
785
                ['parameter' => $selfHintParam],
786
                $selfHintParam,
787
            ],
788
            [
789
                ClassWithParentHint::class,
790
                new ClassWithParentHint(),
791
                'parentHintMethod',
792
                ['parameter' => $empty],
793
                $empty,
794
            ],
795
            [
796
                ClassWithAbstractPublicMethod::class,
797
                new EmptyClass(), // EmptyClass just used to not make reflection explode when synchronizing properties
798
                'publicAbstractMethod',
799
                [],
800
                null,
801
            ],
802
            [
803
                ClassWithMethodWithByRefVariadicFunction::class,
804
                new ClassWithMethodWithByRefVariadicFunction(),
805
                'tuz',
806
                ['Ocramius', 'Malukenho'],
807
                ['Ocramius', 'changed'],
808
            ],
809
        ];
810
    }
811
812
    /**
813
     * Generates a list of object | invoked method | parameters | expected result for methods that cause lazy-loading
814
     * of a ghost object
815
     *
816
     * @return string[][]|object[][]|mixed[][][]|null[][]
817
     */
818
    public function getProxyInitializingMethods() : array
819
    {
820
        return [
821
            [
822
                BaseClass::class,
823
                new BaseClass(),
824
                'publicPropertyGetter',
825
                [],
826
                'publicPropertyDefault',
827
            ],
828
            [
829
                BaseClass::class,
830
                new BaseClass(),
831
                'protectedPropertyGetter',
832
                [],
833
                'protectedPropertyDefault',
834
            ],
835
            [
836
                BaseClass::class,
837
                new BaseClass(),
838
                'privatePropertyGetter',
839
                [],
840
                'privatePropertyDefault',
841
            ],
842
            [
843
                ClassWithMethodWithVariadicFunction::class,
844
                new ClassWithMethodWithVariadicFunction(),
845
                'foo',
846
                ['Ocramius', 'Malukenho'],
847
                null,
848
            ],
849
        ];
850
    }
851
852
    /**
853
     * Generates a list of object | invoked method | parameters | expected result for methods DON'T cause lazy-loading
854
     *
855
     * @return null[][]|string[][]|object[][]|mixed[][][]
856
     */
857
    public function getProxyNonInitializingMethods() : array
858
    {
859
        return $this->getProxyMethods();
860
    }
861
862
    /**
863
     * Generates proxies and instances with a public property to feed to the property accessor methods
864
     *
865
     * @return string[][]|object[][]
866
     */
867
    public function getPropertyAccessProxies() : array
868
    {
869
        $instance1  = new BaseClass();
870
        $proxyName1 = $this->generateProxy(get_class($instance1));
871
        $instance2  = new BaseClass();
872
        $proxyName2 = $this->generateProxy(get_class($instance2));
873
874
        return [
875
            [
876
                $instance1,
877
                new $proxyName1($this->createInitializer(BaseClass::class, $instance1)),
0 ignored issues
show
Documentation introduced by
$instance1 is of type object<ProxyManagerTestAsset\BaseClass>, but the function expects a object<ProxyManagerTest\Functional\object>.

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...
878
                'publicProperty',
879
                'publicPropertyDefault',
880
            ],
881
            [
882
                $instance2,
883
                unserialize(
884
                    serialize(new $proxyName2($this->createInitializer(BaseClass::class, $instance2)))
0 ignored issues
show
Documentation introduced by
$instance2 is of type object<ProxyManagerTestAsset\BaseClass>, but the function expects a object<ProxyManagerTest\Functional\object>.

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...
885
                ),
886
                'publicProperty',
887
                'publicPropertyDefault',
888
            ],
889
        ];
890
    }
891
892
    /**
893
     * @dataProvider skipPropertiesFixture
894
     *
895
     * @param mixed   $expected
896
     * @param mixed[] $proxyOptions
897
     */
898
    public function testInitializationIsSkippedForSkippedProperties(
899
        string $className,
900
        string $propertyClass,
901
        string $propertyName,
902
        array $proxyOptions,
903
        $expected
904
    ) : void {
905
        $proxy       = $this->generateProxy($className, $proxyOptions);
906
        $ghostObject = $proxy::staticProxyConstructor(function () use ($propertyName) : void {
907
            self::fail(sprintf('The Property "%s" was not expected to be lazy-loaded', $propertyName));
908
        });
909
910
        $property = new ReflectionProperty($propertyClass, $propertyName);
911
        $property->setAccessible(true);
912
913
        self::assertSame($expected, $property->getValue($ghostObject));
914
    }
915
916
    /**
917
     * @dataProvider skipPropertiesFixture
918
     *
919
     * @param mixed[] $proxyOptions
920
     */
921
    public function testSkippedPropertiesAreNotOverwrittenOnInitialization(
922
        string $className,
923
        string $propertyClass,
924
        string $propertyName,
925
        array $proxyOptions
926
    ) : void {
927
        $proxyName = $this->generateProxy($className, $proxyOptions);
928
        /** @var GhostObjectInterface $ghostObject */
929
        $ghostObject = $proxyName::staticProxyConstructor(
930
            function ($proxy, string $method, $params, & $initializer) : bool {
931
                $initializer = null;
932
933
                return true;
934
            }
935
        );
936
937
        $property = new ReflectionProperty($propertyClass, $propertyName);
938
939
        $property->setAccessible(true);
940
941
        $value = uniqid('', true);
942
943
        $property->setValue($ghostObject, $value);
944
945
        self::assertTrue($ghostObject->initializeProxy());
946
947
        self::assertSame(
948
            $value,
949
            $property->getValue($ghostObject),
950
            'Property should not be changed by proxy initialization'
951
        );
952
    }
953
954
    /**
955
     * @group 265
956
     */
957
    public function testWillForwardVariadicByRefArguments() : void
958
    {
959
        $proxyName = $this->generateProxy(ClassWithMethodWithByRefVariadicFunction::class);
960
        /** @var ClassWithMethodWithByRefVariadicFunction $object */
961
        $object = $proxyName::staticProxyConstructor(function ($proxy, string $method, $params, & $initializer) : bool {
962
            $initializer = null;
963
964
            return true;
965
        });
966
967
        $parameters = ['a', 'b', 'c'];
968
969
        // first, testing normal variadic behavior (verifying we didn't screw up in the test asset)
970
        self::assertSame(['a', 'changed', 'c'], (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$parameters));
971
        self::assertSame(['a', 'changed', 'c'], $object->tuz(...$parameters));
972
        self::assertSame(['a', 'changed', 'c'], $parameters, 'by-ref variadic parameter was changed');
973
    }
974
975
    /**
976
     * @group 265
977
     */
978
    public function testWillForwardDynamicArguments() : void
979
    {
980
        $proxyName = $this->generateProxy(ClassWithDynamicArgumentsMethod::class);
981
        /** @var ClassWithDynamicArgumentsMethod $object */
982
        $object = $proxyName::staticProxyConstructor(function () : void {
983
        });
984
985
        // first, testing normal variadic behavior (verifying we didn't screw up in the test asset)
986
        self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b'));
987
        self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b'));
988
    }
989
990
    /**
991
     * @return mixed[] in order:
992
     *                  - the class to be proxied
993
     *                  - the class owning the property to be checked
994
     *                  - the property name
995
     *                  - the options to be passed to the generator
996
     *                  - the expected value of the property
997
     */
998
    public function skipPropertiesFixture() : array
999
    {
1000
        return [
1001
            [
1002
                ClassWithPublicProperties::class,
1003
                ClassWithPublicProperties::class,
1004
                'property9',
1005
                [
1006
                    'skippedProperties' => ['property9'],
1007
                ],
1008
                'property9',
1009
            ],
1010
            [
1011
                ClassWithProtectedProperties::class,
1012
                ClassWithProtectedProperties::class,
1013
                'property9',
1014
                [
1015
                    'skippedProperties' => ["\0*\0property9"],
1016
                ],
1017
                'property9',
1018
            ],
1019
            [
1020
                ClassWithPrivateProperties::class,
1021
                ClassWithPrivateProperties::class,
1022
                'property9',
1023
                [
1024
                    'skippedProperties' => ["\0ProxyManagerTestAsset\\ClassWithPrivateProperties\0property9"],
1025
                ],
1026
                'property9',
1027
            ],
1028
            [
1029
                ClassWithCollidingPrivateInheritedProperties::class,
1030
                ClassWithCollidingPrivateInheritedProperties::class,
1031
                'property0',
1032
                [
1033
                    'skippedProperties' => ["\0ProxyManagerTestAsset\\ClassWithCollidingPrivateInheritedProperties\0property0"],
1034
                ],
1035
                'childClassProperty0',
1036
            ],
1037
            [
1038
                ClassWithCollidingPrivateInheritedProperties::class,
1039
                ClassWithPrivateProperties::class,
1040
                'property0',
1041
                [
1042
                    'skippedProperties' => ["\0ProxyManagerTestAsset\\ClassWithPrivateProperties\0property0"],
1043
                ],
1044
                'property0',
1045
            ],
1046
        ];
1047
    }
1048
1049
    /**
1050
     * @group        276
1051
     *
1052
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
1053
     *
1054
     */
1055
    public function testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope(
1056
        object $callerObject,
1057
        string $method,
1058
        string $propertyIndex,
1059
        string $expectedValue
1060
    ) : void {
1061
        $proxyName = $this->generateProxy(get_class($callerObject));
1062
        /** @var OtherObjectAccessClass|LazyLoadingInterface $proxy */
1063
        $proxy = $proxyName::staticProxyConstructor(
1064
            function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue
1065
            ) : void {
1066
                $initializer = null;
1067
1068
                $props[$propertyIndex] = $expectedValue;
1069
            }
1070
        );
1071
1072
        self::assertInstanceOf(LazyLoadingInterface::class, $proxy);
1073
1074
        $accessor = [$callerObject, $method];
1075
1076
        self::assertInternalType('callable', $accessor);
1077
1078
        self::assertFalse($proxy->isProxyInitialized());
1079
        self::assertSame($expectedValue, $accessor($proxy));
1080
        self::assertTrue($proxy->isProxyInitialized());
1081
    }
1082
1083
    /**
1084
     * @group        276
1085
     *
1086
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
1087
     *
1088
     */
1089
    public function testWillAccessMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope(
1090
        object $callerObject,
1091
        string $method,
1092
        string $propertyIndex,
1093
        string $expectedValue
1094
    ) : void {
1095
        $proxyName = $this->generateProxy(get_class($callerObject));
1096
        /** @var OtherObjectAccessClass|LazyLoadingInterface $proxy */
1097
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
1098
            function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue
1099
            ) : void {
1100
                $initializer = null;
1101
1102
                $props[$propertyIndex] = $expectedValue;
1103
            }
1104
        )));
1105
1106
        self::assertInstanceOf(LazyLoadingInterface::class, $proxy);
1107
1108
        $accessor = [$callerObject, $method];
1109
1110
        self::assertInternalType('callable', $accessor);
1111
1112
        self::assertTrue($proxy->isProxyInitialized());
1113
        self::assertSame($expectedValue, $accessor($proxy));
1114
    }
1115
1116
    /**
1117
     * @group        276
1118
     *
1119
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
1120
     *
1121
     */
1122
    public function testWillAccessMembersOfOtherClonedProxiesWithTheSamePrivateScope(
1123
        object $callerObject,
1124
        string $method,
1125
        string $propertyIndex,
1126
        string $expectedValue
1127
    ) : void {
1128
        $proxyName = $this->generateProxy(get_class($callerObject));
1129
        /** @var OtherObjectAccessClass|LazyLoadingInterface $proxy */
1130
        $proxy = clone $proxyName::staticProxyConstructor(
1131
            function ($proxy, $method, $params, & $initializer, array $props) use ($propertyIndex, $expectedValue
1132
            ) : void {
1133
                $initializer = null;
1134
1135
                $props[$propertyIndex] = $expectedValue;
1136
            }
1137
        );
1138
1139
        self::assertInstanceOf(LazyLoadingInterface::class, $proxy);
1140
1141
        $accessor = [$callerObject, $method];
1142
1143
        self::assertInternalType('callable', $accessor);
1144
1145
        self::assertTrue($proxy->isProxyInitialized());
1146
        self::assertSame($expectedValue, $accessor($proxy));
1147
    }
1148
1149
    /** @return string[][]|object[][] */
1150
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : array
1151
    {
1152
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
1153
1154
        return [
1155
            OtherObjectAccessClass::class . '#$privateProperty'                => [
1156
                new OtherObjectAccessClass(),
1157
                'getPrivateProperty',
1158
                "\0" . OtherObjectAccessClass::class . "\0privateProperty",
1159
                uniqid('', true),
1160
            ],
1161
            OtherObjectAccessClass::class . '#$protectedProperty'              => [
1162
                new OtherObjectAccessClass(),
1163
                'getProtectedProperty',
1164
                "\0*\0protectedProperty",
1165
                uniqid('', true),
1166
            ],
1167
            OtherObjectAccessClass::class . '#$publicProperty'                 => [
1168
                new OtherObjectAccessClass(),
1169
                'getPublicProperty',
1170
                'publicProperty',
1171
                uniqid('', true),
1172
            ],
1173
            '(proxy) ' . OtherObjectAccessClass::class . '#$privateProperty'   => [
1174
                $proxyClass::staticProxyConstructor(function () : void {
1175
                    self::fail('Should never be initialized, as its values aren\'t accessed');
1176
                }),
1177
                'getPrivateProperty',
1178
                "\0" . OtherObjectAccessClass::class . "\0privateProperty",
1179
                uniqid('', true),
1180
            ],
1181
            '(proxy) ' . OtherObjectAccessClass::class . '#$protectedProperty' => [
1182
                $proxyClass::staticProxyConstructor(function () : void {
1183
                    self::fail('Should never be initialized, as its values aren\'t accessed');
1184
                }),
1185
                'getProtectedProperty',
1186
                "\0*\0protectedProperty",
1187
                uniqid('', true),
1188
            ],
1189
            '(proxy) ' . OtherObjectAccessClass::class . '#$publicProperty'    => [
1190
                $proxyClass::staticProxyConstructor(function () : void {
1191
                    self::fail('Should never be initialized, as its values aren\'t accessed');
1192
                }),
1193
                'getPublicProperty',
1194
                'publicProperty',
1195
                uniqid('', true),
1196
            ],
1197
        ];
1198
    }
1199
1200
    /**
1201
     * @group 276
1202
     */
1203
    public function testFriendObjectWillNotCauseLazyLoadingOnSkippedProperty() : void
1204
    {
1205
        $proxyName = $this->generateProxy(
1206
            OtherObjectAccessClass::class,
1207
            [
1208
                'skippedProperties' => [
1209
                    "\0" . OtherObjectAccessClass::class . "\0privateProperty",
1210
                    "\0*\0protectedProperty",
1211
                    'publicProperty',
1212
                ],
1213
            ]
1214
        );
1215
1216
        /** @var OtherObjectAccessClass|LazyLoadingInterface $proxy */
1217
        $proxy = $proxyName::staticProxyConstructor(function () : void {
1218
            throw new \BadMethodCallException('The proxy should never be initialized, as all properties are skipped');
1219
        });
1220
1221
        self::assertInstanceOf(OtherObjectAccessClass::class, $proxy);
1222
        self::assertInstanceOf(LazyLoadingInterface::class, $proxy);
1223
1224
        $privatePropertyValue   = uniqid('', true);
1225
        $protectedPropertyValue = uniqid('', true);
1226
        $publicPropertyValue    = uniqid('', true);
1227
1228
        $reflectionPrivateProperty = new \ReflectionProperty(OtherObjectAccessClass::class, 'privateProperty');
1229
1230
        $reflectionPrivateProperty->setAccessible(true);
1231
        $reflectionPrivateProperty->setValue($proxy, $privatePropertyValue);
1232
1233
        $reflectionProtectedProperty = new \ReflectionProperty(OtherObjectAccessClass::class, 'protectedProperty');
1234
1235
        $reflectionProtectedProperty->setAccessible(true);
1236
        $reflectionProtectedProperty->setValue($proxy, $protectedPropertyValue);
1237
1238
        $proxy->publicProperty = $publicPropertyValue;
1239
1240
        $friendObject = new OtherObjectAccessClass();
1241
1242
        self::assertInstanceOf(OtherObjectAccessClass::class, $proxy);
1243
1244
        if (! ($proxy instanceof OtherObjectAccessClass)) {
1245
            return;
1246
        }
1247
1248
        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...
1249
        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...
1250
        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...
1251
    }
1252
1253
    public function testClonedSkippedPropertiesArePreserved() : void
1254
    {
1255
        $proxyName = $this->generateProxy(
1256
            BaseClass::class,
1257
            [
1258
                'skippedProperties' => [
1259
                    "\0" . BaseClass::class . "\0privateProperty",
1260
                    "\0*\0protectedProperty",
1261
                    'publicProperty',
1262
                ],
1263
            ]
1264
        );
1265
1266
        /** @var BaseClass|GhostObjectInterface $proxy */
1267
        $proxy = $proxyName::staticProxyConstructor(function ($proxy) : void {
1268
            $proxy->setProxyInitializer(null);
1269
        });
1270
1271
        self::assertInstanceOf(BaseClass::class, $proxy);
1272
        self::assertInstanceOf(LazyLoadingInterface::class, $proxy);
1273
1274
        $reflectionPrivate   = new \ReflectionProperty(BaseClass::class, 'privateProperty');
1275
        $reflectionProtected = new \ReflectionProperty(BaseClass::class, 'protectedProperty');
1276
1277
        $reflectionPrivate->setAccessible(true);
1278
        $reflectionProtected->setAccessible(true);
1279
1280
        $privateValue   = uniqid('', true);
1281
        $protectedValue = uniqid('', true);
1282
        $publicValue    = uniqid('', true);
1283
1284
        $reflectionPrivate->setValue($proxy, $privateValue);
1285
        $reflectionProtected->setValue($proxy, $protectedValue);
1286
        $proxy->publicProperty = $publicValue;
1287
1288
        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...
1289
1290
        $clone = clone $proxy;
1291
1292
        self::assertFalse($proxy->isProxyInitialized());
1293
        self::assertTrue($clone->isProxyInitialized());
1294
1295
        self::assertSame($privateValue, $reflectionPrivate->getValue($proxy));
1296
        self::assertSame($privateValue, $reflectionPrivate->getValue($clone));
1297
        self::assertSame($protectedValue, $reflectionProtected->getValue($proxy));
1298
        self::assertSame($protectedValue, $reflectionProtected->getValue($clone));
1299
        self::assertSame($publicValue, $proxy->publicProperty);
1300
        self::assertSame($publicValue, $clone->publicProperty);
1301
    }
1302
1303
    /**
1304
     * @group 327
1305
     */
1306
    public function testWillExecuteLogicInAVoidMethod() : void
1307
    {
1308
        $proxyName = $this->generateProxy(VoidCounter::class);
1309
1310
        $initialCounter = random_int(10, 1000);
1311
1312
        /** @var VoidCounter|LazyLoadingInterface $proxy */
1313
        $proxy = $proxyName::staticProxyConstructor(
1314
            function (VoidCounter $proxy, $method, $params, & $initializer, array $props) use ($initialCounter) : bool {
1315
                $initializer = null;
1316
1317
                $props['counter'] = $initialCounter;
1318
1319
                return true;
1320
            }
1321
        );
1322
1323
        self::assertInstanceOf(VoidCounter::class, $proxy);
1324
        self::assertInstanceOf(LazyLoadingInterface::class, $proxy);
1325
1326
        $increment = random_int(1001, 10000);
1327
1328
        $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...
1329
1330
        self::assertSame($initialCounter + $increment, $proxy->counter);
1331
    }
1332
}
1333