Skip to content

Commit 06b6f14

Browse files
authored
Add methods mapping injection (#29)
1 parent 48af3a6 commit 06b6f14

File tree

12 files changed

+480
-51
lines changed

12 files changed

+480
-51
lines changed

features/bootstrap/DemoAppContext.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPUnit\Framework\Assert;
99
use Symfony\Component\HttpFoundation\Request;
1010
use Symfony\Component\HttpFoundation\Response;
11+
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface;
1112

1213
/**
1314
* Defines application features from the specific context.
@@ -46,6 +47,36 @@ public function thenIShouldHaveAResponseFromDemoAppWithFollowingContent($httpCod
4647
Assert::assertSame((int) $httpCode, $this->lastResponse->getStatusCode());
4748
}
4849

50+
/**
51+
* @Then Collector should have :methodClass JSON-RPC method with name :methodName
52+
*/
53+
public function thenCollectorShouldHaveAMethodWithName($methodClass, $methodName)
54+
{
55+
$kernel = $this->getDemoAppKernel();
56+
$kernel->boot();
57+
$mappingList = $kernel->getContainer()
58+
->get('mapping_aware_service')
59+
->getMappingList()
60+
;
61+
$kernel->shutdown();
62+
63+
if (!isset($mappingList[$methodName])) {
64+
throw new \Exception(sprintf('No mapping defined to method name "%s"', $methodName));
65+
}
66+
$method = $mappingList[$methodName];
67+
68+
Assert::assertInstanceOf(
69+
JsonRpcMethodInterface::class,
70+
$method,
71+
'Method must be a JsonRpcMethodInterface instance'
72+
);
73+
Assert::assertInstanceOf(
74+
$methodClass,
75+
$method,
76+
sprintf('Method "%s" is not an instance of "%s"', $methodName, $methodClass)
77+
);
78+
}
79+
4980
/**
5081
* @return AbstractKernel
5182
*/

features/bundle.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Feature: demo symfony application
22

33
Scenario: Check that all methods are available
4-
# Ensure the two methods with tag have been loaded
4+
# Ensure methods with tag have been loaded
55
When I send following "POST" input on "/my-custom-endpoint" demoApp kernel endpoint:
66
"""
77
{"jsonrpc": "2.0", "method": "bundledMethodA", "id": 1}
Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
1-
# Configure JSON-RPC method services. /!\ Do not forget to define public visibility /!\
1+
# Configure JSON-RPC method services.
22
services:
3-
_defaults:
4-
public: true
5-
6-
## JSON-RPC methos
73
jsonrpc.method.a:
84
class: DemoApp\Method\MethodA
5+
tags:
6+
- { name: 'json_rpc_http_server.jsonrpc_method', method: 'bundledMethodA' }
7+
- { name: 'json_rpc_http_server.jsonrpc_method', method: 'bundledMethodAAlias' }
98
jsonrpc.method.b:
109
class: DemoApp\Method\MethodB
10+
tags:
11+
- { name: 'json_rpc_http_server.jsonrpc_method', method: 'bundledMethodB' }
1112
jsonrpc.method.c:
1213
class: DemoApp\Method\MethodC
14+
tags:
15+
- { name: 'json_rpc_http_server.jsonrpc_method', method: 'bundledGetDummy' }
1316
jsonrpc.method.d:
1417
class: DemoApp\Method\MethodD
15-
18+
tags:
19+
- { name: 'json_rpc_http_server.jsonrpc_method', method: 'bundledGetAnotherDummy' }
1620

1721
## Resolver mock
1822
resolver:
1923
class: DemoApp\Resolver\JsonRpcMethodResolver
20-
calls:
21-
- ['addMethod', ['@jsonrpc.method.a', 'bundledMethodA']]
22-
- ['addMethod', ['@jsonrpc.method.a', 'bundledMethodAAlias']]
23-
- ['addMethod', ['@jsonrpc.method.b', 'bundledMethodB']]
24-
- ['addMethod', ['@jsonrpc.method.c', 'bundledGetDummy']]
25-
- ['addMethod', ['@jsonrpc.method.d', 'bundledGetAnotherDummy']]
24+
tags: ['json_rpc_http_server.method_aware']
25+
26+
## Mapping aware service
27+
mapping_aware_service:
28+
public: true # In order to allow Behat context to load it later
29+
class: DemoApp\Collector\MappingCollector
30+
tags: ['json_rpc_http_server.method_aware']
2631

2732
# Alias resolver mock to bundle resolver
2833
json_rpc_http_server.alias.method_resolver: '@resolver'
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
namespace DemoApp\Collector;
3+
4+
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodAwareInterface;
5+
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface;
6+
7+
class MappingCollector implements JsonRpcMethodAwareInterface
8+
{
9+
/** @var JsonRpcMethodInterface[] */
10+
private $mappingList = [];
11+
12+
public function addJsonRpcMethod(string $methodName, JsonRpcMethodInterface $method): void
13+
{
14+
$this->mappingList[$methodName] = $method;
15+
}
16+
17+
/**
18+
* @return JsonRpcMethodInterface[]
19+
*/
20+
public function getMappingList() : array
21+
{
22+
return $this->mappingList;
23+
}
24+
}

features/demo_app/src/Resolver/JsonRpcMethodResolver.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<?php
22
namespace DemoApp\Resolver;
33

4+
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodAwareInterface;
45
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodInterface;
56
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodResolverInterface as BasResolverInterface;
67

7-
class JsonRpcMethodResolver implements BasResolverInterface
8+
class JsonRpcMethodResolver implements BasResolverInterface, JsonRpcMethodAwareInterface
89
{
910
/** @var JsonRpcMethodInterface[] */
1011
private $methodList;
@@ -25,7 +26,7 @@ public function resolve(string $methodName) : ?JsonRpcMethodInterface
2526
* @param JsonRpcMethodInterface $method
2627
* @param string $methodName
2728
*/
28-
public function addMethod(JsonRpcMethodInterface $method, string $methodName)
29+
public function addJsonRpcMethod(string $methodName, JsonRpcMethodInterface $method) : void
2930
{
3031
$this->methodList[$methodName] = $method;
3132
}

features/mapping_collector.feature

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Feature: Mapping collector
2+
3+
Scenario: Check that configured mapping aware service have methods mapping
4+
Then Collector should have "DemoApp\Method\MethodA" JSON-RPC method with name "bundledMethodA"
5+
And Collector should have "DemoApp\Method\MethodA" JSON-RPC method with name "bundledMethodA"
6+
And Collector should have "DemoApp\Method\MethodB" JSON-RPC method with name "bundledMethodB"
7+
And Collector should have "DemoApp\Method\MethodC" JSON-RPC method with name "bundledGetDummy"
8+
And Collector should have "DemoApp\Method\MethodD" JSON-RPC method with name "bundledGetAnotherDummy"

src/DependencyInjection/JsonRpcHttpServerExtension.php

Lines changed: 109 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,40 @@
55
use Symfony\Component\Config\FileLocator;
66
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
77
use Symfony\Component\DependencyInjection\ContainerBuilder;
8+
use Symfony\Component\DependencyInjection\Definition;
89
use Symfony\Component\DependencyInjection\Exception\LogicException;
910
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
1011
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
1112
use Symfony\Component\DependencyInjection\Reference;
1213
use Yoanm\JsonRpcServer\App\Dispatcher\JsonRpcServerDispatcherAwareTrait;
14+
use Yoanm\JsonRpcServer\Domain\JsonRpcMethodAwareInterface;
1315

1416
/**
1517
* Class JsonRpcHttpServerExtension
1618
*/
1719
class JsonRpcHttpServerExtension implements ExtensionInterface, CompilerPassInterface
1820
{
1921
// Extension identifier (used in configuration for instance)
20-
const EXTENSION_IDENTIFIER = 'json_rpc_http_server';
22+
public const EXTENSION_IDENTIFIER = 'json_rpc_http_server';
2123

2224
/** Tags */
25+
/**** Methods tags **/
26+
// Use this tag to inject your JSON-RPC methods into the default method resolver
27+
public const JSONRPC_METHOD_TAG = 'json_rpc_http_server.jsonrpc_method';
28+
// And add an attribute with following key
29+
public const JSONRPC_METHOD_TAG_METHOD_NAME_KEY = 'method';
30+
/**** END - Methods tags **/
31+
2332
// Server dispatcher - Use this tag and server dispatcher will be injected
24-
const JSONRPC_SERVER_DISPATCHER_AWARE_TAG = 'json_rpc_http_server.server_dispatcher_aware';
33+
public const JSONRPC_SERVER_DISPATCHER_AWARE_TAG = 'json_rpc_http_server.server_dispatcher_aware';
2534

35+
// JSON-RPC Methods mapping - Use this tag and all JSON-RPC method instance will be injected
36+
// Useful for documentation for instance
37+
public const JSONRPC_METHOD_AWARE_TAG = 'json_rpc_http_server.method_aware';
2638

27-
/** Method resolver */
28-
const METHOD_RESOLVER_ALIAS = 'json_rpc_http_server.alias.method_resolver';
29-
/** Params validator */
30-
const PARAMS_VALIDATOR_ALIAS = 'json_rpc_http_server.alias.params_validator';
3139

32-
const REQUEST_HANDLER_SERVICE_ID = 'json_rpc_server_sdk.app.handler.jsonrpc_request';
40+
private const PARAMS_VALIDATOR_ALIAS = 'json_rpc_http_server.alias.params_validator';
41+
private const REQUEST_HANDLER_SERVICE_ID = 'json_rpc_server_sdk.app.handler.jsonrpc_request';
3342

3443
/**
3544
* {@inheritdoc}
@@ -54,6 +63,7 @@ public function process(ContainerBuilder $container)
5463
{
5564
$this->bindJsonRpcServerDispatcher($container);
5665
$this->bindValidatorIfDefined($container);
66+
$this->binJsonRpcMethods($container);
5767
}
5868

5969
/**
@@ -80,12 +90,26 @@ public function getAlias()
8090
return self::EXTENSION_IDENTIFIER;
8191
}
8292

93+
/**
94+
* @param array $configs
95+
* @param ContainerBuilder $container
96+
*/
97+
private function compileAndProcessConfigurations(array $configs, ContainerBuilder $container) : void
98+
{
99+
$configuration = new Configuration();
100+
$config = (new Processor())->processConfiguration($configuration, $configs);
101+
102+
$httpEndpointPath = $config['endpoint'];
103+
104+
$container->setParameter(self::EXTENSION_IDENTIFIER.'.http_endpoint_path', $httpEndpointPath);
105+
}
106+
83107
/**
84108
* @param ContainerBuilder $container
85109
*
86110
* @return Reference|null Null in case no dispatcher found
87111
*/
88-
private function bindJsonRpcServerDispatcher(ContainerBuilder $container)
112+
private function bindJsonRpcServerDispatcher(ContainerBuilder $container) : void
89113
{
90114
$dispatcherRef = new Reference('json_rpc_http_server.dispatcher.server');
91115
$dispatcherAwareServiceList = $container->findTaggedServiceIds(self::JSONRPC_SERVER_DISPATCHER_AWARE_TAG);
@@ -107,21 +131,7 @@ private function bindJsonRpcServerDispatcher(ContainerBuilder $container)
107131
}
108132
}
109133

110-
/**
111-
* @param array $configs
112-
* @param ContainerBuilder $container
113-
*/
114-
private function compileAndProcessConfigurations(array $configs, ContainerBuilder $container)
115-
{
116-
$configuration = new Configuration();
117-
$config = (new Processor())->processConfiguration($configuration, $configs);
118-
119-
$httpEndpointPath = $config['endpoint'];
120-
121-
$container->setParameter(self::EXTENSION_IDENTIFIER.'.http_endpoint_path', $httpEndpointPath);
122-
}
123-
124-
private function bindValidatorIfDefined(ContainerBuilder $container)
134+
private function bindValidatorIfDefined(ContainerBuilder $container) : void
125135
{
126136
if ($container->hasAlias(self::PARAMS_VALIDATOR_ALIAS)) {
127137
$container->getDefinition(self::REQUEST_HANDLER_SERVICE_ID)
@@ -134,4 +144,80 @@ private function bindValidatorIfDefined(ContainerBuilder $container)
134144
;
135145
}
136146
}
147+
148+
/**
149+
* @param ContainerBuilder $container
150+
*/
151+
private function binJsonRpcMethods(ContainerBuilder $container) : void
152+
{
153+
$mappingAwareServiceDefinitionList = $this->findAndValidateMappingAwareDefinitionList($container);
154+
155+
if (0 === count($mappingAwareServiceDefinitionList)) {
156+
return;
157+
}
158+
159+
$jsonRpcMethodDefinitionList = (new JsonRpcMethodDefinitionHelper())
160+
->findAndValidateJsonRpcMethodDefinition($container);
161+
162+
foreach ($jsonRpcMethodDefinitionList as $jsonRpcMethodServiceId => $methodNameList) {
163+
foreach ($methodNameList as $methodName) {
164+
$this->bindJsonRpcMethod($methodName, $jsonRpcMethodServiceId, $mappingAwareServiceDefinitionList);
165+
}
166+
}
167+
}
168+
169+
/**
170+
* @param string $methodName
171+
* @param Definition $jsonRpcMethodDefinition
172+
* @param Definition[] $mappingAwareServiceDefinitionList
173+
*/
174+
private function bindJsonRpcMethod(
175+
string $methodName,
176+
string $jsonRpcMethodServiceId,
177+
array $mappingAwareServiceDefinitionList
178+
) : void {
179+
foreach ($mappingAwareServiceDefinitionList as $methodAwareServiceDefinition) {
180+
$methodAwareServiceDefinition->addMethodCall(
181+
'addJsonRpcMethod',
182+
[$methodName, new Reference($jsonRpcMethodServiceId)]
183+
);
184+
}
185+
}
186+
187+
/**
188+
* @param ContainerBuilder $container
189+
*
190+
* @return array
191+
* @throws \ReflectionException
192+
*/
193+
private function findAndValidateMappingAwareDefinitionList(ContainerBuilder $container): array
194+
{
195+
$mappingAwareServiceDefinitionList = [];
196+
$methodAwareServiceIdList = array_keys($container->findTaggedServiceIds(self::JSONRPC_METHOD_AWARE_TAG));
197+
foreach ($methodAwareServiceIdList as $serviceId) {
198+
$definition = $container->getDefinition($serviceId);
199+
200+
$this->checkMethodAwareServiceIdList($definition, $serviceId, $container);
201+
202+
$mappingAwareServiceDefinitionList[$serviceId] = $definition;
203+
}
204+
205+
return $mappingAwareServiceDefinitionList;
206+
}
207+
208+
private function checkMethodAwareServiceIdList(
209+
Definition $definition,
210+
string $serviceId,
211+
ContainerBuilder $container
212+
) : void {
213+
$class = $container->getReflectionClass($definition->getClass());
214+
215+
if (null !== $class && !$class->implementsInterface(JsonRpcMethodAwareInterface::class)) {
216+
throw new LogicException(sprintf(
217+
'Service "%s" is taggued as JSON-RPC method aware but does not implement %s',
218+
$serviceId,
219+
JsonRpcMethodAwareInterface::class
220+
));
221+
}
222+
}
137223
}

0 commit comments

Comments
 (0)