Completed
Push — master ( c27607...e36047 )
by Marco
05:30
created

testCanCreateAndRegisterCallbackWithVariadicNotation()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 2 Features 0
Metric Value
dl 0
loc 33
rs 8.8571
c 3
b 2
f 0
cc 2
eloc 17
nc 2
nop 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A AccessInterceptorValueHolderFunctionalTest::testWillForwardVariadicArguments() 0 22 1
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_TestCase;
22
use ProxyManager\Factory\AccessInterceptorValueHolderFactory;
23
use ProxyManager\Generator\ClassGenerator;
24
use ProxyManager\Generator\Util\UniqueIdentifierGenerator;
25
use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy;
26
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolderGenerator;
27
use ProxyManagerTestAsset\BaseClass;
28
use ProxyManagerTestAsset\BaseInterface;
29
use ProxyManagerTestAsset\ClassWithCounterConstructor;
30
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod;
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 AccessInterceptorValueHolderFunctionalTest 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, $params, $expectedValue)
60
    {
61
        $proxyName = $this->generateProxy($className);
62
63
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */
64
        $proxy     = $proxyName::staticProxyConstructor($instance);
65
66
        $this->assertSame($instance, $proxy->getWrappedValueHolderValue());
67
        $this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
68
69
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
70
        $listener = $this->getMock(stdClass::class, ['__invoke']);
71
        $listener
72
            ->expects($this->once())
73
            ->method('__invoke')
74
            ->with($proxy, $instance, $method, $params, false);
75
76
        $proxy->setMethodPrefixInterceptor(
77
            $method,
78
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
79
                $listener($proxy, $instance, $method, $params, $returnEarly);
80
            }
81
        );
82
83
        $this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
84
85
        $random = uniqid();
86
87
        $proxy->setMethodPrefixInterceptor(
88
            $method,
89
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($random) {
90
                $returnEarly = true;
91
92
                return $random;
93
            }
94
        );
95
96
        $this->assertSame($random, call_user_func_array([$proxy, $method], $params));
97
    }
98
99
    /**
100
     * @dataProvider getProxyMethods
101
     *
102
     * @param string  $className
103
     * @param object  $instance
104
     * @param string  $method
105
     * @param mixed[] $params
106
     * @param mixed   $expectedValue
107
     */
108
    public function testMethodCallsWithSuffixListener($className, $instance, $method, $params, $expectedValue)
109
    {
110
        $proxyName = $this->generateProxy($className);
111
112
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */
113
        $proxy    = $proxyName::staticProxyConstructor($instance);
114
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
115
        $listener = $this->getMock(stdClass::class, ['__invoke']);
116
        $listener
117
            ->expects($this->once())
118
            ->method('__invoke')
119
            ->with($proxy, $instance, $method, $params, $expectedValue, false);
120
121
        $proxy->setMethodSuffixInterceptor(
122
            $method,
123
            function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($listener) {
124
                $listener($proxy, $instance, $method, $params, $returnValue, $returnEarly);
125
            }
126
        );
127
128
        $this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
129
130
        $random = uniqid();
131
132
        $proxy->setMethodSuffixInterceptor(
133
            $method,
134
            function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($random) {
135
                $returnEarly = true;
136
137
                return $random;
138
            }
139
        );
140
141
        $this->assertSame($random, call_user_func_array([$proxy, $method], $params));
142
    }
143
144
    /**
145
     * @dataProvider getProxyMethods
146
     *
147
     * @param string  $className
148
     * @param object  $instance
149
     * @param string  $method
150
     * @param mixed[] $params
151
     * @param mixed   $expectedValue
152
     */
153
    public function testMethodCallsAfterUnSerialization($className, $instance, $method, $params, $expectedValue)
154
    {
155
        $proxyName = $this->generateProxy($className);
156
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */
157
        $proxy     = unserialize(serialize($proxyName::staticProxyConstructor($instance)));
158
159
        $this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
160
        $this->assertEquals($instance, $proxy->getWrappedValueHolderValue());
161
    }
162
163
    /**
164
     * @dataProvider getProxyMethods
165
     *
166
     * @param string  $className
167
     * @param object  $instance
168
     * @param string  $method
169
     * @param mixed[] $params
170
     * @param mixed   $expectedValue
171
     */
172
    public function testMethodCallsAfterCloning($className, $instance, $method, $params, $expectedValue)
173
    {
174
        $proxyName = $this->generateProxy($className);
175
176
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */
177
        $proxy     = $proxyName::staticProxyConstructor($instance);
178
        $cloned    = clone $proxy;
179
180
        $this->assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue());
181
        $this->assertSame($expectedValue, call_user_func_array([$cloned, $method], $params));
182
        $this->assertEquals($instance, $cloned->getWrappedValueHolderValue());
183
    }
184
185
    /**
186
     * @dataProvider getPropertyAccessProxies
187
     *
188
     * @param object                                                                                  $instance
189
     * @param \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface $proxy
190
     * @param string                                                                                  $publicProperty
191
     * @param mixed                                                                                   $propertyValue
192
     */
193
    public function testPropertyReadAccess($instance, $proxy, $publicProperty, $propertyValue)
194
    {
195
        $this->assertSame($propertyValue, $proxy->$publicProperty);
196
        $this->assertEquals($instance, $proxy->getWrappedValueHolderValue());
197
    }
198
199
    /**
200
     * @dataProvider getPropertyAccessProxies
201
     *
202
     * @param object                                                                                  $instance
203
     * @param \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface $proxy
204
     * @param string                                                                                  $publicProperty
205
     */
206
    public function testPropertyWriteAccess($instance, $proxy, $publicProperty)
207
    {
208
        $newValue               = uniqid();
209
        $proxy->$publicProperty = $newValue;
210
211
        $this->assertSame($newValue, $proxy->$publicProperty);
212
        $this->assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty);
213
    }
214
215
    /**
216
     * @dataProvider getPropertyAccessProxies
217
     *
218
     * @param object                                                                                  $instance
219
     * @param \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface $proxy
220
     * @param string                                                                                  $publicProperty
221
     */
222
    public function testPropertyExistence($instance, $proxy, $publicProperty)
223
    {
224
        $this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
225
        $this->assertEquals($instance, $proxy->getWrappedValueHolderValue());
226
227
        $proxy->getWrappedValueHolderValue()->$publicProperty = null;
228
        $this->assertFalse(isset($proxy->$publicProperty));
229
    }
230
231
    /**
232
     * @dataProvider getPropertyAccessProxies
233
     *
234
     * @param object                                                                                  $instance
235
     * @param \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface $proxy
236
     * @param string                                                                                  $publicProperty
237
     */
238
    public function testPropertyUnset($instance, $proxy, $publicProperty)
239
    {
240
        $instance = $proxy->getWrappedValueHolderValue() ? $proxy->getWrappedValueHolderValue() : $instance;
241
        unset($proxy->$publicProperty);
242
243
        $this->assertFalse(isset($instance->$publicProperty));
244
        $this->assertFalse(isset($proxy->$publicProperty));
245
    }
246
247
    /**
248
     * Verifies that accessing a public property containing an array behaves like in a normal context
249
     */
250
    public function testCanWriteToArrayKeysInPublicProperty()
251
    {
252
        $instance    = new ClassWithPublicArrayProperty();
253
        $className   = get_class($instance);
254
        $proxyName   = $this->generateProxy($className);
255
        /* @var $proxy ClassWithPublicArrayProperty */
256
        $proxy       = $proxyName::staticProxyConstructor($instance);
257
258
        $proxy->arrayProperty['foo'] = 'bar';
259
260
        $this->assertSame('bar', $proxy->arrayProperty['foo']);
261
262
        $proxy->arrayProperty = ['tab' => 'taz'];
263
264
        $this->assertSame(['tab' => 'taz'], $proxy->arrayProperty);
265
    }
266
267
    /**
268
     * Verifies that public properties retrieved via `__get` don't get modified in the object state
269
     */
270
    public function testWillNotModifyRetrievedPublicProperties()
271
    {
272
        $instance    = new ClassWithPublicProperties();
273
        $className   = get_class($instance);
274
        $proxyName   = $this->generateProxy($className);
275
        /* @var $proxy ClassWithPublicProperties */
276
        $proxy       = $proxyName::staticProxyConstructor($instance);
277
        $variable    = $proxy->property0;
278
279
        $this->assertSame('property0', $variable);
280
281
        $variable = 'foo';
282
283
        $this->assertSame('property0', $proxy->property0);
284
        $this->assertSame('foo', $variable);
285
    }
286
287
    /**
288
     * Verifies that public properties references retrieved via `__get` modify in the object state
289
     */
290
    public function testWillModifyByRefRetrievedPublicProperties()
291
    {
292
        $instance    = new ClassWithPublicProperties();
293
        $className   = get_class($instance);
294
        $proxyName   = $this->generateProxy($className);
295
        /* @var $proxy ClassWithPublicProperties */
296
        $proxy       = $proxyName::staticProxyConstructor($instance);
297
        $variable    = & $proxy->property0;
298
299
        $this->assertSame('property0', $variable);
300
301
        $variable = 'foo';
302
303
        $this->assertSame('foo', $proxy->property0);
304
        $this->assertSame('foo', $variable);
305
    }
306
307
    /**
308
     * @group 115
309
     * @group 175
310
     */
311
    public function testWillBehaveLikeObjectWithNormalConstructor()
312
    {
313
        $instance = new ClassWithCounterConstructor(10);
314
315
        $this->assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
316
        $this->assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
317
        $instance->__construct(3);
318
        $this->assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
319
        $this->assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected');
320
321
        $proxyName = $this->generateProxy(get_class($instance));
322
323
        /* @var $proxy ClassWithCounterConstructor */
324
        $proxy = new $proxyName(15);
325
326
        $this->assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
327
        $this->assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
328
        $proxy->__construct(5);
329
        $this->assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
330
        $this->assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
331
    }
332
333
    public function testWillForwardVariadicArguments()
334
    {
335
        $factory       = new AccessInterceptorValueHolderFactory();
336
        $targetObject  = new ClassWithMethodWithVariadicFunction();
337
338
        /* @var $object ClassWithMethodWithVariadicFunction */
339
        $object = $factory->createProxy(
340
            $targetObject,
341
            [
342
                function () {
343
                    return 'Foo Baz';
344
                },
345
            ]
346
        );
347
348
        $this->assertNull($object->bar);
349
        $this->assertNull($object->baz);
350
351
        $object->foo('Ocramius', 'Malukenho', 'Danizord');
352
        $this->assertSame('Ocramius', $object->bar);
353
        $this->assertSame(['Malukenho', 'Danizord'], $object->baz);
354
    }
355
356
    /**
357
     * @group 265
358
     */
359
    public function testWillForwardVariadicByRefArguments()
360
    {
361
        $factory       = new AccessInterceptorValueHolderFactory();
362
        $targetObject  = new ClassWithMethodWithByRefVariadicFunction();
363
364
        /* @var $object ClassWithMethodWithByRefVariadicFunction */
365
        $object = $factory->createProxy(
366
            $targetObject,
367
            [
368
                function () {
369
                    return 'Foo Baz';
370
                },
371
            ]
372
        );
373
374
        $arguments = ['Ocramius', 'Malukenho', 'Danizord'];
375
376
        self::assertSame(
377
            ['Ocramius', 'changed', 'Danizord'],
378
            (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$arguments),
379
            'Verifying that the implementation of the test asset is correct before proceeding'
380
        );
381
        self::assertSame(['Ocramius', 'changed', 'Danizord'], $object->tuz(...$arguments));
382
        self::assertSame(['Ocramius', 'changed', 'Danizord'], $arguments, 'By-ref arguments were changed');
383
    }
384
385
    /**
386
     * This test documents a known limitation: `func_get_args()` (and similars) don't work in proxied APIs.
387
     * If you manage to make this test pass, then please do send a patch
388
     *
389
     * @group 265
390
     */
391
    public function testWillNotForwardDynamicArguments()
392
    {
393
        $proxyName = $this->generateProxy(ClassWithDynamicArgumentsMethod::class);
394
395
        /* @var $object ClassWithDynamicArgumentsMethod */
396
        $object = $proxyName::staticProxyConstructor(new ClassWithDynamicArgumentsMethod());
397
398
        self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b'));
399
400
        $this->setExpectedException(\PHPUnit_Framework_ExpectationFailedException::class);
401
402
        self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b'));
403
    }
404
405
    /**
406
     * Generates a proxy for the given class name, and retrieves its class name
407
     *
408
     * @param string $parentClassName
409
     *
410
     * @return string
411
     */
412
    private function generateProxy($parentClassName)
413
    {
414
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
415
        $generator          = new AccessInterceptorValueHolderGenerator();
416
        $generatedClass     = new ClassGenerator($generatedClassName);
417
        $strategy           = new EvaluatingGeneratorStrategy();
418
419
        $generator->generate(new ReflectionClass($parentClassName), $generatedClass);
420
        $strategy->generate($generatedClass);
421
422
        return $generatedClassName;
423
    }
424
425
    /**
426
     * Generates a list of object | invoked method | parameters | expected result
427
     *
428
     * @return array
429
     */
430
    public function getProxyMethods()
431
    {
432
        $selfHintParam = new ClassWithSelfHint();
433
434
        return [
435
            [
436
                BaseClass::class,
437
                new BaseClass(),
438
                'publicMethod',
439
                [],
440
                'publicMethodDefault'
441
            ],
442
            [
443
                BaseClass::class,
444
                new BaseClass(),
445
                'publicTypeHintedMethod',
446
                ['param' => new stdClass()],
447
                'publicTypeHintedMethodDefault'
448
            ],
449
            [
450
                BaseClass::class,
451
                new BaseClass(),
452
                'publicByReferenceMethod',
453
                [],
454
                'publicByReferenceMethodDefault'
455
            ],
456
            [
457
                BaseInterface::class,
458
                new BaseClass(),
459
                'publicMethod',
460
                [],
461
                'publicMethodDefault'
462
            ],
463
            [
464
                ClassWithSelfHint::class,
465
                new ClassWithSelfHint(),
466
                'selfHintMethod',
467
                ['parameter' => $selfHintParam],
468
                $selfHintParam
469
            ],
470
        ];
471
    }
472
473
    /**
474
     * Generates proxies and instances with a public property to feed to the property accessor methods
475
     *
476
     * @return array
477
     */
478
    public function getPropertyAccessProxies()
479
    {
480
        $instance1  = new BaseClass();
481
        $proxyName1 = $this->generateProxy(get_class($instance1));
482
        $instance2  = new BaseClass();
483
        $proxyName2 = $this->generateProxy(get_class($instance2));
484
485
        return [
486
            [
487
                $instance1,
488
                $proxyName1::staticProxyConstructor($instance1),
489
                'publicProperty',
490
                'publicPropertyDefault',
491
            ],
492
            [
493
                $instance2,
494
                unserialize(serialize($proxyName2::staticProxyConstructor($instance2))),
495
                'publicProperty',
496
                'publicPropertyDefault',
497
            ],
498
        ];
499
    }
500
}
501