Completed
Push — master ( af4b60...a13948 )
by Jefersson
11s
created

testWillExecuteLogicInAVoidMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
cc 1
eloc 11
nc 1
nop 0
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license.
17
 */
18
19
declare(strict_types=1);
20
21
namespace ProxyManagerTest\Functional;
22
23
use PHPUnit_Framework_MockObject_MockObject as Mock;
24
use PHPUnit_Framework_TestCase;
25
use ProxyManager\Generator\ClassGenerator;
26
use ProxyManager\Generator\Util\UniqueIdentifierGenerator;
27
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
28
use ProxyManager\Proxy\GhostObjectInterface;
29
use ProxyManager\Proxy\LazyLoadingInterface;
30
use ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator;
31
use ProxyManager\ProxyGenerator\Util\Properties;
32
use ProxyManagerTestAsset\BaseClass;
33
use ProxyManagerTestAsset\ClassWithAbstractPublicMethod;
34
use ProxyManagerTestAsset\ClassWithCollidingPrivateInheritedProperties;
35
use ProxyManagerTestAsset\ClassWithCounterConstructor;
36
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod;
37
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction;
38
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction;
39
use ProxyManagerTestAsset\ClassWithMixedProperties;
40
use ProxyManagerTestAsset\ClassWithMixedPropertiesAndAccessorMethods;
41
use ProxyManagerTestAsset\ClassWithParentHint;
42
use ProxyManagerTestAsset\ClassWithPrivateProperties;
43
use ProxyManagerTestAsset\ClassWithProtectedProperties;
44
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
45
use ProxyManagerTestAsset\ClassWithPublicProperties;
46
use ProxyManagerTestAsset\ClassWithSelfHint;
47
use ProxyManagerTestAsset\EmptyClass;
48
use ProxyManagerTestAsset\OtherObjectAccessClass;
49
use ProxyManagerTestAsset\VoidCounter;
50
use ReflectionClass;
51
use ReflectionProperty;
52
use stdClass;
53
54
/**
55
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator} produced objects
56
 *
57
 * @author Marco Pivetta <[email protected]>
58
 * @license MIT
59
 *
60
 * @group Functional
61
 * @coversNothing
62
 */
63
class LazyLoadingGhostFunctionalTest extends PHPUnit_Framework_TestCase
64
{
65
    /**
66
     * @dataProvider getProxyInitializingMethods
67
     *
68
     * @param string  $className
69
     * @param object  $instance
70
     * @param string  $method
71
     * @param mixed[] $params
72
     * @param mixed   $expectedValue
73
     */
74
    public function testMethodCallsThatLazyLoadTheObject(
75
        string $className,
76
        $instance,
77
        string $method,
78
        array $params,
79
        $expectedValue
80
    ) : void {
81
        $proxyName = $this->generateProxy($className);
82
83
        /* @var $proxy GhostObjectInterface */
84
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
85
86
        self::assertFalse($proxy->isProxyInitialized());
87
88
        /* @var $callProxyMethod callable */
89
        $callProxyMethod = [$proxy, $method];
90
        $parameterValues = array_values($params);
91
92
        self::assertInternalType('callable', $callProxyMethod);
93
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
94
        self::assertTrue($proxy->isProxyInitialized());
95
    }
96
97
    /**
98
     * @dataProvider getProxyNonInitializingMethods
99
     *
100
     * @param string  $className
101
     * @param object  $instance
102
     * @param string  $method
103
     * @param mixed[] $params
104
     * @param mixed   $expectedValue
105
     */
106
    public function testMethodCallsThatDoNotLazyLoadTheObject(
107
        string $className,
108
        $instance,
109
        string $method,
110
        array $params,
111
        $expectedValue
112
    ) : void {
113
        $proxyName         = $this->generateProxy($className);
114
        $initializeMatcher = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
115
116
        $initializeMatcher->expects(self::never())->method('__invoke'); // should not initialize the proxy
117
118
        /* @var $proxy GhostObjectInterface */
119
        $proxy = $proxyName::staticProxyConstructor(
120
            $this->createInitializer($className, $instance, $initializeMatcher)
121
        );
122
123
        self::assertFalse($proxy->isProxyInitialized());
124
125
        /* @var $callProxyMethod callable */
126
        $callProxyMethod = [$proxy, $method];
127
        $parameterValues = array_values($params);
128
129
        self::assertInternalType('callable', $callProxyMethod);
130
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
131
        self::assertFalse($proxy->isProxyInitialized());
132
    }
133
134
    /**
135
     * @dataProvider getProxyMethods
136
     *
137
     * @param string  $className
138
     * @param object  $instance
139
     * @param string  $method
140
     * @param mixed[] $params
141
     * @param mixed   $expectedValue
142
     */
143
    public function testMethodCallsAfterUnSerialization(
144
        string $className,
145
        $instance,
146
        string $method,
147
        array $params,
148
        $expectedValue
149
    ) : void {
150
        $proxyName = $this->generateProxy($className);
151
152
        /* @var $proxy GhostObjectInterface */
153
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
154
            $this->createInitializer($className, $instance)
155
        )));
156
157
        self::assertTrue($proxy->isProxyInitialized());
158
159
        /* @var $callProxyMethod callable */
160
        $callProxyMethod = [$proxy, $method];
161
        $parameterValues = array_values($params);
162
163
        self::assertInternalType('callable', $callProxyMethod);
164
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
165
    }
166
167
    /**
168
     * @dataProvider getProxyMethods
169
     *
170
     * @param string  $className
171
     * @param object  $instance
172
     * @param string  $method
173
     * @param mixed[] $params
174
     * @param mixed   $expectedValue
175
     */
176
    public function testMethodCallsAfterCloning(
177
        string $className,
178
        $instance,
179
        string $method,
180
        array $params,
181
        $expectedValue
182
    ) : void {
183
        $proxyName = $this->generateProxy($className);
184
185
        /* @var $proxy GhostObjectInterface */
186
        $proxy  = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
187
        $cloned = clone $proxy;
188
189
        self::assertTrue($cloned->isProxyInitialized());
190
191
        /* @var $callProxyMethod callable */
192
        $callProxyMethod = [$proxy, $method];
193
        $parameterValues = array_values($params);
194
195
        self::assertInternalType('callable', $callProxyMethod);
196
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
197
    }
198
199
    /**
200
     * @dataProvider getPropertyAccessProxies
201
     *
202
     * @param object               $instance
203
     * @param GhostObjectInterface $proxy
204
     * @param string               $publicProperty
205
     * @param mixed                $propertyValue
206
     */
207
    public function testPropertyReadAccess(
208
        $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...
209
        GhostObjectInterface $proxy,
210
        string $publicProperty,
211
        $propertyValue
212
    ) : void {
213
        self::assertSame($propertyValue, $proxy->$publicProperty);
214
        self::assertTrue($proxy->isProxyInitialized());
215
    }
216
217
    /**
218
     * @dataProvider getPropertyAccessProxies
219
     *
220
     * @param object               $instance
221
     * @param GhostObjectInterface $proxy
222
     * @param string               $publicProperty
223
     */
224
    public function testPropertyWriteAccess($instance, GhostObjectInterface $proxy, string $publicProperty) : void
0 ignored issues
show
Unused Code introduced by
The parameter $instance is not used and could be removed.

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

Loading history...
225
    {
226
        $newValue               = uniqid();
227
        $proxy->$publicProperty = $newValue;
228
229
        self::assertTrue($proxy->isProxyInitialized());
230
        self::assertSame($newValue, $proxy->$publicProperty);
231
    }
232
233
    /**
234
     * @dataProvider getPropertyAccessProxies
235
     *
236
     * @param object               $instance
237
     * @param GhostObjectInterface $proxy
238
     * @param string               $publicProperty
239
     */
240
    public function testPropertyExistence($instance, GhostObjectInterface $proxy, string $publicProperty) : void
241
    {
242
        self::assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
243
        self::assertTrue($proxy->isProxyInitialized());
244
    }
245
246
    /**
247
     * @dataProvider getPropertyAccessProxies
248
     *
249
     * @param object               $instance
250
     * @param GhostObjectInterface $proxy
251
     * @param string               $publicProperty
252
     */
253
    public function testPropertyAbsence($instance, GhostObjectInterface $proxy, string $publicProperty) : void
0 ignored issues
show
Unused Code introduced by
The parameter $instance is not used and could be removed.

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1297
    }
1298
1299
    public function testClonedSkippedPropertiesArePreserved() : void
1300
    {
1301
1302
        $proxyName = $this->generateProxy(
1303
            BaseClass::class,
1304
            [
1305
                'skippedProperties' => [
1306
                    "\0" . BaseClass::class . "\0privateProperty",
1307
                    "\0*\0protectedProperty",
1308
                    'publicProperty'
1309
                ],
1310
            ]
1311
        );
1312
1313
        /* @var $proxy BaseClass|GhostObjectInterface */
1314
        $proxy = $proxyName::staticProxyConstructor(function ($proxy) {
1315
            $proxy->setProxyInitializer(null);
1316
        });
1317
1318
        $reflectionPrivate   = new \ReflectionProperty(BaseClass::class, 'privateProperty');
1319
        $reflectionProtected = new \ReflectionProperty(BaseClass::class, 'protectedProperty');
1320
1321
        $reflectionPrivate->setAccessible(true);
1322
        $reflectionProtected->setAccessible(true);
1323
1324
        $privateValue   = uniqid('', true);
1325
        $protectedValue = uniqid('', true);
1326
        $publicValue    = uniqid('', true);
1327
1328
        $reflectionPrivate->setValue($proxy, $privateValue);
1329
        $reflectionProtected->setValue($proxy, $protectedValue);
1330
        $proxy->publicProperty = $publicValue;
1331
1332
        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...
1333
1334
        $clone = clone $proxy;
1335
1336
        self::assertFalse($proxy->isProxyInitialized());
1337
        self::assertTrue($clone->isProxyInitialized());
1338
1339
        self::assertSame($privateValue, $reflectionPrivate->getValue($proxy));
1340
        self::assertSame($privateValue, $reflectionPrivate->getValue($clone));
1341
        self::assertSame($protectedValue, $reflectionProtected->getValue($proxy));
1342
        self::assertSame($protectedValue, $reflectionProtected->getValue($clone));
1343
        self::assertSame($publicValue, $proxy->publicProperty);
1344
        self::assertSame($publicValue, $clone->publicProperty);
1345
    }
1346
1347
    /**
1348
     * @group 327
1349
     */
1350
    public function testWillExecuteLogicInAVoidMethod() : void
1351
    {
1352
        $proxyName = $this->generateProxy(VoidCounter::class);
1353
1354
        $initialCounter = random_int(10, 1000);
1355
1356
        /* @var $proxy VoidCounter|LazyLoadingInterface */
1357
        $proxy = $proxyName::staticProxyConstructor(
1358
            function (VoidCounter $proxy, $method, $params, & $initializer, array $props) use ($initialCounter) : bool {
1359
                $initializer = null;
1360
1361
                $props['counter'] = $initialCounter;
1362
1363
                return true;
1364
            }
1365
        );
1366
1367
        $increment = random_int(1001, 10000);
1368
1369
        $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...
1370
1371
        self::assertSame($initialCounter + $increment, $proxy->counter);
1372
    }
1373
}
1374