Completed
Pull Request — master (#266)
by Marco
05:26
created

getPropertyAccessProxies()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 26
rs 8.8571
cc 1
eloc 16
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
namespace ProxyManagerTest\Functional;
20
21
use PHPUnit_Framework_MockObject_MockObject as Mock;
22
use PHPUnit_Framework_TestCase;
23
use ProxyManager\Generator\ClassGenerator;
24
use ProxyManager\Generator\Util\UniqueIdentifierGenerator;
25
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
26
use ProxyManager\Proxy\VirtualProxyInterface;
27
use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator;
28
use ProxyManagerTestAsset\BaseClass;
29
use ProxyManagerTestAsset\BaseInterface;
30
use ProxyManagerTestAsset\ClassWithCounterConstructor;
31
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction;
32
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction;
33
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
34
use ProxyManagerTestAsset\ClassWithPublicProperties;
35
use ProxyManagerTestAsset\ClassWithSelfHint;
36
use ReflectionClass;
37
use stdClass;
38
39
/**
40
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator} produced objects
41
 *
42
 * @author Marco Pivetta <[email protected]>
43
 * @license MIT
44
 *
45
 * @group Functional
46
 * @coversNothing
47
 */
48
class LazyLoadingValueHolderFunctionalTest extends PHPUnit_Framework_TestCase
49
{
50
    /**
51
     * @dataProvider getProxyMethods
52
     *
53
     * @param string  $className
54
     * @param object  $instance
55
     * @param string  $method
56
     * @param mixed[] $params
57
     * @param mixed   $expectedValue
58
     */
59
    public function testMethodCalls($className, $instance, $method, array $params, $expectedValue)
60
    {
61
        $proxyName = $this->generateProxy($className);
62
63
        /* @var $proxy VirtualProxyInterface */
64
        $proxy = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
65
66
        $this->assertFalse($proxy->isProxyInitialized());
67
68
        /* @var $callProxyMethod callable */
69
        $callProxyMethod = [$proxy, $method];
70
        $parameterValues = array_values($params);
71
72
        $this->assertSame($expectedValue, $callProxyMethod(...$parameterValues));
73
        $this->assertTrue($proxy->isProxyInitialized());
74
        $this->assertSame($instance, $proxy->getWrappedValueHolderValue());
75
    }
76
77
    /**
78
     * @dataProvider getProxyMethods
79
     *
80
     * @param string  $className
81
     * @param object  $instance
82
     * @param string  $method
83
     * @param mixed[] $params
84
     * @param mixed   $expectedValue
85
     */
86
    public function testMethodCallsAfterUnSerialization($className, $instance, $method, array $params, $expectedValue)
87
    {
88
        $proxyName = $this->generateProxy($className);
89
90
        /* @var $proxy VirtualProxyInterface */
91
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor(
92
            $this->createInitializer($className, $instance)
93
        )));
94
95
        $this->assertTrue($proxy->isProxyInitialized());
96
97
        /* @var $callProxyMethod callable */
98
        $callProxyMethod = [$proxy, $method];
99
        $parameterValues = array_values($params);
100
101
        self::assertInternalType('callable', $callProxyMethod);
102
103
        $this->assertSame($expectedValue, $callProxyMethod(...$parameterValues));
104
        $this->assertEquals($instance, $proxy->getWrappedValueHolderValue());
105
    }
106
107
    /**
108
     * @dataProvider getProxyMethods
109
     *
110
     * @param string  $className
111
     * @param object  $instance
112
     * @param string  $method
113
     * @param mixed[] $params
114
     * @param mixed   $expectedValue
115
     */
116
    public function testMethodCallsAfterCloning($className, $instance, $method, array $params, $expectedValue)
117
    {
118
        $proxyName = $this->generateProxy($className);
119
120
        /* @var $proxy VirtualProxyInterface */
121
        $proxy  = $proxyName::staticProxyConstructor($this->createInitializer($className, $instance));
122
        $cloned = clone $proxy;
123
124
        $this->assertTrue($cloned->isProxyInitialized());
125
        $this->assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue());
126
127
        /* @var $callProxyMethod callable */
128
        $callProxyMethod = [$cloned, $method];
129
        $parameterValues = array_values($params);
130
131
        self::assertInternalType('callable', $callProxyMethod);
132
133
        $this->assertSame($expectedValue, $callProxyMethod(...$parameterValues));
134
        $this->assertEquals($instance, $cloned->getWrappedValueHolderValue());
135
    }
136
137
    /**
138
     * @dataProvider getPropertyAccessProxies
139
     *
140
     * @param object                $instance
141
     * @param VirtualProxyInterface $proxy
142
     * @param string                $publicProperty
143
     * @param mixed                 $propertyValue
144
     */
145
    public function testPropertyReadAccess($instance, VirtualProxyInterface $proxy, $publicProperty, $propertyValue)
146
    {
147
        $this->assertSame($propertyValue, $proxy->$publicProperty);
148
        $this->assertTrue($proxy->isProxyInitialized());
149
        $this->assertEquals($instance, $proxy->getWrappedValueHolderValue());
150
    }
151
152
    /**
153
     * @dataProvider getPropertyAccessProxies
154
     *
155
     * @param object                $instance
156
     * @param VirtualProxyInterface $proxy
157
     * @param string                $publicProperty
158
     */
159
    public function testPropertyWriteAccess($instance, VirtualProxyInterface $proxy, $publicProperty)
160
    {
161
        $newValue               = uniqid();
162
        $proxy->$publicProperty = $newValue;
163
164
        $this->assertTrue($proxy->isProxyInitialized());
165
        $this->assertSame($newValue, $proxy->$publicProperty);
166
        $this->assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty);
167
    }
168
169
    /**
170
     * @dataProvider getPropertyAccessProxies
171
     *
172
     * @param object                $instance
173
     * @param VirtualProxyInterface $proxy
174
     * @param string                $publicProperty
175
     */
176
    public function testPropertyExistence($instance, VirtualProxyInterface $proxy, $publicProperty)
177
    {
178
        $this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
179
        $this->assertTrue($proxy->isProxyInitialized());
180
        $this->assertEquals($instance, $proxy->getWrappedValueHolderValue());
181
    }
182
183
    /**
184
     * @dataProvider getPropertyAccessProxies
185
     *
186
     * @param object                $instance
187
     * @param VirtualProxyInterface $proxy
188
     * @param string                $publicProperty
189
     */
190
    public function testPropertyAbsence($instance, VirtualProxyInterface $proxy, $publicProperty)
191
    {
192
        $instance = $proxy->getWrappedValueHolderValue() ? $proxy->getWrappedValueHolderValue() : $instance;
193
        $instance->$publicProperty = null;
194
        $this->assertFalse(isset($proxy->$publicProperty));
195
        $this->assertTrue($proxy->isProxyInitialized());
196
    }
197
198
    /**
199
     * @dataProvider getPropertyAccessProxies
200
     *
201
     * @param object                $instance
202
     * @param VirtualProxyInterface $proxy
203
     * @param string                $publicProperty
204
     */
205
    public function testPropertyUnset($instance, VirtualProxyInterface $proxy, $publicProperty)
206
    {
207
        $instance = $proxy->getWrappedValueHolderValue() ? $proxy->getWrappedValueHolderValue() : $instance;
208
        unset($proxy->$publicProperty);
209
210
        $this->assertTrue($proxy->isProxyInitialized());
211
212
        $this->assertFalse(isset($instance->$publicProperty));
213
        $this->assertFalse(isset($proxy->$publicProperty));
214
    }
215
216
    /**
217
     * Verifies that accessing a public property containing an array behaves like in a normal context
218
     */
219
    public function testCanWriteToArrayKeysInPublicProperty()
220
    {
221
        $instance    = new ClassWithPublicArrayProperty();
222
        $className   = get_class($instance);
223
        $initializer = $this->createInitializer($className, $instance);
224
        $proxyName   = $this->generateProxy($className);
225
        /* @var $proxy ClassWithPublicArrayProperty */
226
        $proxy       = $proxyName::staticProxyConstructor($initializer);
227
228
        $proxy->arrayProperty['foo'] = 'bar';
229
230
        $this->assertSame('bar', $proxy->arrayProperty['foo']);
231
232
        $proxy->arrayProperty = ['tab' => 'taz'];
233
234
        $this->assertSame(['tab' => 'taz'], $proxy->arrayProperty);
235
    }
236
237
    /**
238
     * Verifies that public properties retrieved via `__get` don't get modified in the object itself
239
     */
240
    public function testWillNotModifyRetrievedPublicProperties()
241
    {
242
        $instance    = new ClassWithPublicProperties();
243
        $className   = get_class($instance);
244
        $initializer = $this->createInitializer($className, $instance);
245
        $proxyName   = $this->generateProxy($className);
246
        /* @var $proxy ClassWithPublicProperties */
247
        $proxy       = $proxyName::staticProxyConstructor($initializer);
248
        $variable    = $proxy->property0;
249
250
        $this->assertSame('property0', $variable);
251
252
        $variable = 'foo';
253
254
        $this->assertSame('property0', $proxy->property0);
255
        $this->assertSame('foo', $variable);
256
    }
257
258
    /**
259
     * Verifies that public properties references retrieved via `__get` modify in the object state
260
     */
261
    public function testWillModifyByRefRetrievedPublicProperties()
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
        $this->assertSame('property0', $variable);
272
273
        $variable = 'foo';
274
275
        $this->assertSame('foo', $proxy->property0);
276
        $this->assertSame('foo', $variable);
277
    }
278
279
    /**
280
     * @group 16
281
     *
282
     * Verifies that initialization of a value holder proxy may happen multiple times
283
     */
284
    public function testWillAllowMultipleProxyInitialization()
285
    {
286
        $proxyClass  = $this->generateProxy(BaseClass::class);
287
        $counter     = 0;
288
289
        /* @var $proxy BaseClass */
290
        $proxy = $proxyClass::staticProxyConstructor(function (& $wrappedInstance) use (& $counter) {
291
            $wrappedInstance = new BaseClass();
292
293
            $wrappedInstance->publicProperty = (string) ($counter += 1);
294
        });
295
296
        $this->assertSame('1', $proxy->publicProperty);
297
        $this->assertSame('2', $proxy->publicProperty);
298
        $this->assertSame('3', $proxy->publicProperty);
299
    }
300
301
    /**
302
     * @group 115
303
     * @group 175
304
     */
305
    public function testWillBehaveLikeObjectWithNormalConstructor()
306
    {
307
        $instance = new ClassWithCounterConstructor(10);
308
309
        $this->assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
310
        $this->assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
311
        $instance->__construct(3);
312
        $this->assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
313
        $this->assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected');
314
315
        $proxyName = $this->generateProxy(get_class($instance));
316
317
        /* @var $proxy ClassWithCounterConstructor */
318
        $proxy = new $proxyName(15);
319
320
        $this->assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
321
        $this->assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
322
        $proxy->__construct(5);
323
        $this->assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
324
        $this->assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
325
    }
326
327
    public function testWillRespectVariadicByRefParameterForwarding()
328
    {
329
        $proxyName   = $this->generateProxy(ClassWithMethodWithByRefVariadicFunction::class);
330
        /* @var $object ClassWithMethodWithByRefVariadicFunction */
331
        $object = $proxyName::staticProxyConstructor(function (& $wrappedInstance) use (& $counter) {
332
            $wrappedInstance = new ClassWithMethodWithByRefVariadicFunction();
333
        });
334
335
        $parameters = ['a', 'b', 'c'];
336
337
        // first, testing normal variadic behavior (verifying we didn't screw up in the test asset)
338
        self::assertSame(['a', 'changed', 'c'], (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$parameters));
339
        self::assertSame(['a', 'changed', 'c'], $object->tuz(...$parameters));
340
        self::assertSame(['a', 'changed', 'c'], $parameters, 'by-ref variadic parameter was changed');
341
    }
342
343
    /**
344
     * Generates a proxy for the given class name, and retrieves its class name
345
     *
346
     * @param string $parentClassName
347
     *
348
     * @return string
349
     */
350
    private function generateProxy($parentClassName)
351
    {
352
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
353
        $generator          = new LazyLoadingValueHolderGenerator();
354
        $generatedClass     = new ClassGenerator($generatedClassName);
355
        $strategy           = new EvaluatingGeneratorStrategy();
356
357
        $generator->generate(new ReflectionClass($parentClassName), $generatedClass);
358
        $strategy->generate($generatedClass);
359
360
        return $generatedClassName;
361
    }
362
363
    /**
364
     * @param string $className
365
     * @param object $realInstance
366
     * @param Mock   $initializerMatcher
367
     *
368
     * @return \Closure
369
     */
370
    private function createInitializer($className, $realInstance, Mock $initializerMatcher = null)
371
    {
372
        if (null === $initializerMatcher) {
373
            $initializerMatcher = $this->getMock(stdClass::class, ['__invoke']);
374
375
            $initializerMatcher
376
                ->expects($this->once())
377
                ->method('__invoke')
378
                ->with(
379
                    $this->logicalAnd(
380
                        $this->isInstanceOf(VirtualProxyInterface::class),
381
                        $this->isInstanceOf($className)
382
                    ),
383
                    $realInstance
384
                );
385
        }
386
387
        /* @var $initializerMatcher callable */
388
        $initializerMatcher = $initializerMatcher ?: $this->getMock(stdClass::class, ['__invoke']);
389
390
        return function (
391
            & $wrappedObject,
392
            VirtualProxyInterface $proxy,
393
            $method,
394
            $params,
395
            & $initializer
396
        ) use (
397
            $initializerMatcher,
398
            $realInstance
399
        ) {
400
            $initializer   = null;
401
            $wrappedObject = $realInstance;
402
403
            $initializerMatcher($proxy, $wrappedObject, $method, $params);
404
        };
405
    }
406
407
    /**
408
     * Generates a list of object | invoked method | parameters | expected result
409
     *
410
     * @return array
411
     */
412
    public function getProxyMethods()
413
    {
414
        $selfHintParam = new ClassWithSelfHint();
415
416
        return [
417
            [
418
                BaseClass::class,
419
                new BaseClass(),
420
                'publicMethod',
421
                [],
422
                'publicMethodDefault'
423
            ],
424
            [
425
                BaseClass::class,
426
                new BaseClass(),
427
                'publicTypeHintedMethod',
428
                [new stdClass()],
429
                'publicTypeHintedMethodDefault'
430
            ],
431
            [
432
                BaseClass::class,
433
                new BaseClass(),
434
                'publicByReferenceMethod',
435
                [],
436
                'publicByReferenceMethodDefault'
437
            ],
438
            [
439
                BaseInterface::class,
440
                new BaseClass(),
441
                'publicMethod',
442
                [],
443
                'publicMethodDefault'
444
            ],
445
            [
446
                ClassWithSelfHint::class,
447
                new ClassWithSelfHint(),
448
                'selfHintMethod',
449
                ['parameter' => $selfHintParam],
450
                $selfHintParam
451
            ],
452
            [
453
                ClassWithMethodWithVariadicFunction::class,
454
                new ClassWithMethodWithVariadicFunction(),
455
                'buz',
456
                ['Ocramius', 'Malukenho'],
457
                ['Ocramius', 'Malukenho']
458
            ],
459
            [
460
                ClassWithMethodWithByRefVariadicFunction::class,
461
                new ClassWithMethodWithByRefVariadicFunction(),
462
                'tuz',
463
                ['Ocramius', 'Malukenho'],
464
                ['Ocramius', 'changed']
465
            ]
466
        ];
467
    }
468
469
    /**
470
     * Generates proxies and instances with a public property to feed to the property accessor methods
471
     *
472
     * @return array
473
     */
474
    public function getPropertyAccessProxies()
475
    {
476
        $instance1 = new BaseClass();
477
        $proxyName1 = $this->generateProxy(get_class($instance1));
478
        $instance2 = new BaseClass();
479
        $proxyName2 = $this->generateProxy(get_class($instance2));
480
481
        return [
482
            [
483
                $instance1,
484
                $proxyName1::staticProxyConstructor(
485
                    $this->createInitializer(BaseClass::class, $instance1)
486
                ),
487
                'publicProperty',
488
                'publicPropertyDefault',
489
            ],
490
            [
491
                $instance2,
492
                unserialize(serialize($proxyName2::staticProxyConstructor(
493
                    $this->createInitializer(BaseClass::class, $instance2)
494
                ))),
495
                'publicProperty',
496
                'publicPropertyDefault',
497
            ],
498
        ];
499
    }
500
}
501