Completed
Pull Request — master (#345)
by Jefersson
05:30
created

LazyLoadingValueHolderFunctionalTest   B

Complexity

Total Complexity 29

Size/Duplication

Total Lines 668
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 18
dl 0
loc 668
rs 7.3797
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A testMethodCalls() 0 17 1
B testMethodCallsAfterUnSerialization() 0 25 1
B testMethodCallsAfterCloning() 0 25 1
A testPropertyReadAccess() 0 10 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
A generateProxy() 0 12 1
B createInitializer() 0 34 2
B getProxyMethods() 0 98 1
B getPropertyAccessProxies() 0 26 1
A testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope() 0 17 1
A testWillFetchMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope() 0 18 1
A testWillFetchMembersOfOtherClonedProxiesWithTheSamePrivateScope() 0 18 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\ClassWithMagicMethodsWeirdlyTypeHinted;
37
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction;
38
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction;
39
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
40
use ProxyManagerTestAsset\ClassWithPublicProperties;
41
use ProxyManagerTestAsset\ClassWithSelfHint;
42
use ProxyManagerTestAsset\OtherObjectAccessClass;
43
use ReflectionClass;
44
use stdClass;
45
46
/**
47
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator} produced objects
48
 *
49
 * @author Marco Pivetta <[email protected]>
50
 * @license MIT
51
 *
52
 * @group Functional
53
 * @coversNothing
54
 */
55
class LazyLoadingValueHolderFunctionalTest extends PHPUnit_Framework_TestCase
56
{
57
    /**
58
     * @dataProvider getProxyMethods
59
     *
60
     * @param string  $className
61
     * @param object  $instance
62
     * @param string  $method
63
     * @param mixed[] $params
64
     * @param mixed   $expectedValue
65
     */
66
    public function testMethodCalls(string $className, $instance, string $method, array $params, $expectedValue)
67
    {
68
        $proxyName = $this->generateProxy($className);
69
70
        /* @var $proxy VirtualProxyInterface */
71
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
72
73
        self::assertFalse($proxy->isProxyInitialized());
74
75
        /* @var $callProxyMethod callable */
76
        $callProxyMethod = [$proxy, $method];
77
        $parameterValues = array_values($params);
78
79
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
80
        self::assertTrue($proxy->isProxyInitialized());
81
        self::assertSame($instance, $proxy->getWrappedValueHolderValue());
82
    }
83
84
    /**
85
     * @dataProvider getProxyMethods
86
     *
87
     * @param string  $className
88
     * @param object  $instance
89
     * @param string  $method
90
     * @param mixed[] $params
91
     * @param mixed   $expectedValue
92
     */
93
    public function testMethodCallsAfterUnSerialization(
94
        string $className,
95
        $instance,
96
        string $method,
97
        array $params,
98
        $expectedValue
99
    ) {
100
        $proxyName = $this->generateProxy($className);
101
102
        /* @var $proxy VirtualProxyInterface */
103
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
104
            $this->createInitializer($className, $instance)
105
        )));
106
107
        self::assertTrue($proxy->isProxyInitialized());
108
109
        /* @var $callProxyMethod callable */
110
        $callProxyMethod = [$proxy, $method];
111
        $parameterValues = array_values($params);
112
113
        self::assertInternalType('callable', $callProxyMethod);
114
115
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
116
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
117
    }
118
119
    /**
120
     * @dataProvider getProxyMethods
121
     *
122
     * @param string  $className
123
     * @param object  $instance
124
     * @param string  $method
125
     * @param mixed[] $params
126
     * @param mixed   $expectedValue
127
     */
128
    public function testMethodCallsAfterCloning(
129
        string $className,
130
        $instance,
131
        string $method,
132
        array $params,
133
        $expectedValue
134
    ) {
135
        $proxyName = $this->generateProxy($className);
136
137
        /* @var $proxy VirtualProxyInterface */
138
        $proxy  = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
139
        $cloned = clone $proxy;
140
141
        self::assertTrue($cloned->isProxyInitialized());
142
        self::assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue());
143
144
        /* @var $callProxyMethod callable */
145
        $callProxyMethod = [$cloned, $method];
146
        $parameterValues = array_values($params);
147
148
        self::assertInternalType('callable', $callProxyMethod);
149
150
        self::assertSame($expectedValue, $callProxyMethod(...$parameterValues));
151
        self::assertEquals($instance, $cloned->getWrappedValueHolderValue());
152
    }
153
154
    /**
155
     * @dataProvider getPropertyAccessProxies
156
     *
157
     * @param object                $instance
158
     * @param VirtualProxyInterface $proxy
159
     * @param string                $publicProperty
160
     * @param mixed                 $propertyValue
161
     */
162
    public function testPropertyReadAccess(
163
        $instance,
164
        VirtualProxyInterface $proxy,
165
        string $publicProperty,
166
        $propertyValue
167
    ) {
168
        self::assertSame($propertyValue, $proxy->$publicProperty);
169
        self::assertTrue($proxy->isProxyInitialized());
170
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
171
    }
172
173
    /**
174
     * @dataProvider getPropertyAccessProxies
175
     *
176
     * @param object                $instance
177
     * @param VirtualProxyInterface $proxy
178
     * @param string                $publicProperty
179
     */
180
    public function testPropertyWriteAccess($instance, VirtualProxyInterface $proxy, string $publicProperty)
0 ignored issues
show
Unused Code introduced by
The parameter $instance is not used and could be removed.

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

Loading history...
181
    {
182
        $newValue               = uniqid();
183
        $proxy->$publicProperty = $newValue;
184
185
        self::assertTrue($proxy->isProxyInitialized());
186
        self::assertSame($newValue, $proxy->$publicProperty);
187
        self::assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty);
188
    }
189
190
    /**
191
     * @dataProvider getPropertyAccessProxies
192
     *
193
     * @param object                $instance
194
     * @param VirtualProxyInterface $proxy
195
     * @param string                $publicProperty
196
     */
197
    public function testPropertyExistence($instance, VirtualProxyInterface $proxy, string $publicProperty)
198
    {
199
        self::assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
200
        self::assertTrue($proxy->isProxyInitialized());
201
        self::assertEquals($instance, $proxy->getWrappedValueHolderValue());
202
    }
203
204
    /**
205
     * @dataProvider getPropertyAccessProxies
206
     *
207
     * @param object                $instance
208
     * @param VirtualProxyInterface $proxy
209
     * @param string                $publicProperty
210
     */
211
    public function testPropertyAbsence($instance, VirtualProxyInterface $proxy, string $publicProperty)
212
    {
213
        $instance = $proxy->getWrappedValueHolderValue() ?: $instance;
214
        $instance->$publicProperty = null;
215
        self::assertFalse(isset($proxy->$publicProperty));
216
        self::assertTrue($proxy->isProxyInitialized());
217
    }
218
219
    /**
220
     * @dataProvider getPropertyAccessProxies
221
     *
222
     * @param object                $instance
223
     * @param VirtualProxyInterface $proxy
224
     * @param string                $publicProperty
225
     */
226
    public function testPropertyUnset($instance, VirtualProxyInterface $proxy, string $publicProperty)
227
    {
228
        $instance = $proxy->getWrappedValueHolderValue() ?: $instance;
229
        unset($proxy->$publicProperty);
230
231
        self::assertTrue($proxy->isProxyInitialized());
232
233
        self::assertFalse(isset($instance->$publicProperty));
234
        self::assertFalse(isset($proxy->$publicProperty));
235
    }
236
237
    /**
238
     * Verifies that accessing a public property containing an array behaves like in a normal context
239
     */
240
    public function testCanWriteToArrayKeysInPublicProperty()
241
    {
242
        $instance    = new ClassWithPublicArrayProperty();
243
        $className   = get_class($instance);
244
        $initializer = $this->createInitializer($className, $instance);
245
        $proxyName   = $this->generateProxy($className);
246
        /* @var $proxy ClassWithPublicArrayProperty */
247
        $proxy       = $proxyName::staticProxyConstructor($initializer);
248
249
        $proxy->arrayProperty['foo'] = 'bar';
250
251
        self::assertSame('bar', $proxy->arrayProperty['foo']);
252
253
        $proxy->arrayProperty = ['tab' => 'taz'];
254
255
        self::assertSame(['tab' => 'taz'], $proxy->arrayProperty);
256
    }
257
258
    /**
259
     * Verifies that public properties retrieved via `__get` don't get modified in the object itself
260
     */
261
    public function testWillNotModifyRetrievedPublicProperties()
262
    {
263
        $instance    = new ClassWithPublicProperties();
264
        $className   = get_class($instance);
265
        $initializer = $this->createInitializer($className, $instance);
266
        $proxyName   = $this->generateProxy($className);
267
        /* @var $proxy ClassWithPublicProperties */
268
        $proxy       = $proxyName::staticProxyConstructor($initializer);
269
        $variable    = $proxy->property0;
270
271
        self::assertSame('property0', $variable);
272
273
        $variable = 'foo';
274
275
        self::assertSame('property0', $proxy->property0);
276
        self::assertSame('foo', $variable);
277
    }
278
279
    /**
280
     * Verifies that public properties references retrieved via `__get` modify in the object state
281
     */
282
    public function testWillModifyByRefRetrievedPublicProperties()
283
    {
284
        $instance    = new ClassWithPublicProperties();
285
        $className   = get_class($instance);
286
        $initializer = $this->createInitializer($className, $instance);
287
        $proxyName   = $this->generateProxy($className);
288
        /* @var $proxy ClassWithPublicProperties */
289
        $proxy       = $proxyName::staticProxyConstructor($initializer);
290
        $variable    = & $proxy->property0;
291
292
        self::assertSame('property0', $variable);
293
294
        $variable = 'foo';
295
296
        self::assertSame('foo', $proxy->property0);
297
        self::assertSame('foo', $variable);
298
    }
299
300
    /**
301
     * @group 16
302
     *
303
     * Verifies that initialization of a value holder proxy may happen multiple times
304
     */
305
    public function testWillAllowMultipleProxyInitialization()
306
    {
307
        $proxyClass  = $this->generateProxy(BaseClass::class);
308
        $counter     = 0;
309
310
        /* @var $proxy BaseClass */
311
        $proxy = $proxyClass::staticProxyConstructor(function (& $wrappedInstance) use (& $counter) {
312
            $wrappedInstance = new BaseClass();
313
314
            $wrappedInstance->publicProperty = (string) ($counter += 1);
315
        });
316
317
        self::assertSame('1', $proxy->publicProperty);
318
        self::assertSame('2', $proxy->publicProperty);
319
        self::assertSame('3', $proxy->publicProperty);
320
    }
321
322
    /**
323
     * @group 115
324
     * @group 175
325
     */
326
    public function testWillBehaveLikeObjectWithNormalConstructor()
327
    {
328
        $instance = new ClassWithCounterConstructor(10);
329
330
        self::assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
331
        self::assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
332
        $instance->__construct(3);
333
        self::assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
334
        self::assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected');
335
336
        $proxyName = $this->generateProxy(get_class($instance));
337
338
        /* @var $proxy ClassWithCounterConstructor */
339
        $proxy = new $proxyName(15);
340
341
        self::assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
342
        self::assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
343
        $proxy->__construct(5);
344
        self::assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
345
        self::assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
346
    }
347
348
    /**
349
     * @group 265
350
     */
351
    public function testWillForwardVariadicByRefArguments()
352
    {
353
        $proxyName   = $this->generateProxy(ClassWithMethodWithByRefVariadicFunction::class);
354
        /* @var $object ClassWithMethodWithByRefVariadicFunction */
355
        $object = $proxyName::staticProxyConstructor(function (& $wrappedInstance) {
356
            $wrappedInstance = new ClassWithMethodWithByRefVariadicFunction();
357
        });
358
359
        $parameters = ['a', 'b', 'c'];
360
361
        // first, testing normal variadic behavior (verifying we didn't screw up in the test asset)
362
        self::assertSame(['a', 'changed', 'c'], (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$parameters));
363
        self::assertSame(['a', 'changed', 'c'], $object->tuz(...$parameters));
364
        self::assertSame(['a', 'changed', 'c'], $parameters, 'by-ref variadic parameter was changed');
365
    }
366
367
    /**
368
     * This test documents a known limitation: `func_get_args()` (and similars) don't work in proxied APIs.
369
     * If you manage to make this test pass, then please do send a patch
370
     *
371
     * @group 265
372
     */
373
    public function testWillNotForwardDynamicArguments()
374
    {
375
        $proxyName = $this->generateProxy(ClassWithDynamicArgumentsMethod::class);
376
377
        /* @var $object ClassWithDynamicArgumentsMethod */
378
        $object = $proxyName::staticProxyConstructor(function (& $wrappedInstance) {
379
            $wrappedInstance = new ClassWithDynamicArgumentsMethod();
380
        });
381
382
        self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b'));
383
384
        $this->expectException(\PHPUnit_Framework_ExpectationFailedException::class);
385
386
        self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b'));
387
    }
388
389
    /**
390
     * Generates a proxy for the given class name, and retrieves its class name
391
     *
392
     * @param string $parentClassName
393
     *
394
     * @return string
395
     */
396
    private function generateProxy(string $parentClassName) : string
397
    {
398
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
399
        $generator          = new LazyLoadingValueHolderGenerator();
400
        $generatedClass     = new ClassGenerator($generatedClassName);
401
        $strategy           = new EvaluatingGeneratorStrategy();
402
403
        $generator->generate(new ReflectionClass($parentClassName), $generatedClass);
404
        $strategy->generate($generatedClass);
405
406
        return $generatedClassName;
407
    }
408
409
    /**
410
     * @param string $className
411
     * @param object $realInstance
412
     * @param Mock   $initializerMatcher
413
     *
414
     * @return callable
415
     */
416
    private function createInitializer(string $className, $realInstance, Mock $initializerMatcher = null) : callable
417
    {
418
        /* @var $initializerMatcher callable|Mock */
419
        if (! $initializerMatcher) {
420
            $initializerMatcher = $this->getMockBuilder(stdClass::class)->setMethods(['__invoke'])->getMock();
421
422
            $initializerMatcher
423
                ->expects(self::once())
424
                ->method('__invoke')
425
                ->with(
426
                    self::logicalAnd(
427
                        self::isInstanceOf(VirtualProxyInterface::class),
428
                        self::isInstanceOf($className)
429
                    ),
430
                    $realInstance
431
                );
432
        }
433
434
        return function (
435
            & $wrappedObject,
436
            VirtualProxyInterface $proxy,
437
            $method,
438
            $params,
439
            & $initializer
440
        ) use (
441
            $initializerMatcher,
442
            $realInstance
443
        ) {
444
            $initializer   = null;
445
            $wrappedObject = $realInstance;
446
447
            $initializerMatcher($proxy, $wrappedObject, $method, $params);
448
        };
449
    }
450
451
    /**
452
     * Generates a list of object | invoked method | parameters | expected result
453
     *
454
     * @return array
455
     */
456
    public function getProxyMethods() : array
457
    {
458
        $selfHintParam = new ClassWithSelfHint();
459
460
        return [
461
            [
462
                BaseClass::class,
463
                new BaseClass(),
464
                'publicMethod',
465
                [],
466
                'publicMethodDefault'
467
            ],
468
            [
469
                BaseClass::class,
470
                new BaseClass(),
471
                'publicTypeHintedMethod',
472
                [new stdClass()],
473
                'publicTypeHintedMethodDefault'
474
            ],
475
            [
476
                BaseClass::class,
477
                new BaseClass(),
478
                'publicByReferenceMethod',
479
                [],
480
                'publicByReferenceMethodDefault'
481
            ],
482
            [
483
                BaseInterface::class,
484
                new BaseClass(),
485
                'publicMethod',
486
                [],
487
                'publicMethodDefault'
488
            ],
489
            [
490
                ClassWithSelfHint::class,
491
                new ClassWithSelfHint(),
492
                'selfHintMethod',
493
                ['parameter' => $selfHintParam],
494
                $selfHintParam
495
            ],
496
            [
497
                ClassWithMethodWithVariadicFunction::class,
498
                new ClassWithMethodWithVariadicFunction(),
499
                'buz',
500
                ['Ocramius', 'Malukenho'],
501
                ['Ocramius', 'Malukenho']
502
            ],
503
            [
504
                ClassWithMethodWithByRefVariadicFunction::class,
505
                new ClassWithMethodWithByRefVariadicFunction(),
506
                'tuz',
507
                ['Ocramius', 'Malukenho'],
508
                ['Ocramius', 'changed']
509
            ],
510
            [
511
                ClassWithMagicMethods::class,
512
                new ClassWithMagicMethods(),
513
                '__get',
514
                ['parameterName'],
515
                'parameterName',
516
            ],
517
            [
518
                ClassWithMagicMethods::class,
519
                new ClassWithMagicMethods(),
520
                '__set',
521
                ['foo', 'bar'],
522
                ['foo' => 'bar'],
523
            ],
524
            [
525
                ClassWithMagicMethods::class,
526
                new ClassWithMagicMethods(),
527
                '__isset',
528
                ['example'],
529
                true,
530
            ],
531
            [
532
                ClassWithMagicMethods::class,
533
                new ClassWithMagicMethods(),
534
                '__isset',
535
                [''],
536
                false,
537
            ],
538
            [
539
                ClassWithMagicMethods::class,
540
                new ClassWithMagicMethods(),
541
                '__unset',
542
                ['example'],
543
                true,
544
            ],
545
            [
546
                ClassWithMagicMethodsWeirdlyTypeHinted::class,
547
                new ClassWithMagicMethodsWeirdlyTypeHinted(),
548
                '__get',
549
                [111],
550
                111,
551
            ],
552
        ];
553
    }
554
555
    /**
556
     * Generates proxies and instances with a public property to feed to the property accessor methods
557
     *
558
     * @return array
559
     */
560
    public function getPropertyAccessProxies() : array
561
    {
562
        $instance1 = new BaseClass();
563
        $proxyName1 = $this->generateProxy(get_class($instance1));
564
        $instance2 = new BaseClass();
565
        $proxyName2 = $this->generateProxy(get_class($instance2));
566
567
        return [
568
            [
569
                $instance1,
570
                $proxyName1::staticProxyConstructor(
571
                    $this->createInitializer(BaseClass::class, $instance1)
572
                ),
573
                'publicProperty',
574
                'publicPropertyDefault',
575
            ],
576
            [
577
                $instance2,
578
                unserialize(serialize($proxyName2::staticProxyConstructor(
579
                    $this->createInitializer(BaseClass::class, $instance2)
580
                ))),
581
                'publicProperty',
582
                'publicPropertyDefault',
583
            ],
584
        ];
585
    }
586
587
    /**
588
     * @group 276
589
     *
590
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
591
     *
592
     * @param object $callerObject
593
     * @param object $realInstance
594
     * @param string $method
595
     * @param string $expectedValue
596
     */
597
    public function testWillLazyLoadMembersOfOtherProxiesWithTheSamePrivateScope(
598
        $callerObject,
599
        $realInstance,
600
        string $method,
601
        string $expectedValue
602
    ) {
603
        $proxyName = $this->generateProxy(get_class($realInstance));
604
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
605
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer(get_class($realInstance), $realInstance));
606
607
        /* @var $accessor callable */
608
        $accessor = [$callerObject, $method];
609
610
        self::assertFalse($proxy->isProxyInitialized());
611
        self::assertSame($expectedValue, $accessor($proxy));
612
        self::assertTrue($proxy->isProxyInitialized());
613
    }
614
615
    /**
616
     * @group 276
617
     *
618
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
619
     *
620
     * @param object $callerObject
621
     * @param object $realInstance
622
     * @param string $method
623
     * @param string $expectedValue
624
     */
625
    public function testWillFetchMembersOfOtherDeSerializedProxiesWithTheSamePrivateScope(
626
        $callerObject,
627
        $realInstance,
628
        string $method,
629
        string $expectedValue
630
    ) {
631
        $proxyName = $this->generateProxy(get_class($realInstance));
632
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
633
        $proxy = unserialize(serialize(
634
            $proxyName::staticProxyConstructor($this->createInitializer(get_class($realInstance), $realInstance))
635
        ));
636
637
        /* @var $accessor callable */
638
        $accessor = [$callerObject, $method];
639
640
        self::assertTrue($proxy->isProxyInitialized());
641
        self::assertSame($expectedValue, $accessor($proxy));
642
    }
643
644
    /**
645
     * @group 276
646
     *
647
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
648
     *
649
     * @param object $callerObject
650
     * @param object $realInstance
651
     * @param string $method
652
     * @param string $expectedValue
653
     */
654
    public function testWillFetchMembersOfOtherClonedProxiesWithTheSamePrivateScope(
655
        $callerObject,
656
        $realInstance,
657
        string $method,
658
        string $expectedValue
659
    ) {
660
        $proxyName = $this->generateProxy(get_class($realInstance));
661
        /* @var $proxy OtherObjectAccessClass|LazyLoadingInterface */
662
        $proxy = clone $proxyName::staticProxyConstructor(
663
            $this->createInitializer(get_class($realInstance), $realInstance)
664
        );
665
666
        /* @var $accessor callable */
667
        $accessor = [$callerObject, $method];
668
669
        self::assertTrue($proxy->isProxyInitialized());
670
        self::assertSame($expectedValue, $accessor($proxy));
671
    }
672
673
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : \Generator
674
    {
675
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
676
677
        foreach ((new \ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
678
            $propertyName  = $property->getName();
679
            $expectedValue = uniqid('', true);
680
681
            // callee is an actual object
682
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
683
                new OtherObjectAccessClass(),
684
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
685
                'get' . ucfirst($propertyName),
686
                $expectedValue,
687
            ];
688
689
            $expectedValue = uniqid('', true);
690
691
            // callee is a proxy (not to be lazy-loaded!)
692
            yield '(proxy) ' . OtherObjectAccessClass::class . '#$' . $propertyName => [
693
                $proxyClass::staticProxyConstructor($this->createInitializer(
694
                    OtherObjectAccessClass::class,
695
                    new OtherObjectAccessClass()
696
                )),
697
                $this->buildInstanceWithValues(new OtherObjectAccessClass(), [$propertyName => $expectedValue]),
698
                'get' . ucfirst($propertyName),
699
                $expectedValue,
700
            ];
701
        }
702
    }
703
704
    /**
705
     * @param object $instance
706
     * @param array  $values
707
     *
708
     * @return object
709
     */
710
    private function buildInstanceWithValues($instance, array $values)
711
    {
712
        foreach ($values as $property => $value) {
713
            $property = new \ReflectionProperty($instance, $property);
714
715
            $property->setAccessible(true);
716
717
            $property->setValue($instance, $value);
718
        }
719
720
        return $instance;
721
    }
722
}
723