Failed Conditions
Pull Request — master (#12)
by Yo
01:57
created

DependencyInjection/JsonRpcHttpServerExtension.php (2 issues)

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