Completed
Pull Request — master (#3)
by Yo
02:03
created

JsonRpcHttpServerExtension::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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