Completed
Pull Request — master (#266)
by Marco
06:25
created

testPropertyUnset()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 10
rs 9.4286
cc 2
eloc 6
nc 2
nop 3
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
    /**
328
     * @group 265
329
     */
330
    public function testWillForwardVariadicByRefArguments()
331
    {
332
        $proxyName   = $this->generateProxy(ClassWithMethodWithByRefVariadicFunction::class);
333
        /* @var $object ClassWithMethodWithByRefVariadicFunction */
334
        $object = $proxyName::staticProxyConstructor(function (& $wrappedInstance) use (& $counter) {
335
            $wrappedInstance = new ClassWithMethodWithByRefVariadicFunction();
336
        });
337
338
        $parameters = ['a', 'b', 'c'];
339
340
        // first, testing normal variadic behavior (verifying we didn't screw up in the test asset)
341
        self::assertSame(['a', 'changed', 'c'], (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$parameters));
342
        self::assertSame(['a', 'changed', 'c'], $object->tuz(...$parameters));
343
        self::assertSame(['a', 'changed', 'c'], $parameters, 'by-ref variadic parameter was changed');
344
    }
345
346
    /**
347
     * Generates a proxy for the given class name, and retrieves its class name
348
     *
349
     * @param string $parentClassName
350
     *
351
     * @return string
352
     */
353
    private function generateProxy($parentClassName)
354
    {
355
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
356
        $generator          = new LazyLoadingValueHolderGenerator();
357
        $generatedClass     = new ClassGenerator($generatedClassName);
358
        $strategy           = new EvaluatingGeneratorStrategy();
359
360
        $generator->generate(new ReflectionClass($parentClassName), $generatedClass);
361
        $strategy->generate($generatedClass);
362
363
        return $generatedClassName;
364
    }
365
366
    /**
367
     * @param string $className
368
     * @param object $realInstance
369
     * @param Mock   $initializerMatcher
370
     *
371
     * @return \Closure
372
     */
373
    private function createInitializer($className, $realInstance, Mock $initializerMatcher = null)
374
    {
375
        if (null === $initializerMatcher) {
376
            $initializerMatcher = $this->getMock(stdClass::class, ['__invoke']);
377
378
            $initializerMatcher
379
                ->expects($this->once())
380
                ->method('__invoke')
381
                ->with(
382
                    $this->logicalAnd(
383
                        $this->isInstanceOf(VirtualProxyInterface::class),
384
                        $this->isInstanceOf($className)
385
                    ),
386
                    $realInstance
387
                );
388
        }
389
390
        /* @var $initializerMatcher callable */
391
        $initializerMatcher = $initializerMatcher ?: $this->getMock(stdClass::class, ['__invoke']);
392
393
        return function (
394
            & $wrappedObject,
395
            VirtualProxyInterface $proxy,
396
            $method,
397
            $params,
398
            & $initializer
399
        ) use (
400
            $initializerMatcher,
401
            $realInstance
402
        ) {
403
            $initializer   = null;
404
            $wrappedObject = $realInstance;
405
406
            $initializerMatcher($proxy, $wrappedObject, $method, $params);
407
        };
408
    }
409
410
    /**
411
     * Generates a list of object | invoked method | parameters | expected result
412
     *
413
     * @return array
414
     */
415
    public function getProxyMethods()
416
    {
417
        $selfHintParam = new ClassWithSelfHint();
418
419
        return [
420
            [
421
                BaseClass::class,
422
                new BaseClass(),
423
                'publicMethod',
424
                [],
425
                'publicMethodDefault'
426
            ],
427
            [
428
                BaseClass::class,
429
                new BaseClass(),
430
                'publicTypeHintedMethod',
431
                [new stdClass()],
432
                'publicTypeHintedMethodDefault'
433
            ],
434
            [
435
                BaseClass::class,
436
                new BaseClass(),
437
                'publicByReferenceMethod',
438
                [],
439
                'publicByReferenceMethodDefault'
440
            ],
441
            [
442
                BaseInterface::class,
443
                new BaseClass(),
444
                'publicMethod',
445
                [],
446
                'publicMethodDefault'
447
            ],
448
            [
449
                ClassWithSelfHint::class,
450
                new ClassWithSelfHint(),
451
                'selfHintMethod',
452
                ['parameter' => $selfHintParam],
453
                $selfHintParam
454
            ],
455
            [
456
                ClassWithMethodWithVariadicFunction::class,
457
                new ClassWithMethodWithVariadicFunction(),
458
                'buz',
459
                ['Ocramius', 'Malukenho'],
460
                ['Ocramius', 'Malukenho']
461
            ],
462
            [
463
                ClassWithMethodWithByRefVariadicFunction::class,
464
                new ClassWithMethodWithByRefVariadicFunction(),
465
                'tuz',
466
                ['Ocramius', 'Malukenho'],
467
                ['Ocramius', 'changed']
468
            ]
469
        ];
470
    }
471
472
    /**
473
     * Generates proxies and instances with a public property to feed to the property accessor methods
474
     *
475
     * @return array
476
     */
477
    public function getPropertyAccessProxies()
478
    {
479
        $instance1 = new BaseClass();
480
        $proxyName1 = $this->generateProxy(get_class($instance1));
481
        $instance2 = new BaseClass();
482
        $proxyName2 = $this->generateProxy(get_class($instance2));
483
484
        return [
485
            [
486
                $instance1,
487
                $proxyName1::staticProxyConstructor(
488
                    $this->createInitializer(BaseClass::class, $instance1)
489
                ),
490
                'publicProperty',
491
                'publicPropertyDefault',
492
            ],
493
            [
494
                $instance2,
495
                unserialize(serialize($proxyName2::staticProxyConstructor(
496
                    $this->createInitializer(BaseClass::class, $instance2)
497
                ))),
498
                'publicProperty',
499
                'publicPropertyDefault',
500
            ],
501
        ];
502
    }
503
}
504