Failed Conditions
Pull Request — master (#75)
by Yo
02:25
created

JsonRpcHttpServerExtension::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 12
ccs 0
cts 8
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
namespace Yoanm\SymfonyJsonRpcHttpServer\DependencyInjection;
3
4
use Symfony\Component\Config\Definition\Processor;
5
use Symfony\Component\Config\FileLocator;
6
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
7
use Symfony\Component\DependencyInjection\ContainerBuilder;
8
use Symfony\Component\DependencyInjection\Definition;
9
use Symfony\Component\DependencyInjection\Exception\LogicException;
10
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
11
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
12
use Symfony\Component\DependencyInjection\Reference;
13
use Yoanm\JsonRpcServer\App\Dispatcher\JsonRpcServerDispatcherAwareTrait;
14
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodAwareInterface;
15
16
/**
17
 * Class JsonRpcHttpServerExtension
18
 */
19
class JsonRpcHttpServerExtension implements ExtensionInterface, CompilerPassInterface
20
{
21
    // Extension identifier (used in configuration for instance)
22
    public const EXTENSION_IDENTIFIER = 'json_rpc_http_server';
23
24
    public const ENDPOINT_PATH_CONTAINER_PARAM_ID = self::EXTENSION_IDENTIFIER.'.http_endpoint_path';
25
26
    /** Tags */
27
    /**** Methods tags **/
28
    // Use this tag to inject your JSON-RPC methods into the default method resolver
29
    public const JSONRPC_METHOD_TAG = 'json_rpc_http_server.jsonrpc_method';
30
    // And add an attribute with following key
31
    public const JSONRPC_METHOD_TAG_METHOD_NAME_KEY = 'method';
32
    /**** END - Methods tags **/
33
34
    // Server dispatcher - Use this tag and server dispatcher will be injected
35
    public const JSONRPC_SERVER_DISPATCHER_AWARE_TAG = 'json_rpc_http_server.server_dispatcher_aware';
36
37
    // JSON-RPC Methods mapping - Use this tag and all JSON-RPC method instance will be injected
38
    // Useful for documentation for instance
39
    public const JSONRPC_METHOD_AWARE_TAG = 'json_rpc_http_server.method_aware';
40
41
42
    private const PARAMS_VALIDATOR_ALIAS = 'json_rpc_http_server.alias.params_validator';
43
    private const REQUEST_HANDLER_SERVICE_ID = 'json_rpc_server_sdk.app.handler.jsonrpc_request';
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function load(array $configs, ContainerBuilder $container)
49
    {
50
        $this->compileAndProcessConfigurations($configs, $container);
51
52
        $loader = new YamlFileLoader(
53
            $container,
54
            new FileLocator(__DIR__.'/../Resources/config')
55
        );
56
        $loader->load('sdk.services.app.yaml');
57
        $loader->load('sdk.services.infra.yaml');
58
        $loader->load('services.private.yaml');
59
        $loader->load('services.public.yaml');
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65
    public function process(ContainerBuilder $container)
66
    {
67
        $this->bindJsonRpcServerDispatcher($container);
68
        $this->bindValidatorIfDefined($container);
69
        $this->binJsonRpcMethods($container);
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function getNamespace()
76
    {
77
        return 'http://example.org/schema/dic/'.$this->getAlias();
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function getXsdValidationBasePath()
84
    {
85
        return '';
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public function getAlias()
92
    {
93
        return self::EXTENSION_IDENTIFIER;
94
    }
95
96
    /**
97
     * @param array            $configs
98
     * @param ContainerBuilder $container
99
     */
100
    private function compileAndProcessConfigurations(array $configs, ContainerBuilder $container) : void
101
    {
102
        $configuration = new Configuration();
103
        $config = (new Processor())->processConfiguration($configuration, $configs);
104
105
        $httpEndpointPath = $config['endpoint'];
106
107
        $container->setParameter(self::ENDPOINT_PATH_CONTAINER_PARAM_ID, $httpEndpointPath);
108
    }
109
110
    /**
111
     * @param ContainerBuilder $container
112
     */
113
    private function bindJsonRpcServerDispatcher(ContainerBuilder $container) : void
114
    {
115
        $dispatcherRef = new Reference('json_rpc_http_server.dispatcher.server');
116
        $dispatcherAwareServiceList = $container->findTaggedServiceIds(self::JSONRPC_SERVER_DISPATCHER_AWARE_TAG);
117
        foreach ($dispatcherAwareServiceList as $serviceId => $tagAttributeList) {
118
            $definition = $container->getDefinition($serviceId);
119
120
            if (!in_array(JsonRpcServerDispatcherAwareTrait::class, class_uses($definition->getClass()))) {
121
                throw new LogicException(
122
                    sprintf(
123
                        'Service "%s" is taggued with "%s" but does not use "%s"',
124
                        $serviceId,
125
                        self::JSONRPC_SERVER_DISPATCHER_AWARE_TAG,
126
                        JsonRpcServerDispatcherAwareTrait::class
127
                    )
128
                );
129
            }
130
131
            $definition->addMethodCall('setJsonRpcServerDispatcher', [$dispatcherRef]);
132
        }
133
    }
134
135
    private function bindValidatorIfDefined(ContainerBuilder $container) : void
136
    {
137
        if ($container->hasAlias(self::PARAMS_VALIDATOR_ALIAS)) {
138
            $container->getDefinition(self::REQUEST_HANDLER_SERVICE_ID)
139
                ->addMethodCall(
140
                    'setMethodParamsValidator',
141
                    [
142
                        new Reference(self::PARAMS_VALIDATOR_ALIAS)
143
                    ]
144
                )
145
            ;
146
        }
147
    }
148
149
    /**
150
     * @param ContainerBuilder $container
151
     */
152
    private function binJsonRpcMethods(ContainerBuilder $container) : void
153
    {
154
        $mappingAwareServiceDefinitionList = $this->findAndValidateMappingAwareDefinitionList($container);
155
156
        $jsonRpcMethodDefinitionList = (new JsonRpcMethodDefinitionHelper())
157
            ->findAndValidateJsonRpcMethodDefinition($container);
158
159
        $methodMappingList = [];
160
        foreach ($jsonRpcMethodDefinitionList as $jsonRpcMethodServiceId => $methodNameList) {
161
            foreach ($methodNameList as $methodName) {
162
                $methodMappingList[$methodName] = new Reference($jsonRpcMethodServiceId);
163
                $this->bindJsonRpcMethod($methodName, $jsonRpcMethodServiceId, $mappingAwareServiceDefinitionList);
164
            }
165
        }
166
167
        // Service locator for method resolver
168
        // => first argument is an array of wanted service with keys as alias for internal use
169
        $container->getDefinition('json_rpc_http_server.service_locator.method_resolver')
170
            ->setArgument(0, $methodMappingList);
171
    }
172
173
    /**
174
     * @param string       $methodName
175
     * @param string       $jsonRpcMethodServiceId
176
     * @param Definition[] $mappingAwareServiceDefinitionList
177
     */
178
    private function bindJsonRpcMethod(
179
        string $methodName,
180
        string $jsonRpcMethodServiceId,
181
        array $mappingAwareServiceDefinitionList
182
    ) : void {
183
        foreach ($mappingAwareServiceDefinitionList as $methodAwareServiceDefinition) {
184
            $methodAwareServiceDefinition->addMethodCall(
185
                'addJsonRpcMethod',
186
                [$methodName, new Reference($jsonRpcMethodServiceId)]
187
            );
188
        }
189
    }
190
191
    /**
192
     * @param ContainerBuilder $container
193
     *
194
     * @return array
195
     */
196
    private function findAndValidateMappingAwareDefinitionList(ContainerBuilder $container): array
197
    {
198
        $mappingAwareServiceDefinitionList = [];
199
        $methodAwareServiceIdList = array_keys($container->findTaggedServiceIds(self::JSONRPC_METHOD_AWARE_TAG));
200
        foreach ($methodAwareServiceIdList as $serviceId) {
201
            $definition = $container->getDefinition($serviceId);
202
203
            $this->checkMethodAwareServiceIdList($definition, $serviceId, $container);
204
205
            $mappingAwareServiceDefinitionList[$serviceId] = $definition;
206
        }
207
208
        return $mappingAwareServiceDefinitionList;
209
    }
210
211
    private function checkMethodAwareServiceIdList(
212
        Definition $definition,
213
        string $serviceId,
214
        ContainerBuilder $container
215
    ) : void {
216
        $class = $container->getReflectionClass($definition->getClass());
217
218
        if (null !== $class && !$class->implementsInterface(JsonRpcMethodAwareInterface::class)) {
219
            throw new LogicException(sprintf(
220
                'Service "%s" is taggued as JSON-RPC method aware but does not implement %s',
221
                $serviceId,
222
                JsonRpcMethodAwareInterface::class
223
            ));
224
        }
225
    }
226
}
227