Completed
Pull Request — master (#384)
by
unknown
19:09
created

LazyLoadingValueHolderFunctionalTest   B

Complexity

Total Complexity 30

Size/Duplication

Total Lines 679
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 0
Metric Value
dl 0
loc 679
c 0
b 0
f 0
wmc 30
lcom 1
cbo 18
rs 7.3866

25 Methods

Rating   Name   Duplication   Size   Complexity  
A generateProxy() 0 12 1
A testMethodCalls() 0 17 1
A testPropertyWriteAccess() 0 9 1
A testPropertyExistence() 0 6 1
A testPropertyAbsence() 0 7 2
A testPropertyUnset() 0 10 2
A testCanWriteToArrayKeysInPublicProperty() 0 17 1
A testWillNotModifyRetrievedPublicProperties() 0 17 1
A testWillModifyByRefRetrievedPublicProperties() 0 17 1
A testWillAllowMultipleProxyInitialization() 0 16 1
A testWillBehaveLikeObjectWithNormalConstructor() 0 21 1
A testWillForwardVariadicByRefArguments() 0 15 1
A testWillNotForwardDynamicArguments() 0 15 1
B testMethodCallsAfterUnSerialization() 0 25 1
B testMethodCallsAfterCloning() 0 25 1
A testPropertyReadAccess() 0 10 1
B createInitializer() 0 34 2
B getProxyMethods() 0 99 1
B getPropertyAccessProxies() 0 26 1
A testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope() 0 17 1
A testWillFetchMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope() 0 18 1
A testWillFetchMembersOfOtherClonedProxiesWithTheSamePrivateScope() 0 18 1
A testWillExecuteLogicInAVoidMethod() 0 12 1
B getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() 0 30 2
A buildInstanceWithValues() 0 12 2
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\LazyLoadingInterface;
29
use ProxyManager\Proxy\VirtualProxyInterface;
30
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator;
31
use ProxyManagerTestAsset\BaseClass;
32
use ProxyManagerTestAsset\BaseInterface;
33
use ProxyManagerTestAsset\ClassWithCounterConstructor;
34
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod;
35
use ProxyManagerTestAsset\ClassWithMagicMethods;
36
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction;
37
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction;
38
use ProxyManagerTestAsset\ClassWithParentHint;
39
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
40
use ProxyManagerTestAsset\ClassWithPublicProperties;
41
use ProxyManagerTestAsset\ClassWithSelfHint;
42
use ProxyManagerTestAsset\EmptyClass;
43
use ProxyManagerTestAsset\OtherObjectAccessClass;
44
use ProxyManagerTestAsset\VoidCounter;
45
use ReflectionClass;
46
use stdClass;
47
48
/**
49
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator} produced objects
50
 *
51
 * @author Marco Pivetta <[email protected]>
52
 * @license MIT
53
 *
54
 * @group Functional
55
 * @coversNothing
56
 */
57
class LazyLoadingValueHolderFunctionalTest extends PHPUnit_Framework_TestCase
58
{
59
    /**
60
     * @dataProvider getProxyMethods
61
     *
62
     * @param string  $className
63
     * @param object  $instance
64
     * @param string  $method
65
     * @param mixed[] $params
66
     * @param mixed   $expectedValue
67
     */
68
    public function testMethodCalls(string $className, $instance, string $method, array $params, $expectedValue) : void
69
    {
70
        $proxyName = $this->generateProxy($className);
71
72
        /* @var $proxy VirtualProxyInterface */
73
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
74
75
        self::assertFalse($proxy->isProxyInitialized());
76
77
        /* @var $callProxyMethod callable */
78
        $callProxyMethod = [$proxy, $method];
79
        $parameterValues = array_values($params);
80
81
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
82
        self::assertTrue($proxy->isProxyInitialized());
83
        self::assertSame($instance, $proxy->getWrappedValueHolderValue());
84
    }
85
86
    /**
87
     * @dataProvider getProxyMethods
88
     *
89
     * @param string  $className
90
     * @param object  $instance
91
     * @param string  $method
92
     * @param mixed[] $params
93
     * @param mixed   $expectedValue
94
     */
95
    public function testMethodCallsAfterUnSerialization(
96
        string $className,
97
        $instance,
98
        string $method,
99
        array $params,
100
        $expectedValue
101
    ) : void {
102
        $proxyName = $this->generateProxy($className);
103
104
        /* @var $proxy VirtualProxyInterface */
105
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
106
            $this->createInitializer($className, $instance)
107
        )));
108
109
        self::assertTrue($proxy->isProxyInitialized());
110
111
        /* @var $callProxyMethod callable */
112
        $callProxyMethod = [$proxy, $method];
113
        $parameterValues = array_values($params);
114
115
        self::assertInternalType('callable', $callProxyMethod);
116
117
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
118
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
119
    }
120
121
    /**
122
     * @dataProvider getProxyMethods
123
     *
124
     * @param string  $className
125
     * @param object  $instance
126
     * @param string  $method
127
     * @param mixed[] $params
128
     * @param mixed   $expectedValue
129
     */
130
    public function testMethodCallsAfterCloning(
131
        string $className,
132
        $instance,
133
        string $method,
134
        array $params,
135
        $expectedValue
136
    ) : void {
137
        $proxyName = $this->generateProxy($className);
138
139
        /* @var $proxy VirtualProxyInterface */
140
        $proxy  = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
141
        $cloned = clone $proxy;
142
143
        self::assertTrue($cloned->isProxyInitialized());
144
        self::assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue());
145
146
        /* @var $callProxyMethod callable */
147
        $callProxyMethod = [$cloned, $method];
148
        $parameterValues = array_values($params);
149
150
        self::assertInternalType('callable', $callProxyMethod);
151
152
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
153
        self::assertEquals($instance, $cloned->getWrappedValueHolderValue());
154
    }
155
156
    /**
157
     * @dataProvider getPropertyAccessProxies
158
     *
159
     * @param object                $instance
160
     * @param VirtualProxyInterface $proxy
161
     * @param string                $publicProperty
162
     * @param mixed                 $propertyValue
163
     */
164
    public function testPropertyReadAccess(
165
        $instance,
166
        VirtualProxyInterface $proxy,
167
        string $publicProperty,
168
        $propertyValue
169
    ) : void {
170
        self::assertSame($propertyValue, $proxy->$publicProperty);
171
        self::assertTrue($proxy->isProxyInitialized());
172
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
173
    }
174
175
    /**
176
     * @dataProvider getPropertyAccessProxies
177
     *
178
     * @param object                $instance
179
     * @param VirtualProxyInterface $proxy
180
     * @param string                $publicProperty
181
     */
182
    public function testPropertyWriteAccess($instance, VirtualProxyInterface $proxy, string $publicProperty) : void
183
    {
184
        $newValue               = uniqid();
185
        $proxy->$publicProperty = $newValue;
186
187
        self::assertTrue($proxy->isProxyInitialized());
188
        self::assertSame($newValue, $proxy->$publicProperty);
189
        self::assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty);
190
    }
191
192
    /**
193
     * @dataProvider getPropertyAccessProxies
194
     *
195
     * @param object                $instance
196
     * @param VirtualProxyInterface $proxy
197
     * @param string                $publicProperty
198
     */
199
    public function testPropertyExistence($instance, VirtualProxyInterface $proxy, string $publicProperty) : void
200
    {
201
        self::assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
202
        self::assertTrue($proxy->isProxyInitialized());
203
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
204
    }
205
206
    /**
207
     * @dataProvider getPropertyAccessProxies
208
     *
209
     * @param object                $instance
210
     * @param VirtualProxyInterface $proxy
211
     * @param string                $publicProperty
212
     */
213
    public function testPropertyAbsence($instance, VirtualProxyInterface $proxy, string $publicProperty) : void
214
    {
215
        $instance = $proxy->getWrappedValueHolderValue() ?: $instance;
216
        $instance->$publicProperty = null;
217
        self::assertFalse(isset($proxy->$publicProperty));
218
        self::assertTrue($proxy->isProxyInitialized());
219
    }
220
221
    /**
222
     * @dataProvider getPropertyAccessProxies
223
     *
224
     * @param object                $instance
225
     * @param VirtualProxyInterface $proxy
226
     * @param string                $publicProperty
227
     */
228
    public function testPropertyUnset($instance, VirtualProxyInterface $proxy, string $publicProperty) : void
229
    {
230
        $instance = $proxy->getWrappedValueHolderValue() ?: $instance;
231
        unset($proxy->$publicProperty);
232
233
        self::assertTrue($proxy->isProxyInitialized());
234
235
        self::assertFalse(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);
247
        $proxyName   = $this->generateProxy($className);
248
        /* @var $proxy ClassWithPublicArrayProperty */
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);
268
        $proxyName   = $this->generateProxy($className);
269
        /* @var $proxy ClassWithPublicProperties */
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);
289
        $proxyName   = $this->generateProxy($className);
290
        /* @var $proxy ClassWithPublicProperties */
291
        $proxy       = $proxyName::staticProxyConstructor($initializer);
292
        $variable    = & $proxy->property0;
293
294
        self::assertSame('property0', $variable);
295
296
        $variable = 'foo';
297
298
        self::assertSame('foo', $proxy->property0);
299
        self::assertSame('foo', $variable);
300
    }
301
302
    /**
303
     * @group 16
304
     *
305
     * Verifies that initialization of a value holder proxy may happen multiple times
306
     */
307
    public function testWillAllowMultipleProxyInitialization() : void
308
    {
309
        $proxyClass  = $this->generateProxy(BaseClass::class);
310
        $counter     = 0;
311
312
        /* @var $proxy BaseClass */
313
        $proxy = $proxyClass::staticProxyConstructor(function (& $wrappedInstance) use (& $counter) {
314
            $wrappedInstance = new BaseClass();
315
316
            $wrappedInstance->publicProperty = (string) ($counter += 1);
317
        });
318
319
        self::assertSame('1', $proxy->publicProperty);
320
        self::assertSame('2', $proxy->publicProperty);
321
        self::assertSame('3', $proxy->publicProperty);
322
    }
323
324
    /**
325
     * @group 115
326
     * @group 175
327
     */
328
    public function testWillBehaveLikeObjectWithNormalConstructor() : void
329
    {
330
        $instance = new ClassWithCounterConstructor(10);
331
332
        self::assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
333
        self::assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
334
        $instance->__construct(3);
335
        self::assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
336
        self::assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected');
337
338
        $proxyName = $this->generateProxy(get_class($instance));
339
340
        /* @var $proxy ClassWithCounterConstructor */
341
        $proxy = new $proxyName(15);
342
343
        self::assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
344
        self::assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
345
        $proxy->__construct(5);
346
        self::assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
347
        self::assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
348
    }
349
350
    /**
351
     * @group 265
352
     */
353
    public function testWillForwardVariadicByRefArguments() : void
354
    {
355
        $proxyName   = $this->generateProxy(ClassWithMethodWithByRefVariadicFunction::class);
356
        /* @var $object ClassWithMethodWithByRefVariadicFunction */
357
        $object = $proxyName::staticProxyConstructor(function (& $wrappedInstance) {
358
            $wrappedInstance = new ClassWithMethodWithByRefVariadicFunction();
359
        });
360
361
        $parameters = ['a', 'b', 'c'];
362
363
        // first, testing normal variadic behavior (verifying we didn't screw up in the test asset)
364
        self::assertSame(['a', 'changed', 'c'], (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$parameters));
365
        self::assertSame(['a', 'changed', 'c'], $object->tuz(...$parameters));
366
        self::assertSame(['a', 'changed', 'c'], $parameters, 'by-ref variadic parameter was changed');
367
    }
368
369
    /**
370
     * This test documents a known limitation: `func_get_args()` (and similars) don't work in proxied APIs.
371
     * If you manage to make this test pass, then please do send a patch
372
     *
373
     * @group 265
374
     */
375
    public function testWillNotForwardDynamicArguments() : void
376
    {
377
        $proxyName = $this->generateProxy(ClassWithDynamicArgumentsMethod::class);
378
379
        /* @var $object ClassWithDynamicArgumentsMethod */
380
        $object = $proxyName::staticProxyConstructor(function (& $wrappedInstance) {
381
            $wrappedInstance = new ClassWithDynamicArgumentsMethod();
382
        });
383
384
        self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b'));
385
386
        $this->expectException(\PHPUnit_Framework_ExpectationFailedException::class);
387
388
        self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b'));
389
    }
390
391
    /**
392
     * Generates a proxy for the given class name, and retrieves its class name
393
     */
394
    private function generateProxy(string $parentClassName) : string
395
    {
396
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
397
        $generator          = new LazyLoadingValueHolderGenerator();
398
        $generatedClass     = new ClassGenerator($generatedClassName);
399
        $strategy           = new EvaluatingGeneratorStrategy();
400
401
        $generator->generate(new ReflectionClass($parentClassName), $generatedClass);
402
        $strategy->generate($generatedClass);
403
404
        return $generatedClassName;
405
    }
406
407
    /**
408
     * @param string $className
409
     * @param object $realInstance
410
     * @param Mock   $initializerMatcher
411
     *
412
     * @return callable
413
     */
414
    private function createInitializer(string $className, $realInstance, Mock $initializerMatcher = null) : callable
415
    {
416
        /* @var $initializerMatcher callable|Mock */
417
        if (! $initializerMatcher) {
418
            $initializerMatcher = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
419
420
            $initializerMatcher
421
                ->expects(self::once())
422
                ->method('__invoke')
423
                ->with(
424
                    self::logicalAnd(
425
                        self::isInstanceOf(VirtualProxyInterface::class),
426
                        self::isInstanceOf($className)
427
                    ),
428
                    $realInstance
429
                );
430
        }
431
432
        return function (
433
            & $wrappedObject,
434
            VirtualProxyInterface $proxy,
435
            $method,
436
            $params,
437
            & $initializer
438
        ) use (
439
            $initializerMatcher,
440
            $realInstance
441
        ) : void {
442
            $initializer   = null;
443
            $wrappedObject = $realInstance;
444
445
            $initializerMatcher($proxy, $wrappedObject, $method, $params);
446
        };
447
    }
448
449
    /**
450
     * Generates a list of object | invoked method | parameters | expected result
451
     */
452
    public function getProxyMethods() : array
453
    {
454
        $selfHintParam = new ClassWithSelfHint();
455
        $empty         = new EmptyClass();
456
457
        return [
458
            [
459
                BaseClass::class,
460
                new BaseClass(),
461
                'publicMethod',
462
                [],
463
                'publicMethodDefault'
464
            ],
465
            [
466
                BaseClass::class,
467
                new BaseClass(),
468
                'publicTypeHintedMethod',
469
                [new stdClass()],
470
                'publicTypeHintedMethodDefault'
471
            ],
472
            [
473
                BaseClass::class,
474
                new BaseClass(),
475
                'publicByReferenceMethod',
476
                [],
477
                'publicByReferenceMethodDefault'
478
            ],
479
            [
480
                BaseInterface::class,
481
                new BaseClass(),
482
                'publicMethod',
483
                [],
484
                'publicMethodDefault'
485
            ],
486
            [
487
                ClassWithSelfHint::class,
488
                new ClassWithSelfHint(),
489
                'selfHintMethod',
490
                ['parameter' => $selfHintParam],
491
                $selfHintParam
492
            ],
493
            [
494
                ClassWithParentHint::class,
495
                new ClassWithParentHint(),
496
                'parentHintMethod',
497
                ['parameter' => $empty],
498
                $empty
499
            ],
500
            [
501
                ClassWithMethodWithVariadicFunction::class,
502
                new ClassWithMethodWithVariadicFunction(),
503
                'buz',
504
                ['Ocramius', 'Malukenho'],
505
                ['Ocramius', 'Malukenho']
506
            ],
507
            [
508
                ClassWithMethodWithByRefVariadicFunction::class,
509
                new ClassWithMethodWithByRefVariadicFunction(),
510
                'tuz',
511
                ['Ocramius', 'Malukenho'],
512
                ['Ocramius', 'changed']
513
            ],
514
            [
515
                ClassWithMagicMethods::class,
516
                new ClassWithMagicMethods(),
517
                '__get',
518
                ['parameterName'],
519
                'parameterName',
520
            ],
521
            [
522
                ClassWithMagicMethods::class,
523
                new ClassWithMagicMethods(),
524
                '__set',
525
                ['foo', 'bar'],
526
                ['foo' => 'bar'],
527
            ],
528
            [
529
                ClassWithMagicMethods::class,
530
                new ClassWithMagicMethods(),
531
                '__isset',
532
                ['example'],
533
                true,
534
            ],
535
            [
536
                ClassWithMagicMethods::class,
537
                new ClassWithMagicMethods(),
538
                '__isset',
539
                [''],
540
                false,
541
            ],
542
            [
543
                ClassWithMagicMethods::class,
544
                new ClassWithMagicMethods(),
545
                '__unset',
546
                ['example'],
547
                true,
548
            ],
549
        ];
550
    }
551
552
    /**
553
     * Generates proxies and instances with a public property to feed to the property accessor methods
554
     *
555
     * @return array
556
     */
557
    public function getPropertyAccessProxies() : array
558
    {
559
        $instance1 = new BaseClass();
560
        $proxyName1 = $this->generateProxy(get_class($instance1));
561
        $instance2 = new BaseClass();
562
        $proxyName2 = $this->generateProxy(get_class($instance2));
563
564
        return [
565
            [
566
                $instance1,
567
                $proxyName1::staticProxyConstructor(
568
                    $this->createInitializer(BaseClass::class, $instance1)
569
                ),
570
                'publicProperty',
571
                'publicPropertyDefault',
572
            ],
573
            [
574
                $instance2,
575
                unserialize(serialize($proxyName2::staticProxyConstructor(
576
                    $this->createInitializer(BaseClass::class, $instance2)
577
                ))),
578
                'publicProperty',
579
                'publicPropertyDefault',
580
            ],
581
        ];
582
    }
583
584
    /**
585
     * @group 276
586
     *
587
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
588
     *
589
     * @param object $callerObject
590
     * @param object $realInstance
591
     * @param string $method
592
     * @param string $expectedValue
593
     */
594
    public function testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope(
595
        $callerObject,
596
        $realInstance,
597
        string $method,
598
        string $expectedValue
599
    ) : void {
600
        $proxyName = $this->generateProxy(get_class($realInstance));
601
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
602
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer(get_class($realInstance), $realInstance));
603
604
        /* @var $accessor callable */
605
        $accessor = [$callerObject, $method];
606
607
        self::assertFalse($proxy->isProxyInitialized());
608
        self::assertSame($expectedValue, $accessor($proxy));
609
        self::assertTrue($proxy->isProxyInitialized());
610
    }
611
612
    /**
613
     * @group 276
614
     *
615
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
616
     *
617
     * @param object $callerObject
618
     * @param object $realInstance
619
     * @param string $method
620
     * @param string $expectedValue
621
     */
622
    public function testWillFetchMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope(
623
        $callerObject,
624
        $realInstance,
625
        string $method,
626
        string $expectedValue
627
    ) : void {
628
        $proxyName = $this->generateProxy(get_class($realInstance));
629
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
630
        $proxy = unserialize(serialize(
631
            $proxyName::staticProxyConstructor($this->createInitializer(get_class($realInstance), $realInstance))
632
        ));
633
634
        /* @var $accessor callable */
635
        $accessor = [$callerObject, $method];
636
637
        self::assertTrue($proxy->isProxyInitialized());
638
        self::assertSame($expectedValue, $accessor($proxy));
639
    }
640
641
    /**
642
     * @group 276
643
     *
644
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
645
     *
646
     * @param object $callerObject
647
     * @param object $realInstance
648
     * @param string $method
649
     * @param string $expectedValue
650
     */
651
    public function testWillFetchMembersOfOtherClonedProxiesWithTheSamePrivateScope(
652
        $callerObject,
653
        $realInstance,
654
        string $method,
655
        string $expectedValue
656
    ) : void {
657
        $proxyName = $this->generateProxy(get_class($realInstance));
658
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
659
        $proxy = clone $proxyName::staticProxyConstructor(
660
            $this->createInitializer(get_class($realInstance), $realInstance)
661
        );
662
663
        /* @var $accessor callable */
664
        $accessor = [$callerObject, $method];
665
666
        self::assertTrue($proxy->isProxyInitialized());
667
        self::assertSame($expectedValue, $accessor($proxy));
668
    }
669
670
    /**
671
     * @group 327
672
     */
673
    public function testWillExecuteLogicInAVoidMethod() : void
674
    {
675
        $proxyName = $this->generateProxy(VoidCounter::class);
676
        /* @var $proxy VoidCounter */
677
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer(VoidCounter::class, new VoidCounter()));
678
679
        $increment = random_int(100, 1000);
680
681
        $proxy->increment($increment);
682
683
        self::assertSame($increment, $proxy->counter);
684
    }
685
686
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : \Generator
687
    {
688
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
689
690
        foreach ((new \ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
691
            $propertyName  = $property->getName();
692
            $expectedValue = uniqid('', true);
693
694
            // callee is an actual object
695
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
696
                new OtherObjectAccessClass(),
697
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
698
                'get' . ucfirst($propertyName),
699
                $expectedValue,
700
            ];
701
702
            $expectedValue = uniqid('', true);
703
704
            // callee is a proxy (not to be lazy-loaded!)
705
            yield '(proxy) ' . OtherObjectAccessClass::class . '#$' . $propertyName => [
706
                $proxyClass::staticProxyConstructor($this->createInitializer(
707
                    OtherObjectAccessClass::class,
708
                    new OtherObjectAccessClass()
709
                )),
710
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
711
                'get' . ucfirst($propertyName),
712
                $expectedValue,
713
            ];
714
        }
715
    }
716
717
    /**
718
     * @param object $instance
719
     * @param array  $values
720
     *
721
     * @return object
722
     */
723
    private function buildInstanceWithValues($instance, array $values)
724
    {
725
        foreach ($values as $property => $value) {
726
            $property = new \ReflectionProperty($instance, $property);
727
728
            $property->setAccessible(true);
729
730
            $property->setValue($instance, $value);
731
        }
732
733
        return $instance;
734
    }
735
}
736