Failed Conditions
Pull Request — master (#3)
by Yo
02:04
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 9
    public function getNamespace()
75
    {
76 9
        return 'http://example.org/schema/dic/'.$this->getAlias();
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function getXsdValidationBasePath()
83
    {
84
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by Symfony\Component\Depend...XsdValidationBasePath() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90 9
    public function getAlias()
91
    {
92 9
        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