Completed
Push — master ( 980b0e...23464b )
by Yo
02:00
created

JsonRpcHttpServerExtension::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 1
rs 9.6666
c 0
b 0
f 0
1
<?php
2
namespace Yoanm\SymfonyJsonRpcHttpServer\Infra\Symfony\DependencyInjection;
3
4
use Symfony\Component\Config\Definition\Processor; // <= Must stay optional !
5
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
6
use Symfony\Component\DependencyInjection\ContainerBuilder;
7
use Symfony\Component\DependencyInjection\Definition;
8
use Symfony\Component\DependencyInjection\Exception\LogicException;
9
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
10
use Symfony\Component\DependencyInjection\Reference;
11
use Yoanm\JsonRpcServer\App\Creator\CustomExceptionCreator;
12
use Yoanm\JsonRpcServer\App\Creator\ResponseCreator;
13
use Yoanm\JsonRpcServer\App\Manager\MethodManager;
14
use Yoanm\JsonRpcServer\App\RequestHandler;
15
use Yoanm\JsonRpcServer\App\Serialization\RequestDenormalizer;
16
use Yoanm\JsonRpcServer\App\Serialization\ResponseNormalizer;
17
use Yoanm\JsonRpcServer\Infra\Endpoint\JsonRpcEndpoint;
18
use Yoanm\JsonRpcServer\Infra\Serialization\RawRequestSerializer;
19
use Yoanm\JsonRpcServer\Infra\Serialization\RawResponseSerializer;
20
use Yoanm\JsonRpcServerPsr11Resolver\Infra\Resolver\ContainerMethodResolver;
21
use Yoanm\SymfonyJsonRpcHttpServer\Infra\Endpoint\JsonRpcHttpEndpoint;
22
use Yoanm\SymfonyJsonRpcHttpServer\Infra\Resolver\ServiceNameResolver;
23
24
/**
25
 * Class JsonRpcHttpServerExtension
26
 *
27
 * /!\ In case you use the default resolver (yoanm/jsonrpc-server-sdk-psr11-resolver),
28
 * your JSON-RPC method services must be public in order to retrieve it later from container
29
 */
30
class JsonRpcHttpServerExtension implements ExtensionInterface, CompilerPassInterface
31
{
32
    // Use this service to inject string request
33
    const ENDPOINT_SERVICE_NAME = 'json_rpc_http_server.endpoint';
34
35
    // Use this tag to inject your own resolver
36
    const METHOD_RESOLVER_TAG = 'json_rpc_http_server.method_resolver';
37
38
    // Use this tag to inject your JSON-RPC methods into the default method resolver
39
    const JSONRPC_METHOD_TAG = 'json_rpc_http_server.jsonrpc_method';
40
41
    // In case you want to add mapping for a method, use the following service
42
    const SERVICE_NAME_RESOLVER_SERVICE_NAME = 'json_rpc_http_server.resolver.service_name';
43
    // And add an attribute with following key
44
    const JSONRPC_METHOD_TAG_METHOD_NAME_KEY = 'method';
45
46
    // Extension identifier (used in configuration for instance)
47
    const EXTENSION_IDENTIFIER = 'json_rpc_http_server';
48
49
50
    /** Private constants */
51
    const CUSTOM_METHOD_RESOLVER_CONTAINER_PARAM = self::EXTENSION_IDENTIFIER.'.custom_method_resolver';
52
53
    /** @var bool */
54
    private $parseConfig = false;
55
56
    /**
57
     * @param bool|false $parseConfig If true, Config component is required
58
     */
59 11
    public function __construct(bool $parseConfig = false)
60
    {
61 11
        $this->parseConfig = $parseConfig;
62 11
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 10
    public function load(array $configs, ContainerBuilder $container)
68
    {
69 10
        $this->compileAndProcessConfigurations($configs, $container);
70
71
        // Use only references to avoid class instantiation
72
        // And don't use file configuration in order to not add Symfony\Component\Config as dependency
73 10
        $this->createPublicServiceDefinitions($container);
74 10
        $this->createInfraServiceDefinitions($container);
75 10
        $this->createAppServiceDefinitions($container);
76 10
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 11
    public function getNamespace()
82
    {
83 11
        return 'http://example.org/schema/dic/'.$this->getAlias();
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 1
    public function getXsdValidationBasePath()
90
    {
91 1
        return '';
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97 11
    public function getAlias()
98
    {
99 11
        return self::EXTENSION_IDENTIFIER;
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105 10
    public function process(ContainerBuilder $container)
106
    {
107 10
        $isContainerResolver = $this->aliasMethodResolver($container);
108 9
        if (true === $isContainerResolver) {
109 6
            $this->loadJsonRpcMethodsFromTag($container);
110
        }
111 7
    }
112
113
    /**
114
     * @param ContainerBuilder $container
115
     */
116 10
    protected function createAppServiceDefinitions(ContainerBuilder $container)
117
    {
118
        // RequestDenormalizer
119 10
        $container->setDefinition(
120 10
            'json_rpc_http_server.sdk.app.serialization.request_denormalizer',
121 10
            new Definition(RequestDenormalizer::class)
122
        );
123
        // ResponseNormalizer
124 10
        $container->setDefinition(
125 10
            'json_rpc_http_server.sdk.app.serialization.response_normalizer',
126 10
            new Definition(ResponseNormalizer::class)
127
        );
128
        // ResponseCreator
129 10
        $container->setDefinition(
130 10
            'json_rpc_http_server.sdk.app.creator.response',
131 10
            new Definition(ResponseCreator::class)
132
        );
133
        // CustomExceptionCreator
134 10
        $container->setDefinition(
135 10
            'json_rpc_http_server.sdk.app.creator.custom_exception',
136 10
            new Definition(CustomExceptionCreator::class)
137
        );
138
139
        // MethodManager
140 10
        $container->setDefinition(
141 10
            'json_rpc_http_server.sdk.app.manager.method',
142 10
            new Definition(
143 10
                MethodManager::class,
144
                [
145 10
                    new Reference('json_rpc_http_server.infra.resolver.method'),
146 10
                    new Reference('json_rpc_http_server.sdk.app.creator.custom_exception')
147
                ]
148
            )
149
        );
150
        // RequestHandler
151 10
        $container->setDefinition(
152 10
            'json_rpc_http_server.sdk.app.handler.request',
153 10
            new Definition(
154 10
                RequestHandler::class,
155
                [
156 10
                    new Reference('json_rpc_http_server.sdk.app.manager.method'),
157 10
                    new Reference('json_rpc_http_server.sdk.app.creator.response')
158
                ]
159
            )
160
        );
161 10
    }
162
163
    /**
164
     * @param ContainerBuilder $container
165
     */
166 10
    protected function createInfraServiceDefinitions(ContainerBuilder $container)
167
    {
168
        // RawRequestSerializer
169 10
        $container->setDefinition(
170 10
            'json_rpc_http_server.sdk.infra.serialization.raw_request_serializer',
171 10
            new Definition(
172 10
                RawRequestSerializer::class,
173 10
                [new Reference('json_rpc_http_server.sdk.app.serialization.request_denormalizer')]
174
            )
175
        );
176
177
        // RawResponseSerializer
178 10
        $container->setDefinition(
179 10
            'json_rpc_http_server.sdk.infra.serialization.raw_response_serializer',
180 10
            new Definition(
181 10
                RawResponseSerializer::class,
182 10
                [new Reference('json_rpc_http_server.sdk.app.serialization.response_normalizer')]
183
            )
184
        );
185
        // JsonRpcEndpoint
186 10
        $container->setDefinition(
187 10
            'json_rpc_http_server.sdk.infra.endpoint',
188 10
            new Definition(
189 10
                JsonRpcEndpoint::class,
190
                [
191 10
                    new Reference('json_rpc_http_server.sdk.infra.serialization.raw_request_serializer'),
192 10
                    new Reference('json_rpc_http_server.sdk.app.handler.request'),
193 10
                    new Reference('json_rpc_http_server.sdk.infra.serialization.raw_response_serializer'),
194 10
                    new Reference('json_rpc_http_server.sdk.app.creator.response')
195
                ]
196
            )
197
        );
198
        // ContainerMethodResolver
199 10
        $container->setDefinition(
200 10
            'json_rpc_http_server.psr11.infra.resolver.method',
201 10
            (new Definition(
202 10
                ContainerMethodResolver::class,
203
                [
204 10
                    new Reference('service_container')
205
                ]
206 10
            ))->addMethodCall(
207 10
                'setServiceNameResolver',
208
                [
209 10
                    new Reference(self::SERVICE_NAME_RESOLVER_SERVICE_NAME)
210
                ]
211
            )
212
        );
213 10
    }
214
215
    /**
216
     * @param ContainerBuilder $container
217
     */
218 10
    protected function createPublicServiceDefinitions(ContainerBuilder $container)
219
    {
220
        // JsonRpcHttpEndpoint
221 10
        $container->setDefinition(
222 10
            self::ENDPOINT_SERVICE_NAME,
223 10
            (new Definition(
224 10
                JsonRpcHttpEndpoint::class,
225
                [
226 10
                    new Reference('json_rpc_http_server.sdk.infra.endpoint')
227
                ]
228 10
            ))->setPublic(true)
229
        );
230
        // ServiceNameResolver
231 10
        $container->setDefinition(
232 10
            self::SERVICE_NAME_RESOLVER_SERVICE_NAME,
233 10
            (new Definition(ServiceNameResolver::class))->setPublic(true)
234
        );
235 10
    }
236
237
    /**
238
     * @param ContainerBuilder $container
239
     *
240
     * @return bool Whether it is a ContainerResolver or not
241
     */
242 10
    private function aliasMethodResolver(ContainerBuilder $container)
243
    {
244 10
        $isContainerResolver = false;
245 10
        if ($container->hasParameter(self::CUSTOM_METHOD_RESOLVER_CONTAINER_PARAM)) {
246 1
            $resolverServiceId = $container->getParameter(self::CUSTOM_METHOD_RESOLVER_CONTAINER_PARAM);
247
        } else {
248 9
            $serviceIdList = array_keys($container->findTaggedServiceIds(self::METHOD_RESOLVER_TAG));
249 9
            $serviceCount = count($serviceIdList);
250 9
            if ($serviceCount > 0) {
251 3
                if ($serviceCount > 1) {
252 1
                    throw new LogicException(
253 1
                        sprintf(
254 1
                            'Only one method resolver could be defined, found following services : %s',
255 1
                            implode(', ', $serviceIdList)
256
                        )
257
                    );
258
                }
259
                // Use the first result
260 2
                $resolverServiceId = array_shift($serviceIdList);
261
            } else {
262
                // Use ArrayMethodResolver as default resolver
263 6
                $resolverServiceId = 'json_rpc_http_server.psr11.infra.resolver.method';
264 6
                $isContainerResolver = true;
265
            }
266
        }
267
268 9
        $container->setAlias('json_rpc_http_server.infra.resolver.method', $resolverServiceId);
269
270 9
        return $isContainerResolver;
271
    }
272
273
    /**
274
     * @param ContainerBuilder $container
275
     */
276 6
    private function loadJsonRpcMethodsFromTag(ContainerBuilder $container)
277
    {
278
        // Check if methods have been defined by tags
279 6
        $methodServiceList = $container->findTaggedServiceIds(self::JSONRPC_METHOD_TAG);
280 6
        $defaultResolverDefinition = $container->getDefinition(self::SERVICE_NAME_RESOLVER_SERVICE_NAME);
281
282 6
        foreach ($methodServiceList as $serviceId => $tagAttributeList) {
283 3
            $this->checkJsonRpcMethodService($container, $serviceId);
284 3
            $methodNameList = $this->extractMethodNameList($tagAttributeList, $serviceId);
285 3
            foreach ($methodNameList as $methodName) {
286 3
                $defaultResolverDefinition->addMethodCall('addMethodMapping', [$methodName, $serviceId]);
287
            }
288
        }
289 4
    }
290
291
    /**
292
     * @param array  $tagAttributeList
293
     * @param string $serviceId
294
     */
295 3
    private function extractMethodNameList(array $tagAttributeList, string $serviceId) : array
296
    {
297 3
        $methodNameList = [];
298 3
        foreach ($tagAttributeList as $tagAttributeKey => $tagAttributeData) {
299 3
            if (!array_key_exists(self::JSONRPC_METHOD_TAG_METHOD_NAME_KEY, $tagAttributeData)) {
300 1
                throw new LogicException(sprintf(
301
                    'Service "%s" is taggued as JSON-RPC method but does not have'
302 1
                    . ' method name defined under "%s" tag attribute key',
303 1
                    $serviceId,
304 1
                    self::JSONRPC_METHOD_TAG_METHOD_NAME_KEY
305
                ));
306
            }
307 3
            $methodNameList[] = $tagAttributeData[self::JSONRPC_METHOD_TAG_METHOD_NAME_KEY];
308
        }
309
310 3
        return $methodNameList;
311
    }
312
313
    /**
314
     * @param ContainerBuilder $container
315
     * @param string           $serviceId
316
     */
317 3
    private function checkJsonRpcMethodService(ContainerBuilder $container, string $serviceId)
318
    {
319
        // Check if given service is public => must be public in order to get it from container later
320 3
        if (!$container->getDefinition($serviceId)->isPublic()) {
321 1
            throw new LogicException(sprintf(
322
                'Service "%s" is taggued as JSON-RPC method but is not public. Service must be public in order'
323 1
                . ' to retrieve it later',
324 1
                $serviceId
325
            ));
326
        }
327 3
    }
328
329
    /**
330
     * @param array            $configs
331
     * @param ContainerBuilder $container
332
     */
333 10
    private function compileAndProcessConfigurations(array $configs, ContainerBuilder $container)
334
    {
335 10
        if (true === $this->parseConfig) {
336 1
            $configuration = new Configuration();
337 1
            $config = (new Processor())->processConfiguration($configuration, $configs);
338
339 1
            if (array_key_exists('method_resolver', $config) && $config['method_resolver']) {
340 1
                $container->setParameter(self::CUSTOM_METHOD_RESOLVER_CONTAINER_PARAM, $config['method_resolver']);
341
            }
342
        }
343 10
    }
344
}
345