Completed
Pull Request — master (#540)
by
unknown
21:29
created

getPropertyAccessProxies()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ProxyManagerTest\Functional;
6
7
use Generator;
8
use PHPUnit\Framework\MockObject\MockObject;
9
use PHPUnit\Framework\TestCase;
10
use ProxyManager\Factory\RemoteObject\Adapter\JsonRpc as JsonRpcAdapter;
11
use ProxyManager\Factory\RemoteObject\Adapter\XmlRpc as XmlRpcAdapter;
12
use ProxyManager\Factory\RemoteObject\AdapterInterface;
13
use ProxyManager\Factory\RemoteObjectFactory;
14
use ProxyManagerTestAsset\ClassWithSelfHint;
15
use ProxyManagerTestAsset\OtherObjectAccessClass;
16
use ProxyManagerTestAsset\RemoteProxy\BazServiceInterface;
17
use ProxyManagerTestAsset\RemoteProxy\Foo;
18
use ProxyManagerTestAsset\RemoteProxy\FooServiceInterface;
19
use ProxyManagerTestAsset\RemoteProxy\RemoteServiceWithDefaultsInterface;
20
use ProxyManagerTestAsset\RemoteProxy\VariadicArgumentsServiceInterface;
21
use ProxyManagerTestAsset\VoidCounter;
22
use ReflectionClass;
23
use Zend\Server\Client;
24
25
use function get_class;
26
use function random_int;
27
use function ucfirst;
28
use function uniqid;
29
30
/**
31
 * Tests for {@see \ProxyManager\ProxyGenerator\RemoteObjectGenerator} produced objects
32
 *
33
 * @group Functional
34
 * @coversNothing
35
 */
36
final class RemoteObjectFunctionalTest extends TestCase
37
{
38
    /**
39
     * @param mixed $expectedValue
40
     * @param string $method
41
     * @param mixed[] $params
42
     * @return XmlRpcAdapter
43
     */
44
    protected function getXmlRpcAdapter($expectedValue, string $method, array $params): XmlRpcAdapter
45
    {
46
        /** @var Client|MockObject $client */
47
        $client = $this->getMockBuilder(Client::class)->getMock();
48
49
        $client
50
            ->method('call')
51
            ->with(self::stringEndsWith($method), $params)
52
            ->willReturn($expectedValue);
53
54
        return new XmlRpcAdapter(
55
            $client,
56
            ['ProxyManagerTestAsset\RemoteProxy\Foo.foo' => 'ProxyManagerTestAsset\RemoteProxy\FooServiceInterface.foo']
57
        );
58
    }
59
60
    /**
61
     * @param mixed $expectedValue
62
     * @param string $method
63
     * @param mixed[] $params
64
     * @return JsonRpcAdapter
65
     */
66
    protected function getJsonRpcAdapter($expectedValue, string $method, array $params): JsonRpcAdapter
67
    {
68
        /** @var Client|MockObject $client */
69
        $client = $this->getMockBuilder(Client::class)->getMock();
70
71
        $client
72
            ->method('call')
73
            ->with(self::stringEndsWith($method), $params)
74
            ->willReturn($expectedValue);
75
76
        return new JsonRpcAdapter(
77
            $client,
78
            ['ProxyManagerTestAsset\RemoteProxy\Foo.foo' => 'ProxyManagerTestAsset\RemoteProxy\FooServiceInterface.foo']
79
        );
80
    }
81
82
    /**
83
     * @param string|object $instanceOrClassName
84
     * @param string $method
85
     * @param array $passedParams
86
     * @param mixed[] $parametersForProxy
87
     * @param mixed $expectedValue
88
     *
89
     * @dataProvider getProxyMethods
90
     *
91
     * @psalm-template OriginalClass of object
92
     * @psalm-param class-string<OriginalClass>|OriginalClass $instanceOrClassName
93
     */
94
    public function testXmlRpcMethodCalls(
95
        $instanceOrClassName,
96
        string $method,
97
        array $passedParams,
98
        array $parametersForProxy,
99
        $expectedValue
100
    ): void {
101
        $proxy = (new RemoteObjectFactory($this->getXmlRpcAdapter($expectedValue, $method, $parametersForProxy)))
102
            ->createProxy($instanceOrClassName);
103
104
        $callback = [$proxy, $method];
105
106
        self::assertIsCallable($callback);
107
        self::assertSame($expectedValue, $callback(...$passedParams));
108
    }
109
110
    /**
111
     * @param string|object $instanceOrClassName
112
     * @param string $method
113
     * @param array $passedParams
114
     * @param mixed[] $parametersForProxy
115
     * @param mixed $expectedValue
116
     *
117
     * @dataProvider getProxyMethods
118
     *
119
     * @psalm-template OriginalClass of object
120
     * @psalm-param class-string<OriginalClass>|OriginalClass $instanceOrClassName
121
     */
122
    public function testJsonRpcMethodCalls(
123
        $instanceOrClassName,
124
        string $method,
125
        array $passedParams,
126
        array $parametersForProxy,
127
        $expectedValue
128
    ): void {
129
        $proxy = (new RemoteObjectFactory($this->getJsonRpcAdapter($expectedValue, $method, $parametersForProxy)))
130
            ->createProxy($instanceOrClassName);
131
132
        $callback = [$proxy, $method];
133
134
        self::assertIsCallable($callback);
135
        self::assertSame($expectedValue, $callback(...$passedParams));
136
    }
137
138
    /**
139
     * @param string|object $instanceOrClassName
140
     * @param string $publicProperty
141
     * @param mixed $propertyValue
142
     *
143
     * @dataProvider getPropertyAccessProxies
144
     *
145
     * @psalm-template OriginalClass of object
146
     * @psalm-param class-string<OriginalClass>|OriginalClass $instanceOrClassName
147
     */
148
    public function testJsonRpcPropertyReadAccess($instanceOrClassName, string $publicProperty, $propertyValue): void
149
    {
150
        $proxy = (new RemoteObjectFactory($this->getJsonRpcAdapter($propertyValue, '__get', [$publicProperty])))
151
            ->createProxy($instanceOrClassName);
152
153
        self::assertSame($propertyValue, $proxy->{$publicProperty});
154
    }
155
156
    /**
157
     * Generates a list of object | invoked method | parameters | expected result
158
     *
159
     * @return string[][]|bool[][]|object[][]|mixed[][][]
160
     */
161
    public function getProxyMethods(): array
162
    {
163
        $selfHintParam = new ClassWithSelfHint();
164
165
        return [
166
            [
167
                FooServiceInterface::class,
168
                'foo',
169
                [],
170
                [],
171
                'bar remote',
172
            ],
173
            [
174
                Foo::class,
175
                'foo',
176
                [],
177
                [],
178
                'bar remote',
179
            ],
180
            [
181
                new Foo(),
182
                'foo',
183
                [],
184
                [],
185
                'bar remote',
186
            ],
187
            [
188
                BazServiceInterface::class,
189
                'baz',
190
                ['baz'],
191
                ['baz'],
192
                'baz remote',
193
            ],
194
            [
195
                new ClassWithSelfHint(),
196
                'selfHintMethod',
197
                [$selfHintParam],
198
                [$selfHintParam],
199
                $selfHintParam,
200
            ],
201
            [
202
                VariadicArgumentsServiceInterface::class,
203
                'method',
204
                ['aaa', 1, 2, 3, 4, 5],
205
                ['aaa', 1, 2, 3, 4, 5],
206
                true,
207
            ],
208
            [
209
                RemoteServiceWithDefaultsInterface::class,
210
                'optionalNonNullable',
211
                ['aaa'],
212
                ['aaa', ''],
213
                200
214
            ],
215
            [
216
                RemoteServiceWithDefaultsInterface::class,
217
                'optionalNullable',
218
                ['aaa'],
219
                ['aaa', null],
220
                200
221
            ]
222
        ];
223
    }
224
225
    /**
226
     * Generates proxies and instances with a public property to feed to the property accessor methods
227
     *
228
     * @return string[][]
229
     */
230
    public function getPropertyAccessProxies(): array
231
    {
232
        return [
233
            [
234
                FooServiceInterface::class,
235
                'publicProperty',
236
                'publicProperty remote',
237
            ],
238
        ];
239
    }
240
241
    /**
242
     * @group        276
243
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
244
     *
245
     * @param object $callerObject
246
     * @param object $realInstance
247
     * @param string $method
248
     * @param string $expectedValue
249
     * @param string $propertyName
250
     */
251
    public function testWillInterceptAccessToPropertiesViaFriendClassAccess(
252
        object $callerObject,
253
        object $realInstance,
254
        string $method,
255
        string $expectedValue,
256
        string $propertyName
257
    ): void {
258
        $adapter = $this->createMock(AdapterInterface::class);
259
260
        $adapter
261
            ->expects(self::once())
262
            ->method('call')
263
            ->with(get_class($realInstance), '__get', [$propertyName])
264
            ->willReturn($expectedValue);
265
266
        $proxy = (new RemoteObjectFactory($adapter))
267
            ->createProxy($realInstance);
268
269
        /** @var callable $accessor */
270
        $accessor = [$callerObject, $method];
271
272
        self::assertSame($expectedValue, $accessor($proxy));
273
    }
274
275
    /**
276
     * @group        276
277
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
278
     *
279
     * @param object $callerObject
280
     * @param object $realInstance
281
     * @param string $method
282
     * @param string $expectedValue
283
     * @param string $propertyName
284
     */
285
    public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfCloned(
286
        object $callerObject,
287
        object $realInstance,
288
        string $method,
289
        string $expectedValue,
290
        string $propertyName
291
    ): void {
292
        $adapter = $this->createMock(AdapterInterface::class);
293
294
        $adapter
295
            ->expects(self::once())
296
            ->method('call')
297
            ->with(get_class($realInstance), '__get', [$propertyName])
298
            ->willReturn($expectedValue);
299
300
        $proxy = clone (new RemoteObjectFactory($adapter))
301
            ->createProxy($realInstance);
302
303
        /** @var callable $accessor */
304
        $accessor = [$callerObject, $method];
305
306
        self::assertSame($expectedValue, $accessor($proxy));
307
    }
308
309
    /**
310
     * @group 327
311
     */
312
    public function testWillExecuteLogicInAVoidMethod(): void
313
    {
314
        $adapter = $this->createMock(AdapterInterface::class);
315
316
        $increment = random_int(10, 1000);
317
318
        $adapter
319
            ->expects(self::once())
320
            ->method('call')
321
            ->with(VoidCounter::class, 'increment', [$increment])
322
            ->willReturn(random_int(10, 1000));
323
324
        $proxy = clone (new RemoteObjectFactory($adapter))
325
            ->createProxy(VoidCounter::class);
326
327
        $proxy->increment($increment);
328
    }
329
330
    /**
331
     * @return Generator
332
     */
333
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope(): Generator
334
    {
335
        foreach ((new ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
336
            $property->setAccessible(true);
337
338
            $propertyName = $property->getName();
339
            $realInstance = new OtherObjectAccessClass();
340
            $expectedValue = uniqid('', true);
341
342
            $property->setValue($realInstance, $expectedValue);
343
344
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
345
                new OtherObjectAccessClass(),
346
                $realInstance,
347
                'get' . ucfirst($propertyName),
348
                $expectedValue,
349
                $propertyName,
350
            ];
351
        }
352
    }
353
}
354