Completed
Pull Request — master (#3)
by Yo
02:42 queued 33s
created

JsonRpcHttpServerExtension::getNamespace()   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 0
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\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
     * @param ContainerBuilder $container
108
     */
109 9
    protected function createAppServiceDefinitions(ContainerBuilder $container)
110
    {
111
        // RequestDenormalizer
112 9
        $container->setDefinition(
113 9
            $this->prependServiceName($this->sdkAppRequestDenormalizerServiceId),
114 9
            new Definition(RequestDenormalizer::class)
115
        );
116
        // ResponseNormalizer
117 9
        $container->setDefinition(
118 9
            $this->prependServiceName($this->sdkAppResponseNormalizerServiceId),
119 9
            new Definition(ResponseNormalizer::class)
120
        );
121
        // ResponseCreator
122 9
        $container->setDefinition(
123 9
            $this->prependServiceName($this->sdkAppResponseCreatorServiceId),
124 9
            new Definition(ResponseCreator::class)
125
        );
126
        // CustomExceptionCreator
127 9
        $container->setDefinition(
128 9
            $this->prependServiceName($this->sdkAppCustomExceptionCreatorServiceId),
129 9
            new Definition(CustomExceptionCreator::class)
130
        );
131
132
        // MethodManager
133 9
        $container->setDefinition(
134 9
            $this->prependServiceName($this->sdkAppMethodManagerServiceId),
135 9
            new Definition(
136 9
                MethodManager::class,
137
                [
138 9
                    new Reference($this->prependServiceName($this->methodResolverStubServiceId)),
139 9
                    new Reference($this->prependServiceName($this->sdkAppCustomExceptionCreatorServiceId))
140
                ]
141
            )
142
        );
143
        // RequestHandler
144 9
        $container->setDefinition(
145 9
            $this->prependServiceName($this->sdkAppRequestHandlerServiceId),
146 9
            new Definition(
147 9
                RequestHandler::class,
148
                [
149 9
                    new Reference($this->prependServiceName($this->sdkAppMethodManagerServiceId)),
150 9
                    new Reference($this->prependServiceName($this->sdkAppResponseCreatorServiceId))
151
                ]
152
            )
153
        );
154 9
    }
155
156
    /**
157
     * @param ContainerBuilder $container
158
     */
159 9
    protected function createInfraServiceDefinitions(ContainerBuilder $container)
160
    {
161
        // RawRequestSerializer
162 9
        $container->setDefinition(
163 9
            $this->prependServiceName($this->sdkInfraRawReqSerializerServiceId),
164 9
            new Definition(
165 9
                RawRequestSerializer::class,
166 9
                [new Reference($this->prependServiceName($this->sdkAppRequestDenormalizerServiceId))]
167
            )
168
        );
169
170
        // RawResponseSerializer
171 9
        $container->setDefinition(
172 9
            $this->prependServiceName($this->sdkInfraRawRespSerializerServiceId),
173 9
            new Definition(
174 9
                RawResponseSerializer::class,
175 9
                [new Reference($this->prependServiceName($this->sdkAppResponseNormalizerServiceId))]
176
            )
177
        );
178
        // JsonRpcEndpoint
179 9
        $container->setDefinition(
180 9
            $this->prependServiceName($this->sdkInfraEndpointServiceId),
181 9
            new Definition(
182 9
                JsonRpcEndpoint::class,
183
                [
184 9
                    new Reference($this->prependServiceName($this->sdkInfraRawReqSerializerServiceId)),
185 9
                    new Reference($this->prependServiceName($this->sdkAppRequestHandlerServiceId)),
186 9
                    new Reference($this->prependServiceName($this->sdkInfraRawRespSerializerServiceId)),
187 9
                    new Reference($this->prependServiceName($this->sdkAppResponseCreatorServiceId))
188
                ]
189
            )
190
        );
191
        // ContainerMethodResolver
192 9
        $container->setDefinition(
193 9
            $this->prependServiceName($this->psr11InfraMethodResolverServiceId),
194 9
            (new Definition(
195 9
                ContainerMethodResolver::class,
196
                [
197 9
                    new Reference('service_container')
198
                ]
199 9
            ))->addMethodCall(
200 9
                'setServiceNameResolver',
201
                [
202 9
                    new Reference(self::SERVICE_NAME_RESOLVER_SERVICE_NAME)
203
                ]
204
            )
205
        );
206 9
    }
207
208
    /**
209
     * @param ContainerBuilder $container
210
     */
211 9
    protected function createPublicServiceDefinitions(ContainerBuilder $container)
212
    {
213
        // JsonRpcHttpEndpoint
214 9
        $container->setDefinition(
215 9
            self::ENDPOINT_SERVICE_NAME,
216 9
            (new Definition(
217 9
                JsonRpcHttpEndpoint::class,
218
                [
219 9
                    new Reference($this->prependServiceName($this->sdkInfraEndpointServiceId))
220
                ]
221 9
            ))->setPublic(true)
222
        );
223
        // ServiceNameResolver
224 9
        $container->setDefinition(
225 9
            self::SERVICE_NAME_RESOLVER_SERVICE_NAME,
226 9
            (new Definition(ServiceNameResolver::class))->setPublic(true)
227
        );
228 9
    }
229
230
    /**
231
     * @param ContainerBuilder $container
232
     *
233
     * @return bool Whether it is a ContainerResolver or not
234
     */
235 9
    private function aliasMethodResolver(ContainerBuilder $container)
236
    {
237 9
        $isContainerResolver = false;
238 9
        $serviceIdList = array_keys($container->findTaggedServiceIds(self::METHOD_RESOLVER_TAG));
239 9
        $serviceCount = count($serviceIdList);
240 9
        if ($serviceCount > 0) {
241 3
            if ($serviceCount > 1) {
242 1
                throw new LogicException(
243 1
                    sprintf(
244 1
                        'Only one method resolver could be defined, found following services : %s',
245 1
                        implode(', ', $serviceIdList)
246
                    )
247
                );
248
            }
249
            // Use the first result
250 2
            $resolverServiceId = array_shift($serviceIdList);
251
        } else {
252
            // Use ArrayMethodResolver as default resolver
253 6
            $resolverServiceId = $this->prependServiceName($this->psr11InfraMethodResolverServiceId);
254 6
            $isContainerResolver = true;
255
        }
256
257 8
        $container->setAlias($this->prependServiceName($this->methodResolverStubServiceId), $resolverServiceId);
258
259 8
        return $isContainerResolver;
260
    }
261
262
    /**
263
     * @param ContainerBuilder $container
264
     */
265 6
    private function loadJsonRpcMethodsFromTag(ContainerBuilder $container)
266
    {
267
        // Check if methods have been defined by tags
268 6
        $methodServiceList = $container->findTaggedServiceIds(self::JSONRPC_METHOD_TAG);
269 6
        $defaultResolverDefinition = $container->getDefinition(self::SERVICE_NAME_RESOLVER_SERVICE_NAME);
270
271 6
        foreach ($methodServiceList as $serviceId => $tagAttributeList) {
272 3
            $this->checkJsonRpcMethodService($container, $serviceId);
273 3
            $methodNameList = $this->extractMethodNameList($tagAttributeList, $serviceId);
274 3
            foreach ($methodNameList as $methodName) {
275 3
                $defaultResolverDefinition->addMethodCall('addMethodMapping', [$methodName, $serviceId]);
276
            }
277
        }
278 4
    }
279
280
    /**
281
     * @param string $serviceName
282
     *
283
     * @return string
284
     */
285 9
    private function prependServiceName(string $serviceName) : string
286
    {
287 9
        return sprintf('yoanm.jsonrpc_http_server.%s', $serviceName);
288
    }
289
290
    /**
291
     * @param array  $tagAttributeList
292
     * @param string $serviceId
293
     */
294 3
    private function extractMethodNameList(array $tagAttributeList, string $serviceId) : array
295
    {
296 3
        $methodNameList = [];
297 3
        foreach ($tagAttributeList as $tagAttributeKey => $tagAttributeData) {
298 3
            if (!array_key_exists(self::JSONRPC_METHOD_TAG_METHOD_NAME_KEY, $tagAttributeData)) {
299 1
                throw new LogicException(sprintf(
300
                    'Service "%s" is taggued as JSON-RPC method but does not have'
301 1
                    . ' method name defined under "%s" tag attribute key',
302 1
                    $serviceId,
303 1
                    self::JSONRPC_METHOD_TAG_METHOD_NAME_KEY
304
                ));
305
            }
306 3
            $methodNameList[] = $tagAttributeData[self::JSONRPC_METHOD_TAG_METHOD_NAME_KEY];
307
        }
308
309 3
        return $methodNameList;
310
    }
311
312
    /**
313
     * @param ContainerBuilder $container
314
     * @param string           $serviceId
315
     */
316 3
    private function checkJsonRpcMethodService(ContainerBuilder $container, string $serviceId)
317
    {
318
        // Check if given service is public => must be public in order to get it from container later
319 3
        if (!$container->getDefinition($serviceId)->isPublic()) {
320 1
            throw new LogicException(sprintf(
321
                'Service "%s" is taggued as JSON-RPC method but is not public. Service must be public in order'
322 1
                . ' to retrieve it later',
323 1
                $serviceId
324
            ));
325
        }
326 3
    }
327
}
328