Completed
Pull Request — master (#467)
by Marco
23:32
created

RemoteObjectFunctionalTest::generateProxy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ProxyManagerTest\Functional;
6
7
use Generator;
8
use PHPUnit\Framework\TestCase;
9
use ProxyManager\Factory\RemoteObject\Adapter\JsonRpc as JsonRpcAdapter;
10
use ProxyManager\Factory\RemoteObject\Adapter\XmlRpc as XmlRpcAdapter;
11
use ProxyManager\Factory\RemoteObject\AdapterInterface;
12
use ProxyManager\Factory\RemoteObjectFactory;
13
use ProxyManagerTestAsset\ClassWithSelfHint;
14
use ProxyManagerTestAsset\OtherObjectAccessClass;
15
use ProxyManagerTestAsset\RemoteProxy\BazServiceInterface;
16
use ProxyManagerTestAsset\RemoteProxy\Foo;
17
use ProxyManagerTestAsset\RemoteProxy\FooServiceInterface;
18
use ProxyManagerTestAsset\RemoteProxy\VariadicArgumentsServiceInterface;
19
use ProxyManagerTestAsset\VoidCounter;
20
use ReflectionClass;
21
use Zend\Server\Client;
22
use function get_class;
23
use function random_int;
24
use function ucfirst;
25
use function uniqid;
26
27
/**
28
 * Tests for {@see \ProxyManager\ProxyGenerator\RemoteObjectGenerator} produced objects
29
 *
30
 * @group Functional
31
 * @coversNothing
32
 */
33
final class RemoteObjectFunctionalTest extends TestCase
34
{
35
    /**
36
     * @param mixed   $expectedValue
37
     * @param mixed[] $params
38
     */
39
    protected function getXmlRpcAdapter($expectedValue, string $method, array $params) : XmlRpcAdapter
40
    {
41
        $client = $this->getMockBuilder(Client::class)->setMethods(['call'])->getMock();
42
43
        $client
0 ignored issues
show
Bug introduced by
The method method() does not seem to exist on object<PHPUnit\Framework\MockObject\MockObject>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
44
            ->method('call')
45
            ->with(self::stringEndsWith($method), $params)
46
            ->willReturn($expectedValue);
47
48
        return new XmlRpcAdapter(
49
            $client,
50
            ['ProxyManagerTestAsset\RemoteProxy\Foo.foo' => 'ProxyManagerTestAsset\RemoteProxy\FooServiceInterface.foo']
51
        );
52
    }
53
54
    /**
55
     * @param mixed   $expectedValue
56
     * @param mixed[] $params
57
     */
58
    protected function getJsonRpcAdapter($expectedValue, string $method, array $params) : JsonRpcAdapter
59
    {
60
        $client = $this->getMockBuilder(Client::class)->setMethods(['call'])->getMock();
61
62
        $client
0 ignored issues
show
Bug introduced by
The method method() does not seem to exist on object<PHPUnit\Framework\MockObject\MockObject>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
63
            ->method('call')
64
            ->with(self::stringEndsWith($method), $params)
65
            ->willReturn($expectedValue);
66
67
        return new JsonRpcAdapter(
68
            $client,
69
            ['ProxyManagerTestAsset\RemoteProxy\Foo.foo' => 'ProxyManagerTestAsset\RemoteProxy\FooServiceInterface.foo']
70
        );
71
    }
72
73
    /**
74
     * @param string|object $instanceOrClassName
75
     * @param mixed[]       $params
76
     * @param mixed         $expectedValue
77
     *
78
     * @dataProvider getProxyMethods
79
     *
80
     * @psalm-template OriginalClass of object
81
     * @psalm-param class-string<OriginalClass>|OriginalClass $instanceOrClassName
82
     */
83
    public function testXmlRpcMethodCalls($instanceOrClassName, string $method, array $params, $expectedValue) : void
84
    {
85
        $proxy = (new RemoteObjectFactory($this->getXmlRpcAdapter($expectedValue, $method, $params)))
86
            ->createProxy($instanceOrClassName);
87
88
        $callback = [$proxy, $method];
89
90
        self::assertIsCallable($callback);
91
        self::assertSame($expectedValue, $callback(...$params));
92
    }
93
94
    /**
95
     * @param string|object $instanceOrClassName
96
     * @param mixed[]       $params
97
     * @param mixed         $expectedValue
98
     *
99
     * @dataProvider getProxyMethods
100
     *
101
     * @psalm-template OriginalClass of object
102
     * @psalm-param class-string<OriginalClass>|OriginalClass $instanceOrClassName
103
     */
104
    public function testJsonRpcMethodCalls($instanceOrClassName, string $method, array $params, $expectedValue) : void
105
    {
106
        $proxy = (new RemoteObjectFactory($this->getJsonRpcAdapter($expectedValue, $method, $params)))
107
            ->createProxy($instanceOrClassName);
108
109
        $callback = [$proxy, $method];
110
111
        self::assertIsCallable($callback);
112
        self::assertSame($expectedValue, $callback(...$params));
113
    }
114
115
    /**
116
     * @param string|object $instanceOrClassName
117
     * @param mixed         $propertyValue
118
     *
119
     * @dataProvider getPropertyAccessProxies
120
     *
121
     * @psalm-template OriginalClass of object
122
     * @psalm-param class-string<OriginalClass>|OriginalClass $instanceOrClassName
123
     */
124
    public function testJsonRpcPropertyReadAccess($instanceOrClassName, string $publicProperty, $propertyValue) : void
125
    {
126
        $proxy = (new RemoteObjectFactory($this->getJsonRpcAdapter($propertyValue, '__get', [$publicProperty])))
127
            ->createProxy($instanceOrClassName);
128
129
        self::assertSame($propertyValue, $proxy->$publicProperty);
130
    }
131
132
    /**
133
     * Generates a list of object | invoked method | parameters | expected result
134
     *
135
     * @return string[][]|bool[][]|object[][]|mixed[][][]
136
     */
137
    public function getProxyMethods() : array
138
    {
139
        $selfHintParam = new ClassWithSelfHint();
140
141
        return [
142
            [
143
                FooServiceInterface::class,
144
                'foo',
145
                [],
146
                'bar remote',
147
            ],
148
            [
149
                Foo::class,
150
                'foo',
151
                [],
152
                'bar remote',
153
            ],
154
            [
155
                new Foo(),
156
                'foo',
157
                [],
158
                'bar remote',
159
            ],
160
            [
161
                BazServiceInterface::class,
162
                'baz',
163
                ['baz'],
164
                'baz remote',
165
            ],
166
            [
167
                new ClassWithSelfHint(),
168
                'selfHintMethod',
169
                [$selfHintParam],
170
                $selfHintParam,
171
            ],
172
            [
173
                VariadicArgumentsServiceInterface::class,
174
                'method',
175
                ['aaa', 1, 2, 3, 4, 5],
176
                true,
177
            ],
178
        ];
179
    }
180
181
    /**
182
     * Generates proxies and instances with a public property to feed to the property accessor methods
183
     *
184
     * @return string[][]
185
     */
186
    public function getPropertyAccessProxies() : array
187
    {
188
        return [
189
            [
190
                FooServiceInterface::class,
191
                'publicProperty',
192
                'publicProperty remote',
193
            ],
194
        ];
195
    }
196
197
    /**
198
     * @group        276
199
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
200
     */
201
    public function testWillInterceptAccessToPropertiesViaFriendClassAccess(
202
        object $callerObject,
203
        object $realInstance,
204
        string $method,
205
        string $expectedValue,
206
        string $propertyName
207
    ) : void {
208
        $adapter = $this->createMock(AdapterInterface::class);
209
210
        $adapter
211
            ->expects(self::once())
212
            ->method('call')
213
            ->with(get_class($realInstance), '__get', [$propertyName])
214
            ->willReturn($expectedValue);
215
216
        $proxy = (new RemoteObjectFactory($adapter))
217
            ->createProxy($realInstance);
218
219
        /** @var callable $accessor */
220
        $accessor = [$callerObject, $method];
221
222
        self::assertSame($expectedValue, $accessor($proxy));
223
    }
224
225
    /**
226
     * @group        276
227
     * @dataProvider getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope
228
     */
229
    public function testWillInterceptAccessToPropertiesViaFriendClassAccessEvenIfCloned(
230
        object $callerObject,
231
        object $realInstance,
232
        string $method,
233
        string $expectedValue,
234
        string $propertyName
235
    ) : void {
236
        $adapter = $this->createMock(AdapterInterface::class);
237
238
        $adapter
239
            ->expects(self::once())
240
            ->method('call')
241
            ->with(get_class($realInstance), '__get', [$propertyName])
242
            ->willReturn($expectedValue);
243
244
        $proxy = clone (new RemoteObjectFactory($adapter))
245
            ->createProxy($realInstance);
246
247
        /** @var callable $accessor */
248
        $accessor = [$callerObject, $method];
249
250
        self::assertSame($expectedValue, $accessor($proxy));
251
    }
252
253
    /**
254
     * @group 327
255
     */
256
    public function testWillExecuteLogicInAVoidMethod() : void
257
    {
258
        $adapter = $this->createMock(AdapterInterface::class);
259
260
        $increment = random_int(10, 1000);
261
262
        $adapter
263
            ->expects(self::once())
264
            ->method('call')
265
            ->with(VoidCounter::class, 'increment', [$increment])
266
            ->willReturn(random_int(10, 1000));
267
268
        $proxy = clone (new RemoteObjectFactory($adapter))
269
            ->createProxy(VoidCounter::class);
270
271
        $proxy->increment($increment);
272
    }
273
274
    public function getMethodsThatAccessPropertiesOnOtherObjectsInTheSameScope() : Generator
275
    {
276
        foreach ((new ReflectionClass(OtherObjectAccessClass::class))->getProperties() as $property) {
277
            $property->setAccessible(true);
278
279
            $propertyName  = $property->getName();
280
            $realInstance  = new OtherObjectAccessClass();
281
            $expectedValue = uniqid('', true);
282
283
            $property->setValue($realInstance, $expectedValue);
284
285
            yield OtherObjectAccessClass::class . '#$' . $propertyName => [
286
                new OtherObjectAccessClass(),
287
                $realInstance,
288
                'get' . ucfirst($propertyName),
289
                $expectedValue,
290
                $propertyName,
291
            ];
292
        }
293
    }
294
}
295