Failed Conditions
Pull Request — master (#5)
by Yo
01:55
created

JsonRpcHttpServerExtension::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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 = 'yoanm.jsonrpc_http_server.endpoint';
34
35
    // Use this tag to inject your own resolver
36
    const METHOD_RESOLVER_TAG = 'yoanm.jsonrpc_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 = 'yoanm.jsonrpc_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 = 'yoanm.jsonrpc_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 $sdkAppResponseCreatorServiceId        = 'sdk.app.creator.response';
51
    private $sdkAppCustomExceptionCreatorServiceId = 'sdk.app.creator.custom_exception';
52
    private $sdkAppRequestDenormalizerServiceId    = 'sdk.app.serialization.request_denormalizer';
53
    private $sdkAppResponseNormalizerServiceId     = 'sdk.app.serialization.response_normalizer';
54
    private $sdkAppMethodManagerServiceId          = 'sdk.app.manager.method';
55
    private $sdkAppRequestHandlerServiceId         = 'sdk.app.handler.request';
56
57
    private $sdkInfraEndpointServiceId          = 'sdk.infra.endpoint';
58
    private $sdkInfraRawReqSerializerServiceId  = 'sdk.infra.serialization.raw_request_serializer';
59
    private $sdkInfraRawRespSerializerServiceId = 'sdk.infra.serialization.raw_response_serializer';
60
61
    private $psr11InfraMethodResolverServiceId = 'psr11.infra.resolver.method';
62
63
    private $methodResolverStubServiceId = 'infra.resolver.method';
64
    private $customResolverContainerParameter = self::EXTENSION_IDENTIFIER.'.custom_method_resolver';
65
66
    /**
67
     * @param bool|false $parseConfig If true, Config component is required
68
     */
69 10
    public function __construct(bool $parseConfig = false)
70
    {
71 10
        $this->parseConfig = $parseConfig;
0 ignored issues
show
Bug Best Practice introduced by
The property parseConfig does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
72 10
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77 9
    public function load(array $configs, ContainerBuilder $container)
78
    {
79 9
        $this->compileAndProcessConfigurations($configs, $container);
80
81
        // Use only references to avoid class instantiation
82
        // And don't use file configuration in order to not add Symfony\Component\Config as dependency
83 9
        $this->createPublicServiceDefinitions($container);
84 9
        $this->createInfraServiceDefinitions($container);
85 9
        $this->createAppServiceDefinitions($container);
86 9
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91 10
    public function getNamespace()
92
    {
93 10
        return 'http://example.org/schema/dic/'.$this->getAlias();
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99 1
    public function getXsdValidationBasePath()
100
    {
101 1
        return '';
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107 10
    public function getAlias()
108
    {
109 10
        return self::EXTENSION_IDENTIFIER;
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 9
    public function process(ContainerBuilder $container)
116
    {
117 9
        $isContainerResolver = $this->aliasMethodResolver($container);
118 8
        if (true === $isContainerResolver) {
119 6
            $this->loadJsonRpcMethodsFromTag($container);
120
        }
121 6
    }
122
123
    /**
124
     * @param ContainerBuilder $container
125
     */
126 9
    protected function createAppServiceDefinitions(ContainerBuilder $container)
127
    {
128
        // RequestDenormalizer
129 9
        $container->setDefinition(
130 9
            $this->prependServiceName($this->sdkAppRequestDenormalizerServiceId),
131 9
            new Definition(RequestDenormalizer::class)
132
        );
133
        // ResponseNormalizer
134 9
        $container->setDefinition(
135 9
            $this->prependServiceName($this->sdkAppResponseNormalizerServiceId),
136 9
            new Definition(ResponseNormalizer::class)
137
        );
138
        // ResponseCreator
139 9
        $container->setDefinition(
140 9
            $this->prependServiceName($this->sdkAppResponseCreatorServiceId),
141 9
            new Definition(ResponseCreator::class)
142
        );
143
        // CustomExceptionCreator
144 9
        $container->setDefinition(
145 9
            $this->prependServiceName($this->sdkAppCustomExceptionCreatorServiceId),
146 9
            new Definition(CustomExceptionCreator::class)
147
        );
148
149
        // MethodManager
150 9
        $container->setDefinition(
151 9
            $this->prependServiceName($this->sdkAppMethodManagerServiceId),
152 9
            new Definition(
153 9
                MethodManager::class,
154
                [
155 9
                    new Reference($this->prependServiceName($this->methodResolverStubServiceId)),
156 9
                    new Reference($this->prependServiceName($this->sdkAppCustomExceptionCreatorServiceId))
157
                ]
158
            )
159
        );
160
        // RequestHandler
161 9
        $container->setDefinition(
162 9
            $this->prependServiceName($this->sdkAppRequestHandlerServiceId),
163 9
            new Definition(
164 9
                RequestHandler::class,
165
                [
166 9
                    new Reference($this->prependServiceName($this->sdkAppMethodManagerServiceId)),
167 9
                    new Reference($this->prependServiceName($this->sdkAppResponseCreatorServiceId))
168
                ]
169
            )
170
        );
171 9
    }
172
173
    /**
174
     * @param ContainerBuilder $container
175
     */
176 9
    protected function createInfraServiceDefinitions(ContainerBuilder $container)
177
    {
178
        // RawRequestSerializer
179 9
        $container->setDefinition(
180 9
            $this->prependServiceName($this->sdkInfraRawReqSerializerServiceId),
181 9
            new Definition(
182 9
                RawRequestSerializer::class,
183 9
                [new Reference($this->prependServiceName($this->sdkAppRequestDenormalizerServiceId))]
184
            )
185
        );
186
187
        // RawResponseSerializer
188 9
        $container->setDefinition(
189 9
            $this->prependServiceName($this->sdkInfraRawRespSerializerServiceId),
190 9
            new Definition(
191 9
                RawResponseSerializer::class,
192 9
                [new Reference($this->prependServiceName($this->sdkAppResponseNormalizerServiceId))]
193
            )
194
        );
195
        // JsonRpcEndpoint
196 9
        $container->setDefinition(
197 9
            $this->prependServiceName($this->sdkInfraEndpointServiceId),
198 9
            new Definition(
199 9
                JsonRpcEndpoint::class,
200
                [
201 9
                    new Reference($this->prependServiceName($this->sdkInfraRawReqSerializerServiceId)),
202 9
                    new Reference($this->prependServiceName($this->sdkAppRequestHandlerServiceId)),
203 9
                    new Reference($this->prependServiceName($this->sdkInfraRawRespSerializerServiceId)),
204 9
                    new Reference($this->prependServiceName($this->sdkAppResponseCreatorServiceId))
205
                ]
206
            )
207
        );
208
        // ContainerMethodResolver
209 9
        $container->setDefinition(
210 9
            $this->prependServiceName($this->psr11InfraMethodResolverServiceId),
211 9
            (new Definition(
212 9
                ContainerMethodResolver::class,
213
                [
214 9
                    new Reference('service_container')
215
                ]
216 9
            ))->addMethodCall(
217 9
                'setServiceNameResolver',
218
                [
219 9
                    new Reference(self::SERVICE_NAME_RESOLVER_SERVICE_NAME)
220
                ]
221
            )
222
        );
223 9
    }
224
225
    /**
226
     * @param ContainerBuilder $container
227
     */
228 9
    protected function createPublicServiceDefinitions(ContainerBuilder $container)
229
    {
230
        // JsonRpcHttpEndpoint
231 9
        $container->setDefinition(
232 9
            self::ENDPOINT_SERVICE_NAME,
233 9
            (new Definition(
234 9
                JsonRpcHttpEndpoint::class,
235
                [
236 9
                    new Reference($this->prependServiceName($this->sdkInfraEndpointServiceId))
237
                ]
238 9
            ))->setPublic(true)
239
        );
240
        // ServiceNameResolver
241 9
        $container->setDefinition(
242 9
            self::SERVICE_NAME_RESOLVER_SERVICE_NAME,
243 9
            (new Definition(ServiceNameResolver::class))->setPublic(true)
244
        );
245 9
    }
246
247
    /**
248
     * @param ContainerBuilder $container
249
     *
250
     * @return bool Whether it is a ContainerResolver or not
251
     */
252 9
    private function aliasMethodResolver(ContainerBuilder $container)
253
    {
254 9
        $isContainerResolver = false;
255 9
        if ($container->hasParameter($this->customResolverContainerParameter)) {
256
            $resolverServiceId = $container->getParameter($this->customResolverContainerParameter);
257
        } else {
258 9
            $serviceIdList = array_keys($container->findTaggedServiceIds(self::METHOD_RESOLVER_TAG));
259 9
            $serviceCount = count($serviceIdList);
260 9
            if ($serviceCount > 0) {
261 3
                if ($serviceCount > 1) {
262 1
                    throw new LogicException(
263 1
                        sprintf(
264 1
                            'Only one method resolver could be defined, found following services : %s',
265 1
                            implode(', ', $serviceIdList)
266
                        )
267
                    );
268
                }
269
                // Use the first result
270 2
                $resolverServiceId = array_shift($serviceIdList);
271
            } else {
272
                // Use ArrayMethodResolver as default resolver
273 6
                $resolverServiceId = $this->prependServiceName($this->psr11InfraMethodResolverServiceId);
274 6
                $isContainerResolver = true;
275
            }
276
        }
277
278 8
        $container->setAlias($this->prependServiceName($this->methodResolverStubServiceId), $resolverServiceId);
279
280 8
        return $isContainerResolver;
281
    }
282
283
    /**
284
     * @param ContainerBuilder $container
285
     */
286 6
    private function loadJsonRpcMethodsFromTag(ContainerBuilder $container)
287
    {
288
        // Check if methods have been defined by tags
289 6
        $methodServiceList = $container->findTaggedServiceIds(self::JSONRPC_METHOD_TAG);
290 6
        $defaultResolverDefinition = $container->getDefinition(self::SERVICE_NAME_RESOLVER_SERVICE_NAME);
291
292 6
        foreach ($methodServiceList as $serviceId => $tagAttributeList) {
293 3
            $this->checkJsonRpcMethodService($container, $serviceId);
294 3
            $methodNameList = $this->extractMethodNameList($tagAttributeList, $serviceId);
295 3
            foreach ($methodNameList as $methodName) {
296 3
                $defaultResolverDefinition->addMethodCall('addMethodMapping', [$methodName, $serviceId]);
297
            }
298
        }
299 4
    }
300
301
    /**
302
     * @param string $serviceName
303
     *
304
     * @return string
305
     */
306 9
    private function prependServiceName(string $serviceName) : string
307
    {
308 9
        return sprintf('yoanm.jsonrpc_http_server.%s', $serviceName);
309
    }
310
311
    /**
312
     * @param array  $tagAttributeList
313
     * @param string $serviceId
314
     */
315 3
    private function extractMethodNameList(array $tagAttributeList, string $serviceId) : array
316
    {
317 3
        $methodNameList = [];
318 3
        foreach ($tagAttributeList as $tagAttributeKey => $tagAttributeData) {
319 3
            if (!array_key_exists(self::JSONRPC_METHOD_TAG_METHOD_NAME_KEY, $tagAttributeData)) {
320 1
                throw new LogicException(sprintf(
321
                    'Service "%s" is taggued as JSON-RPC method but does not have'
322 1
                    . ' method name defined under "%s" tag attribute key',
323 1
                    $serviceId,
324 1
                    self::JSONRPC_METHOD_TAG_METHOD_NAME_KEY
325
                ));
326
            }
327 3
            $methodNameList[] = $tagAttributeData[self::JSONRPC_METHOD_TAG_METHOD_NAME_KEY];
328
        }
329
330 3
        return $methodNameList;
331
    }
332
333
    /**
334
     * @param ContainerBuilder $container
335
     * @param string           $serviceId
336
     */
337 3
    private function checkJsonRpcMethodService(ContainerBuilder $container, string $serviceId)
338
    {
339
        // Check if given service is public => must be public in order to get it from container later
340 3
        if (!$container->getDefinition($serviceId)->isPublic()) {
341 1
            throw new LogicException(sprintf(
342
                'Service "%s" is taggued as JSON-RPC method but is not public. Service must be public in order'
343 1
                . ' to retrieve it later',
344 1
                $serviceId
345
            ));
346
        }
347 3
    }
348
349
    /**
350
     * @param array            $configs
351
     * @param ContainerBuilder $container
352
     */
353 9
    private function compileAndProcessConfigurations(array $configs, ContainerBuilder $container)
354
    {
355 9
        if (true === $this->parseConfig) {
356
            $configuration = new Configuration();
357
            $config = (new Processor())->processConfiguration($configuration, $configs);
358
359
            if (array_key_exists('method_resolver', $config) && $config['method_resolver']) {
360
                $container->setParameter($this->customResolverContainerParameter, $config['method_resolver']);
361
            }
362
        }
363 9
    }
364
}
365