Completed
Pull Request — master (#277)
by Marco
04:04 queued 17s
created

testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfDeSerialized()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 31
rs 8.8571
cc 1
eloc 19
nc 1
nop 5
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\Proxy\AccessInterceptorInterface;
27
use ProxyManager\ProxyGenerator\AccessInterceptorValueHolderGenerator;
28
use ProxyManagerTestAsset\BaseClass;
29
use ProxyManagerTestAsset\BaseInterface;
30
use ProxyManagerTestAsset\ClassWithCounterConstructor;
31
use ProxyManagerTestAsset\ClassWithDynamicArgumentsMethod;
32
use ProxyManagerTestAsset\ClassWithMethodWithByRefVariadicFunction;
33
use ProxyManagerTestAsset\ClassWithMethodWithVariadicFunction;
34
use ProxyManagerTestAsset\ClassWithPublicArrayProperty;
35
use ProxyManagerTestAsset\ClassWithPublicProperties;
36
use ProxyManagerTestAsset\ClassWithSelfHint;
37
use ProxyManagerTestAsset\OtherObjectAccessClass;
38
use ReflectionClass;
39
use stdClass;
40
41
/**
42
 * Tests for {@see \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator} produced objects
43
 *
44
 * @author Marco Pivetta <[email protected]>
45
 * @license MIT
46
 *
47
 * @group Functional
48
 * @coversNothing
49
 */
50
class AccessInterceptorValueHolderFunctionalTest extends PHPUnit_Framework_TestCase
51
{
52
    /**
53
     * @dataProvider getProxyMethods
54
     *
55
     * @param string  $className
56
     * @param object  $instance
57
     * @param string  $method
58
     * @param mixed[] $params
59
     * @param mixed   $expectedValue
60
     */
61
    public function testMethodCalls($className, $instance, $method, $params, $expectedValue)
62
    {
63
        $proxyName = $this->generateProxy($className);
64
65
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */
66
        $proxy     = $proxyName::staticProxyConstructor($instance);
67
68
        $this->assertSame($instance, $proxy->getWrappedValueHolderValue());
69
        $this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
70
71
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
72
        $listener = $this->getMock(stdClass::class, ['__invoke']);
73
        $listener
74
            ->expects($this->once())
75
            ->method('__invoke')
76
            ->with($proxy, $instance, $method, $params, false);
77
78
        $proxy->setMethodPrefixInterceptor(
0 ignored issues
show
Bug introduced by
The method setMethodPrefixInterceptor does only exist in ProxyManager\Proxy\AccessInterceptorInterface, but not in ProxyManager\Proxy\ValueHolderInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
79
            $method,
80
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
81
                $listener($proxy, $instance, $method, $params, $returnEarly);
82
            }
83
        );
84
85
        $this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
86
87
        $random = uniqid();
88
89
        $proxy->setMethodPrefixInterceptor(
0 ignored issues
show
Bug introduced by
The method setMethodPrefixInterceptor does only exist in ProxyManager\Proxy\AccessInterceptorInterface, but not in ProxyManager\Proxy\ValueHolderInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
90
            $method,
91
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($random) {
92
                $returnEarly = true;
93
94
                return $random;
95
            }
96
        );
97
98
        $this->assertSame($random, call_user_func_array([$proxy, $method], $params));
99
    }
100
101
    /**
102
     * @dataProvider getProxyMethods
103
     *
104
     * @param string  $className
105
     * @param object  $instance
106
     * @param string  $method
107
     * @param mixed[] $params
108
     * @param mixed   $expectedValue
109
     */
110
    public function testMethodCallsWithSuffixListener($className, $instance, $method, $params, $expectedValue)
111
    {
112
        $proxyName = $this->generateProxy($className);
113
114
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */
115
        $proxy    = $proxyName::staticProxyConstructor($instance);
116
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
117
        $listener = $this->getMock(stdClass::class, ['__invoke']);
118
        $listener
119
            ->expects($this->once())
120
            ->method('__invoke')
121
            ->with($proxy, $instance, $method, $params, $expectedValue, false);
122
123
        $proxy->setMethodSuffixInterceptor(
0 ignored issues
show
Bug introduced by
The method setMethodSuffixInterceptor does only exist in ProxyManager\Proxy\AccessInterceptorInterface, but not in ProxyManager\Proxy\ValueHolderInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
124
            $method,
125
            function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($listener) {
126
                $listener($proxy, $instance, $method, $params, $returnValue, $returnEarly);
127
            }
128
        );
129
130
        $this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
131
132
        $random = uniqid();
133
134
        $proxy->setMethodSuffixInterceptor(
0 ignored issues
show
Bug introduced by
The method setMethodSuffixInterceptor does only exist in ProxyManager\Proxy\AccessInterceptorInterface, but not in ProxyManager\Proxy\ValueHolderInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
135
            $method,
136
            function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($random) {
137
                $returnEarly = true;
138
139
                return $random;
140
            }
141
        );
142
143
        $this->assertSame($random, call_user_func_array([$proxy, $method], $params));
144
    }
145
146
    /**
147
     * @dataProvider getProxyMethods
148
     *
149
     * @param string  $className
150
     * @param object  $instance
151
     * @param string  $method
152
     * @param mixed[] $params
153
     * @param mixed   $expectedValue
154
     */
155
    public function testMethodCallsAfterUnSerialization($className, $instance, $method, $params, $expectedValue)
156
    {
157
        $proxyName = $this->generateProxy($className);
158
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */
159
        $proxy     = unserialize(serialize($proxyName::staticProxyConstructor($instance)));
160
161
        $this->assertSame($expectedValue, call_user_func_array([$proxy, $method], $params));
162
        $this->assertEquals($instance, $proxy->getWrappedValueHolderValue());
0 ignored issues
show
Bug introduced by
The method getWrappedValueHolderValue does only exist in ProxyManager\Proxy\ValueHolderInterface, but not in ProxyManager\Proxy\AccessInterceptorInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
163
    }
164
165
    /**
166
     * @dataProvider getProxyMethods
167
     *
168
     * @param string  $className
169
     * @param object  $instance
170
     * @param string  $method
171
     * @param mixed[] $params
172
     * @param mixed   $expectedValue
173
     */
174
    public function testMethodCallsAfterCloning($className, $instance, $method, $params, $expectedValue)
175
    {
176
        $proxyName = $this->generateProxy($className);
177
178
        /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */
179
        $proxy     = $proxyName::staticProxyConstructor($instance);
180
        $cloned    = clone $proxy;
181
182
        $this->assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue());
183
        $this->assertSame($expectedValue, call_user_func_array([$cloned, $method], $params));
184
        $this->assertEquals($instance, $cloned->getWrappedValueHolderValue());
185
    }
186
187
    /**
188
     * @dataProvider getPropertyAccessProxies
189
     *
190
     * @param object                                                                                  $instance
191
     * @param \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface $proxy
192
     * @param string                                                                                  $publicProperty
193
     * @param mixed                                                                                   $propertyValue
194
     */
195
    public function testPropertyReadAccess($instance, $proxy, $publicProperty, $propertyValue)
196
    {
197
        $this->assertSame($propertyValue, $proxy->$publicProperty);
198
        $this->assertEquals($instance, $proxy->getWrappedValueHolderValue());
0 ignored issues
show
Bug introduced by
The method getWrappedValueHolderValue does only exist in ProxyManager\Proxy\ValueHolderInterface, but not in ProxyManager\Proxy\AccessInterceptorInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
199
    }
200
201
    /**
202
     * @dataProvider getPropertyAccessProxies
203
     *
204
     * @param object                                                                                  $instance
205
     * @param \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface $proxy
206
     * @param string                                                                                  $publicProperty
207
     */
208
    public function testPropertyWriteAccess($instance, $proxy, $publicProperty)
209
    {
210
        $newValue               = uniqid();
211
        $proxy->$publicProperty = $newValue;
212
213
        $this->assertSame($newValue, $proxy->$publicProperty);
214
        $this->assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty);
215
    }
216
217
    /**
218
     * @dataProvider getPropertyAccessProxies
219
     *
220
     * @param object                                                                                  $instance
221
     * @param \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface $proxy
222
     * @param string                                                                                  $publicProperty
223
     */
224
    public function testPropertyExistence($instance, $proxy, $publicProperty)
225
    {
226
        $this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty));
227
        $this->assertEquals($instance, $proxy->getWrappedValueHolderValue());
0 ignored issues
show
Bug introduced by
The method getWrappedValueHolderValue does only exist in ProxyManager\Proxy\ValueHolderInterface, but not in ProxyManager\Proxy\AccessInterceptorInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
228
229
        $proxy->getWrappedValueHolderValue()->$publicProperty = null;
230
        $this->assertFalse(isset($proxy->$publicProperty));
231
    }
232
233
    /**
234
     * @dataProvider getPropertyAccessProxies
235
     *
236
     * @param object                                                                                  $instance
237
     * @param \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface $proxy
238
     * @param string                                                                                  $publicProperty
239
     */
240
    public function testPropertyUnset($instance, $proxy, $publicProperty)
241
    {
242
        $instance = $proxy->getWrappedValueHolderValue() ? $proxy->getWrappedValueHolderValue() : $instance;
243
        unset($proxy->$publicProperty);
244
245
        $this->assertFalse(isset($instance->$publicProperty));
246
        $this->assertFalse(isset($proxy->$publicProperty));
247
    }
248
249
    /**
250
     * Verifies that accessing a public property containing an array behaves like in a normal context
251
     */
252
    public function testCanWriteToArrayKeysInPublicProperty()
253
    {
254
        $instance    = new ClassWithPublicArrayProperty();
255
        $className   = get_class($instance);
256
        $proxyName   = $this->generateProxy($className);
257
        /* @var $proxy ClassWithPublicArrayProperty */
258
        $proxy       = $proxyName::staticProxyConstructor($instance);
259
260
        $proxy->arrayProperty['foo'] = 'bar';
261
262
        $this->assertSame('bar', $proxy->arrayProperty['foo']);
263
264
        $proxy->arrayProperty = ['tab' => 'taz'];
265
266
        $this->assertSame(['tab' => 'taz'], $proxy->arrayProperty);
267
    }
268
269
    /**
270
     * Verifies that public properties retrieved via `__get` don't get modified in the object state
271
     */
272
    public function testWillNotModifyRetrievedPublicProperties()
273
    {
274
        $instance    = new ClassWithPublicProperties();
275
        $className   = get_class($instance);
276
        $proxyName   = $this->generateProxy($className);
277
        /* @var $proxy ClassWithPublicProperties */
278
        $proxy       = $proxyName::staticProxyConstructor($instance);
279
        $variable    = $proxy->property0;
280
281
        $this->assertSame('property0', $variable);
282
283
        $variable = 'foo';
284
285
        $this->assertSame('property0', $proxy->property0);
286
        $this->assertSame('foo', $variable);
287
    }
288
289
    /**
290
     * Verifies that public properties references retrieved via `__get` modify in the object state
291
     */
292
    public function testWillModifyByRefRetrievedPublicProperties()
293
    {
294
        $instance    = new ClassWithPublicProperties();
295
        $className   = get_class($instance);
296
        $proxyName   = $this->generateProxy($className);
297
        /* @var $proxy ClassWithPublicProperties */
298
        $proxy       = $proxyName::staticProxyConstructor($instance);
299
        $variable    = & $proxy->property0;
300
301
        $this->assertSame('property0', $variable);
302
303
        $variable = 'foo';
304
305
        $this->assertSame('foo', $proxy->property0);
306
        $this->assertSame('foo', $variable);
307
    }
308
309
    /**
310
     * @group 115
311
     * @group 175
312
     */
313
    public function testWillBehaveLikeObjectWithNormalConstructor()
314
    {
315
        $instance = new ClassWithCounterConstructor(10);
316
317
        $this->assertSame(10, $instance->amount, 'Verifying that test asset works as expected');
318
        $this->assertSame(10, $instance->getAmount(), 'Verifying that test asset works as expected');
319
        $instance->__construct(3);
320
        $this->assertSame(13, $instance->amount, 'Verifying that test asset works as expected');
321
        $this->assertSame(13, $instance->getAmount(), 'Verifying that test asset works as expected');
322
323
        $proxyName = $this->generateProxy(get_class($instance));
324
325
        /* @var $proxy ClassWithCounterConstructor */
326
        $proxy = new $proxyName(15);
327
328
        $this->assertSame(15, $proxy->amount, 'Verifying that the proxy constructor works as expected');
329
        $this->assertSame(15, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
330
        $proxy->__construct(5);
331
        $this->assertSame(20, $proxy->amount, 'Verifying that the proxy constructor works as expected');
332
        $this->assertSame(20, $proxy->getAmount(), 'Verifying that the proxy constructor works as expected');
333
    }
334
335
    public function testWillForwardVariadicArguments()
336
    {
337
        $factory       = new AccessInterceptorValueHolderFactory();
338
        $targetObject  = new ClassWithMethodWithVariadicFunction();
339
340
        /* @var $object ClassWithMethodWithVariadicFunction */
341
        $object = $factory->createProxy(
342
            $targetObject,
343
            [
344
                function () {
345
                    return 'Foo Baz';
346
                },
347
            ]
348
        );
349
350
        $this->assertNull($object->bar);
351
        $this->assertNull($object->baz);
352
353
        $object->foo('Ocramius', 'Malukenho', 'Danizord');
354
        $this->assertSame('Ocramius', $object->bar);
355
        $this->assertSame(['Malukenho', 'Danizord'], $object->baz);
356
    }
357
358
    /**
359
     * @group 265
360
     */
361
    public function testWillForwardVariadicByRefArguments()
362
    {
363
        $factory       = new AccessInterceptorValueHolderFactory();
364
        $targetObject  = new ClassWithMethodWithByRefVariadicFunction();
365
366
        /* @var $object ClassWithMethodWithByRefVariadicFunction */
367
        $object = $factory->createProxy(
368
            $targetObject,
369
            [
370
                function () {
371
                    return 'Foo Baz';
372
                },
373
            ]
374
        );
375
376
        $arguments = ['Ocramius', 'Malukenho', 'Danizord'];
377
378
        self::assertSame(
379
            ['Ocramius', 'changed', 'Danizord'],
380
            (new ClassWithMethodWithByRefVariadicFunction())->tuz(...$arguments),
381
            'Verifying that the implementation of the test asset is correct before proceeding'
382
        );
383
        self::assertSame(['Ocramius', 'changed', 'Danizord'], $object->tuz(...$arguments));
384
        self::assertSame(['Ocramius', 'changed', 'Danizord'], $arguments, 'By-ref arguments were changed');
385
    }
386
387
    /**
388
     * This test documents a known limitation: `func_get_args()` (and similars) don't work in proxied APIs.
389
     * If you manage to make this test pass, then please do send a patch
390
     *
391
     * @group 265
392
     */
393
    public function testWillNotForwardDynamicArguments()
394
    {
395
        $proxyName = $this->generateProxy(ClassWithDynamicArgumentsMethod::class);
396
397
        /* @var $object ClassWithDynamicArgumentsMethod */
398
        $object = $proxyName::staticProxyConstructor(new ClassWithDynamicArgumentsMethod());
399
400
        self::assertSame(['a', 'b'], (new ClassWithDynamicArgumentsMethod())->dynamicArgumentsMethod('a', 'b'));
401
402
        $this->setExpectedException(\PHPUnit_Framework_ExpectationFailedException::class);
403
404
        self::assertSame(['a', 'b'], $object->dynamicArgumentsMethod('a', 'b'));
405
    }
406
407
    /**
408
     * Generates a proxy for the given class name, and retrieves its class name
409
     *
410
     * @param string $parentClassName
411
     *
412
     * @return string
413
     */
414
    private function generateProxy($parentClassName)
415
    {
416
        $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo');
417
        $generator          = new AccessInterceptorValueHolderGenerator();
418
        $generatedClass     = new ClassGenerator($generatedClassName);
419
        $strategy           = new EvaluatingGeneratorStrategy();
420
421
        $generator->generate(new ReflectionClass($parentClassName), $generatedClass);
422
        $strategy->generate($generatedClass);
423
424
        return $generatedClassName;
425
    }
426
427
    /**
428
     * Generates a list of object | invoked method | parameters | expected result
429
     *
430
     * @return array
431
     */
432
    public function getProxyMethods()
433
    {
434
        $selfHintParam = new ClassWithSelfHint();
435
436
        return [
437
            [
438
                BaseClass::class,
439
                new BaseClass(),
440
                'publicMethod',
441
                [],
442
                'publicMethodDefault'
443
            ],
444
            [
445
                BaseClass::class,
446
                new BaseClass(),
447
                'publicTypeHintedMethod',
448
                ['param' => new stdClass()],
449
                'publicTypeHintedMethodDefault'
450
            ],
451
            [
452
                BaseClass::class,
453
                new BaseClass(),
454
                'publicByReferenceMethod',
455
                [],
456
                'publicByReferenceMethodDefault'
457
            ],
458
            [
459
                BaseInterface::class,
460
                new BaseClass(),
461
                'publicMethod',
462
                [],
463
                'publicMethodDefault'
464
            ],
465
            [
466
                ClassWithSelfHint::class,
467
                new ClassWithSelfHint(),
468
                'selfHintMethod',
469
                ['parameter' => $selfHintParam],
470
                $selfHintParam
471
            ],
472
        ];
473
    }
474
475
    /**
476
     * Generates proxies and instances with a public property to feed to the property accessor methods
477
     *
478
     * @return array
479
     */
480
    public function getPropertyAccessProxies()
481
    {
482
        $instance1  = new BaseClass();
483
        $proxyName1 = $this->generateProxy(get_class($instance1));
484
        $instance2  = new BaseClass();
485
        $proxyName2 = $this->generateProxy(get_class($instance2));
486
487
        return [
488
            [
489
                $instance1,
490
                $proxyName1::staticProxyConstructor($instance1),
491
                'publicProperty',
492
                'publicPropertyDefault',
493
            ],
494
            [
495
                $instance2,
496
                unserialize(serialize($proxyName2::staticProxyConstructor($instance2))),
497
                'publicProperty',
498
                'publicPropertyDefault',
499
            ],
500
        ];
501
    }
502
503
    /**
504
     * @group 276
505
     *
506
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
507
     *
508
     * @param object $callerObject
509
     * @param object $realInstance
510
     * @param string $method
511
     * @param string $expectedValue
512
     * @param string $propertyName
513
     */
514
    public function testWillInterceptAccessToPropertiesViaFriendClassAccess(
515
        $callerObject,
516
        $realInstance,
517
        string $method,
518
        string $expectedValue,
519
        string $propertyName
520
    ) {
521
        $proxyName = $this->generateProxy(get_class($realInstance));
522
        /* @var $proxy OtherObjectAccessClass|AccessInterceptorInterface */
523
        $proxy = $proxyName::staticProxyConstructor($realInstance);
524
525
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
526
        $listener = $this->getMock(\stdClass::class, ['__invoke']);
527
528
        $listener
529
            ->expects(self::once())
530
            ->method('__invoke')
531
            ->with($proxy, $realInstance, '__get', ['name' => $propertyName]);
532
533
        $proxy->setMethodPrefixInterceptor(
0 ignored issues
show
Bug introduced by
The method setMethodPrefixInterceptor does only exist in ProxyManager\Proxy\AccessInterceptorInterface, but not in ProxyManagerTestAsset\OtherObjectAccessClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
534
            '__get',
535
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
536
                $listener($proxy, $instance, $method, $params, $returnEarly);
537
            }
538
        );
539
540
        /* @var $accessor callable */
541
        $accessor = [$callerObject, $method];
542
543
        self::assertSame($expectedValue, $accessor($proxy));
544
    }
545
546
    /**
547
     * @group 276
548
     *
549
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
550
     *
551
     * @param object $callerObject
552
     * @param object $realInstance
553
     * @param string $method
554
     * @param string $expectedValue
555
     * @param string $propertyName
556
     */
557
    public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfDeSerialized(
558
        $callerObject,
559
        $realInstance,
560
        string $method,
561
        string $expectedValue,
562
        string $propertyName
563
    ) {
564
        $proxyName = $this->generateProxy(get_class($realInstance));
565
        /* @var $proxy OtherObjectAccessClass|AccessInterceptorInterface */
566
        $proxy = unserialize(serialize($proxyName::staticProxyConstructor($realInstance)));
567
568
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
569
        $listener = $this->getMock(\stdClass::class, ['__invoke']);
570
571
        $listener
572
            ->expects(self::once())
573
            ->method('__invoke')
574
            ->with($proxy, $realInstance, '__get', ['name' => $propertyName]);
575
576
        $proxy->setMethodPrefixInterceptor(
0 ignored issues
show
Bug introduced by
The method setMethodPrefixInterceptor does only exist in ProxyManager\Proxy\AccessInterceptorInterface, but not in ProxyManagerTestAsset\OtherObjectAccessClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
577
            '__get',
578
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
579
                $listener($proxy, $instance, $method, $params, $returnEarly);
580
            }
581
        );
582
583
        /* @var $accessor callable */
584
        $accessor = [$callerObject, $method];
585
586
        self::assertSame($expectedValue, $accessor($proxy));
587
    }
588
589
590
    /**
591
     * @group 276
592
     *
593
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
594
     *
595
     * @param object $callerObject
596
     * @param object $realInstance
597
     * @param string $method
598
     * @param string $expectedValue
599
     * @param string $propertyName
600
     */
601
    public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfCloned(
602
        $callerObject,
603
        $realInstance,
604
        string $method,
605
        string $expectedValue,
606
        string $propertyName
607
    ) {
608
        $proxyName = $this->generateProxy(get_class($realInstance));
609
        /* @var $proxy OtherObjectAccessClass|AccessInterceptorInterface */
610
        $proxy = clone $proxyName::staticProxyConstructor($realInstance);
611
612
        /* @var $listener callable|\PHPUnit_Framework_MockObject_MockObject */
613
        $listener = $this->getMock(\stdClass::class, ['__invoke']);
614
615
        $listener
616
            ->expects(self::once())
617
            ->method('__invoke')
618
            ->with($proxy, $realInstance, '__get', ['name' => $propertyName]);
619
620
        $proxy->setMethodPrefixInterceptor(
0 ignored issues
show
Bug introduced by
The method setMethodPrefixInterceptor does only exist in ProxyManager\Proxy\AccessInterceptorInterface, but not in ProxyManagerTestAsset\OtherObjectAccessClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
621
            '__get',
622
            function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) {
623
                $listener($proxy, $instance, $method, $params, $returnEarly);
624
            }
625
        );
626
627
        /* @var $accessor callable */
628
        $accessor = [$callerObject, $method];
629
630
        self::assertSame($expectedValue, $accessor($proxy));
631
    }
632
633
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : \Generator
634
    {
635
        $proxyClass = $this->generateProxy(OtherObjectAccessClass::class);
636
637
        foreach ((new \ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
638
            $property->setAccessible(true);
639
640
            $propertyName  = $property->getName();
641
            $realInstance  = new OtherObjectAccessClass();
642
            $expectedValue = uniqid('', true);
643
644
            $property->setValue($realInstance, $expectedValue);
645
646
            // callee is an actual object
647
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
648
                new OtherObjectAccessClass(),
649
                $realInstance,
650
                'get' . ucfirst($propertyName),
651
                $expectedValue,
652
                $propertyName,
653
            ];
654
655
            $realInstance  = new OtherObjectAccessClass();
656
            $expectedValue = uniqid('', true);
657
658
            $property->setValue($realInstance, $expectedValue);
659
660
            // callee is a proxy (not to be lazy-loaded!)
661
            yield '(proxy) ' . OtherObjectAccessClass::class . '#$' . $propertyName => [
662
                $proxyClass::staticProxyConstructor(new OtherObjectAccessClass()),
663
                $realInstance,
664
                'get' . ucfirst($propertyName),
665
                $expectedValue,
666
                $propertyName,
667
            ];
668
        }
669
    }
670
}
671