Completed
Pull Request — master (#425)
by Marco
08:16
created

testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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