Skip to content

Commit 7c9f7b3

Browse files
authored
Add symfony extension (#2)
1 parent 682c63d commit 7c9f7b3

File tree

15 files changed

+1022
-5
lines changed

15 files changed

+1022
-5
lines changed

composer.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@
2828
},
2929
"require": {
3030
"php": ">7.0",
31-
"yoanm/jsonrpc-server-sdk": "^1.0",
32-
"symfony/http-foundation": "^3.0 || ^4.0"
31+
"yoanm/jsonrpc-server-sdk": "^1.2",
32+
"symfony/http-foundation": "^3.0 || ^4.0",
33+
"symfony/dependency-injection": "^3.0 || ^4.0",
34+
"yoanm/jsonrpc-server-sdk-psr11-resolver": "^2.0"
3335
},
3436
"require-dev": {
3537
"behat/behat": "~3.0",
3638
"squizlabs/php_codesniffer": "3.*",
37-
"phpunit/phpunit": "^6.0"
39+
"phpunit/phpunit": "^6.0 || ^7.0",
40+
"matthiasnoback/symfony-dependency-injection-test": "^2.0 || ^3.0"
3841
}
3942
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
namespace Tests\Functional\BehatContext\App;
3+
4+
use Yoanm\JsonRpcServer\Domain\Model\JsonRpcMethodInterface;
5+
use Yoanm\JsonRpcServer\Domain\Model\MethodResolverInterface;
6+
7+
class CustomMethodResolver implements MethodResolverInterface
8+
{
9+
/** @var JsonRpcMethodInterface[] */
10+
private $methodList;
11+
12+
/**
13+
* {@inheritdoc}
14+
*/
15+
public function resolve(string $methodName)
16+
{
17+
if (!array_key_exists($methodName, $this->methodList)) {
18+
return null;
19+
}
20+
21+
return $this->methodList[$methodName];
22+
}
23+
24+
/**
25+
* @param JsonRpcMethodInterface $method
26+
* @param string $methodName
27+
*/
28+
public function addMethod(JsonRpcMethodInterface $method, string $methodName)
29+
{
30+
$this->methodList[$methodName] = $method;
31+
}
32+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
namespace Tests\Functional\BehatContext\App;
3+
4+
use Yoanm\JsonRpcServer\Domain\Model\JsonRpcMethodInterface;
5+
6+
class JsonRpcMethod implements JsonRpcMethodInterface
7+
{
8+
public function validateParams(array $paramList)
9+
{
10+
}
11+
12+
public function apply(array $paramList = null)
13+
{
14+
return 'OK';
15+
}
16+
}

features/bootstrap/FeatureContext.php

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,35 @@
22
namespace Tests\Functional\BehatContext;
33

44
use Behat\Behat\Context\Context;
5+
use Behat\Gherkin\Node\PyStringNode;
6+
use Behat\Gherkin\Node\TableNode;
7+
use PHPUnit\Framework\Assert;
8+
use Prophecy\Argument;
9+
use Prophecy\Prophet;
10+
use Symfony\Component\DependencyInjection\ContainerBuilder;
11+
use Symfony\Component\DependencyInjection\Definition;
12+
use Symfony\Component\HttpFoundation\Request;
13+
use Tests\Functional\BehatContext\App\CustomMethodResolver;
14+
use Tests\Functional\BehatContext\App\JsonRpcMethod;
15+
use Yoanm\JsonRpcServer\Infra\Endpoint\JsonRpcEndpoint;
16+
use Yoanm\SymfonyJsonRpcHttpServer\Infra\Endpoint\JsonRpcHttpEndpoint;
17+
use Yoanm\SymfonyJsonRpcHttpServer\Infra\Symfony\DependencyInjection\JsonRpcHttpServerExtension;
518

619
/**
720
* Defines application features from the specific context.
821
*/
922
class FeatureContext implements Context
1023
{
24+
const CUSTOM_METHOD_RESOLVER_SERVICE_ID = 'custom-method-resolver-service';
25+
26+
/** @var JsonRpcHttpServerExtension */
27+
private $extension;
28+
/** @var Prophet */
29+
private $prophet;
30+
/** @var ContainerBuilder */
31+
private $containerBuilder;
32+
/** @var mixed */
33+
private $endpoint;
1134
/**
1235
* Initializes context.
1336
*
@@ -17,5 +40,190 @@ class FeatureContext implements Context
1740
*/
1841
public function __construct()
1942
{
43+
$this->prophet = new Prophet();
44+
}
45+
46+
/**
47+
* @Given I process the symfony extension
48+
*/
49+
public function givenIProcessTheSymfonyExtension()
50+
{
51+
(new JsonRpcHttpServerExtension())->load([], $this->getContainerBuilder());
52+
}
53+
54+
/**
55+
* @Given there is a public :serviceName JSON-RPC method service
56+
*/
57+
public function givenThereAJsonRpcMethodService($serviceId)
58+
{
59+
$this->getContainerBuilder()->setDefinition($serviceId, $this->createJsonRpcMethodDefinition());
60+
}
61+
62+
/**
63+
* @Given I inject my :methodName to :serviceName JSON-RPC mapping into default method resolver definition
64+
*/
65+
public function givenIInjectMyJsonRpcMethodIntoDefaultMethodResolverDefinition($methodName, $serviceName)
66+
{
67+
$this->injectJsonRpcMethodToDefaultResolverService($methodName, $serviceName, true);
68+
}
69+
70+
/**
71+
* @Given I inject my :methodName to :serviceName JSON-RPC mapping into default method resolver instance
72+
*/
73+
public function givenIInjectMyJsonRpcMethodIntoDefaultMethodResolverInstance($methodName, $serviceName)
74+
{
75+
$this->injectJsonRpcMethodToDefaultResolverService($methodName, $serviceName);
76+
}
77+
78+
/**
79+
* @Given I tag my custom method resolver service with :tagName
80+
*/
81+
public function givenITagMyCustomMethodResolverServiceWith($tagName)
82+
{
83+
$this->getContainerBuilder()->findDefinition(self::CUSTOM_METHOD_RESOLVER_SERVICE_ID)->addTag($tagName);
84+
}
85+
86+
/**
87+
* @Given I inject my :methodName JSON-RPC method into my custom method resolver instance
88+
*/
89+
public function givenIInjectMyJsonRpcMethodIntoMyCustomMethodResolverInstance($methodName)
90+
{
91+
$this->injectJsonRpcMethodToCustomResolverService($methodName, $this->createJsonRpcMethod());
92+
}
93+
94+
/**
95+
* @Given I inject my :methodName JSON-RPC method into my custom method resolver definition
96+
*/
97+
public function givenIInjectMyJsonRpcMethodIntoMyCustomMethodResolverDefinition($methodName)
98+
{
99+
$this->injectJsonRpcMethodToCustomResolverService($methodName, $this->createJsonRpcMethodDefinition());
100+
}
101+
102+
/**
103+
* @Given I have a JSON-RPC method service definition with :tagName tag and following tag attributes:
104+
*/
105+
public function givenITagMyJsonRpcMethodServiceWithTagAndFollowingAttributes(
106+
$tagName,
107+
PyStringNode $tagAttributeNode
108+
) {
109+
$definition = $this->createJsonRpcMethodDefinition()
110+
->addTag($tagName, json_decode($tagAttributeNode, true));
111+
$this->getContainerBuilder()->setDefinition(uniqid(), $definition);
112+
}
113+
114+
/**
115+
* @When I load endpoint from :serviceId service
116+
*/
117+
public function whenILoadEndpointFromService($serviceId)
118+
{
119+
$this->getContainerBuilder()->compile();
120+
$this->endpoint = $this->getContainerBuilder()->get($serviceId);
121+
}
122+
123+
/**
124+
* @Then endpoint should respond to following JSON-RPC methods:
125+
*/
126+
public function thenEndpointShouldResponseToFollowingJsonRpcMethods(TableNode $methodList)
127+
{
128+
Assert::assertInstanceOf(JsonRpcHttpEndpoint::class, $this->endpoint);
129+
$methodList = array_map('array_shift', $methodList->getRows());
130+
$this->assertEndpointRespondToCalls($this->endpoint, $methodList);
131+
}
132+
133+
/**
134+
* @param JsonRpcEndpoint $endpoint
135+
* @param array $methodNameList
136+
*/
137+
private function assertEndpointRespondToCalls(JsonRpcHttpEndpoint $endpoint, array $methodNameList)
138+
{
139+
foreach ($methodNameList as $methodName) {
140+
$requestId = uniqid();
141+
$requestContent = json_encode(
142+
[
143+
'jsonrpc' => '2.0',
144+
'id' => $requestId,
145+
'method' => $methodName
146+
]
147+
);
148+
$request = new Request([], [], [], [], [], [], $requestContent);
149+
$request->setMethod(Request::METHOD_POST);
150+
Assert::assertSame(
151+
json_encode(
152+
[
153+
'jsonrpc' => '2.0',
154+
'id' => $requestId,
155+
'result' => 'OK'
156+
]
157+
),
158+
$endpoint->index($request)->getContent()
159+
);
160+
}
161+
}
162+
163+
/**
164+
* @return JsonRpcMethod
165+
*/
166+
private function createJsonRpcMethod()
167+
{
168+
return new JsonRpcMethod();
169+
}
170+
171+
/**
172+
* @return Definition
173+
*/
174+
private function createJsonRpcMethodDefinition()
175+
{
176+
return (new Definition(JsonRpcMethod::class))->setPrivate(false);
177+
}
178+
179+
/**
180+
* @param string $methodName
181+
* @param string $methodServiceId
182+
* @param bool|false $isDefinition
183+
*/
184+
private function injectJsonRpcMethodToDefaultResolverService($methodName, $methodServiceId, $isDefinition = false)
185+
{
186+
$resolverServiceId = JsonRpcHttpServerExtension::SERVICE_NAME_RESOLVER_SERVICE_NAME;
187+
if (true === $isDefinition) {
188+
$this->getContainerBuilder()
189+
->getDefinition($resolverServiceId)
190+
->addMethodCall('addMethodMapping', [$methodName, $methodServiceId]);
191+
} else {
192+
$this->getContainerBuilder()
193+
->get($resolverServiceId)
194+
->addMethodMapping($methodName, $methodServiceId);
195+
}
196+
}
197+
198+
/**
199+
* @param string $methodName
200+
* @param JsonRpcMethod|Definition $method
201+
*/
202+
private function injectJsonRpcMethodToCustomResolverService($methodName, $method)
203+
{
204+
$resolverServiceId = self::CUSTOM_METHOD_RESOLVER_SERVICE_ID;
205+
if ($method instanceof Definition) {
206+
$this->getContainerBuilder()
207+
->getDefinition($resolverServiceId)
208+
->addMethodCall('addMethod', [$method, $methodName]);
209+
} else {
210+
$this->getContainerBuilder()
211+
->get($resolverServiceId)
212+
->addMethod($method, $methodName);
213+
}
214+
}
215+
216+
/**
217+
* @return ContainerBuilder
218+
*/
219+
private function getContainerBuilder()
220+
{
221+
if (!$this->containerBuilder) {
222+
$this->containerBuilder = new ContainerBuilder();
223+
// Add definition of custom resolver (without tags)
224+
$customResolverDefinition = (new Definition(CustomMethodResolver::class))->setPrivate(false);
225+
$this->containerBuilder->setDefinition(self::CUSTOM_METHOD_RESOLVER_SERVICE_ID, $customResolverDefinition);
226+
}
227+
return $this->containerBuilder;
20228
}
21229
}

features/symfony-extension.feature

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
@symfony-extension
2+
Feature: Symfony extension
3+
4+
Scenario: An endpoint should be quickly usable
5+
Given I process the symfony extension
6+
And there is a public "my_service.method.method_name" JSON-RPC method service
7+
And there is a public "my_service.method.another" JSON-RPC method service
8+
And there is a public "my_service.method.dummy" JSON-RPC method service
9+
When I load endpoint from "yoanm.jsonrpc_http_server.endpoint" service
10+
And I inject my "my-method-name" to "my_service.method.method_name" JSON-RPC mapping into default method resolver instance
11+
# Bind service a second time
12+
And I inject my "my-method-alias" to "my_service.method.method_name" JSON-RPC mapping into default method resolver instance
13+
And I inject my "an-another-method" to "my_service.method.another" JSON-RPC mapping into default method resolver instance
14+
And I inject my "getDummy" to "my_service.method.dummy" JSON-RPC mapping into default method resolver instance
15+
Then endpoint should respond to following JSON-RPC methods:
16+
| getDummy |
17+
| my-method-name |
18+
| an-another-method |
19+
| my-method-alias |
20+
21+
Scenario: An endpoint should be quickly usable also by using container injection
22+
Given I process the symfony extension
23+
And there is a public "my_service.method.method_name" JSON-RPC method service
24+
And there is a public "my_service.method.another" JSON-RPC method service
25+
And there is a public "my_service.method.dummy" JSON-RPC method service
26+
And I inject my "my-method-name" to "my_service.method.method_name" JSON-RPC mapping into default method resolver definition
27+
# Bind service a second time
28+
And I inject my "my-method-alias" to "my_service.method.method_name" JSON-RPC mapping into default method resolver definition
29+
And I inject my "an-another-method" to "my_service.method.another" JSON-RPC mapping into default method resolver definition
30+
And I inject my "getDummy" to "my_service.method.dummy" JSON-RPC mapping into default method resolver definition
31+
When I load endpoint from "yoanm.jsonrpc_http_server.endpoint" service
32+
Then endpoint should respond to following JSON-RPC methods:
33+
| getDummy |
34+
| my-method-name |
35+
| an-another-method |
36+
| my-method-alias |
37+
38+
@symfony-method-resolver-tag
39+
Scenario: Use a custom method resolver
40+
Given I tag my custom method resolver service with "yoanm.jsonrpc_http_server.method_resolver"
41+
And I process the symfony extension
42+
When I load endpoint from "yoanm.jsonrpc_http_server.endpoint" service
43+
And I inject my "doSomething" JSON-RPC method into my custom method resolver instance
44+
And I inject my "doAnotherThing" JSON-RPC method into my custom method resolver instance
45+
And I inject my "doALastThing" JSON-RPC method into my custom method resolver instance
46+
Then endpoint should respond to following JSON-RPC methods:
47+
| doAnotherThing |
48+
| doALastThing |
49+
| doSomething |
50+
51+
@symfony-method-resolver-tag
52+
Scenario: Use a custom method resolver with json-rpc methods autoloading
53+
Given I tag my custom method resolver service with "yoanm.jsonrpc_http_server.method_resolver"
54+
And I process the symfony extension
55+
And I inject my "doSomething" JSON-RPC method into my custom method resolver definition
56+
And I inject my "doAnotherThing" JSON-RPC method into my custom method resolver definition
57+
And I inject my "doALastThing" JSON-RPC method into my custom method resolver definition
58+
When I load endpoint from "yoanm.jsonrpc_http_server.endpoint" service
59+
Then endpoint should respond to following JSON-RPC methods:
60+
| doAnotherThing |
61+
| doALastThing |
62+
| doSomething |
63+
64+
@symfony-jsonrpc-method-tag
65+
Scenario: Define json-rpc method with tags
66+
Given I have a JSON-RPC method service definition with "yoanm.jsonrpc_http_server.jsonrpc_method" tag and following tag attributes:
67+
"""
68+
{"method": "my-method-name"}
69+
"""
70+
And I have a JSON-RPC method service definition with "yoanm.jsonrpc_http_server.jsonrpc_method" tag and following tag attributes:
71+
"""
72+
{"method": "an-another-method"}
73+
"""
74+
And I have a JSON-RPC method service definition with "yoanm.jsonrpc_http_server.jsonrpc_method" tag and following tag attributes:
75+
"""
76+
{"method": "getDummy"}
77+
"""
78+
And I process the symfony extension
79+
When I load endpoint from "yoanm.jsonrpc_http_server.endpoint" service
80+
Then endpoint should respond to following JSON-RPC methods:
81+
| getDummy |
82+
| my-method-name |
83+
| an-another-method |

src/.gitkeep

Whitespace-only changes.

src/Infra/Endpoint/JsonRpcHttpEndpoint.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,19 @@ class JsonRpcHttpEndpoint
1313
/** @var SdkJsonRpcEndpoint */
1414
private $sdkEndpoint;
1515

16+
/**
17+
* @param SDKJsonRpcEndpoint $sdkEndpoint
18+
*/
1619
public function __construct(SDKJsonRpcEndpoint $sdkEndpoint)
1720
{
1821
$this->sdkEndpoint = $sdkEndpoint;
1922
}
2023

24+
/**
25+
* @param Request $request
26+
*
27+
* @return Response
28+
*/
2129
public function index(Request $request) : Response
2230
{
2331
if (Request::METHOD_POST !== $request->getMethod()) {

0 commit comments

Comments
 (0)