Skip to content

Commit 9eee240

Browse files
authoredNov 29, 2021
Instantiate request handlers on demand (#12)
* feat: instantiate request handlers on demand (#11)
1 parent 53c3450 commit 9eee240

File tree

4 files changed

+79
-13
lines changed

4 files changed

+79
-13
lines changed
 

‎CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## v3.1.0 - 2021-11-XX
4+
5+
- add ability to register request handlers as callable to instantiate them on demand to reduce memory load
6+
37
## v3.0.0 - 2021-10-04
48

59
- require `dogado/json-api-common:^3.0`

‎docs/01-json-api-server.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
`Dogado\JsonApi\Server\JsonApiServer`:
66

7-
| Method | Return Type | Description |
8-
|-------------------------------------------------------------|------------------------|----------------------------------------------------------------|
9-
| addHandler(string $type, RequestHandlerInterface $handler) | void | Adds a request handler |
10-
| createRequestBody(?string $requestBody) | DocumentInterface/null | Creates a document from the given string |
11-
| handleRequest(RequestInterface $request) | ResponseInterface | Handles a request to generate a response |
12-
| createResponseBody(ResponseInterface $response) | string | Creates the (http) response body for a given json api response |
13-
| handleException(\Throwable $throwable, bool $debug = false) | ResponseInterface | Creates a response for an exception |
7+
| Method | Return Type | Description
8+
|---------------------------------------------------------------------|------------------------|----------------------------------------------------------------
9+
| addHandler(string $type, RequestHandlerInterface\|callable $handler) | void | Adds a request handler. Can be a callable to instantiate request handlers on demand to reduce memory load.
10+
| createRequestBody(?string $requestBody) | DocumentInterface/null | Creates a document from the given string
11+
| handleRequest(RequestInterface $request) | ResponseInterface | Handles a request to generate a response
12+
| createResponseBody(ResponseInterface $response) | string | Creates the (http) response body for a given json api response
13+
| handleException(\Throwable $throwable, bool $debug = false) | ResponseInterface | Creates a response for an exception
1414

1515
## Table of contents
1616

@@ -45,8 +45,8 @@ use Dogado\JsonApi\Model\Request\Request;
4545
// create the server
4646
$jsonApi = new JsonApiServer(new Deserializer(), new Serializer());
4747

48-
// add your request handlers to the registry of the json api server
49-
$jsonApi->addHandler('customResources', new YourCustomRequestHandler());
48+
// Add your request handlers to the registry of the json api server. You can either pass an instance or a callable.
49+
$jsonApi->addHandler('customResources', fn () => new YourCustomRequestHandler());
5050

5151
// create the json api request
5252
$request = new Request(

‎src/JsonApiServer.php

+23-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Dogado\JsonApi\Serializer\Serializer;
2222
use Dogado\JsonApi\Server\Decorator\ResponseDecorator;
2323
use Dogado\JsonApi\Server\RequestHandler\RequestHandlerInterface;
24+
use RuntimeException;
2425
use Throwable;
2526

2627
class JsonApiServer
@@ -31,8 +32,11 @@ class JsonApiServer
3132
protected DocumentSerializerInterface $serializer;
3233
protected ResponseDecorator $responseDecorator;
3334

35+
/** @var RequestHandlerInterface[]|callable[] */
36+
private array $handlers = [];
37+
3438
/** @var RequestHandlerInterface[] */
35-
protected array $handlers = [];
39+
private array $handlerInstances = [];
3640

3741
public function __construct(
3842
?DocumentDeserializerInterface $deserializer = null,
@@ -44,22 +48,38 @@ public function __construct(
4448
$this->responseDecorator = $responseDecorator ?? new ResponseDecorator();
4549
}
4650

47-
public function addHandler(string $type, RequestHandlerInterface $handler): self
51+
public function addHandler(string $type, RequestHandlerInterface|callable $handler): self
4852
{
4953
$this->handlers[$type] = $handler;
5054
return $this;
5155
}
5256

5357
/**
5458
* @throws UnsupportedTypeException
59+
* @throws RuntimeException
5560
*/
5661
private function getHandler(string $type): RequestHandlerInterface
5762
{
63+
if (array_key_exists($type, $this->handlerInstances)) {
64+
return $this->handlerInstances[$type];
65+
}
66+
5867
if (!array_key_exists($type, $this->handlers)) {
5968
throw new UnsupportedTypeException($type);
6069
}
6170

62-
return $this->handlers[$type];
71+
$handler = $this->handlers[$type];
72+
if (is_callable($handler)) {
73+
$handler = $handler();
74+
75+
if (!$handler instanceof RequestHandlerInterface) {
76+
throw new RuntimeException(
77+
'Unable to initialize request handler for type "' . $type . '" by given callable.'
78+
);
79+
}
80+
}
81+
82+
return ($this->handlerInstances[$type] = $handler);
6383
}
6484

6585
/**

‎tests/JsonApiServerTest.php

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
namespace Dogado\JsonApi\Server\Tests;
34

45
use Dogado\JsonApi\Exception\DocumentSerializerException;
@@ -21,6 +22,7 @@
2122
use Generator;
2223
use GuzzleHttp\Psr7\Uri;
2324
use PHPUnit\Framework\MockObject\MockObject;
25+
use RuntimeException;
2426
use Throwable;
2527

2628
class JsonApiServerTest extends TestCase
@@ -34,7 +36,7 @@ class JsonApiServerTest extends TestCase
3436
/** @var ResponseDecorator|MockObject */
3537
protected $responseDecorator;
3638

37-
/** @var RequestHandlerInterface|MockObject */
39+
/** @var RequestHandlerInterface|callable|MockObject */
3840
protected $requestHandler;
3941

4042
protected JsonApiServer $server;
@@ -152,6 +154,29 @@ public function handlerAssociationScenarios(): Generator
152154
yield ['DELETE', $type, $uri, 'deleteResource'];
153155
}
154156

157+
public function testCallableHandlerAssociation(): void
158+
{
159+
$type = $this->faker()->slug();
160+
$uri = sprintf(
161+
'http://%s/%s/%s',
162+
$this->faker()->domainName(),
163+
$type,
164+
$this->faker()->numberBetween()
165+
);
166+
167+
$this->server->addHandler($type, fn () => $this->requestHandler);
168+
169+
$request = new Request('GET', new Uri($uri));
170+
$response = $this->createMock(ResponseInterface::class);
171+
$this->requestHandler->expects(self::exactly(2))->method('fetchResource')->with($request)
172+
->willReturn($response);
173+
174+
$this->responseDecorator->expects(self::exactly(2))->method('handle')->with($request, $response);
175+
$this->assertEquals($response, $this->server->handleRequest($request));
176+
// execute a second time to test caching behaviour
177+
$this->assertEquals($response, $this->server->handleRequest($request));
178+
}
179+
155180
public function testUnknownType(): void
156181
{
157182
$type = $this->faker()->slug();
@@ -166,6 +191,23 @@ public function testUnknownType(): void
166191
$this->server->handleRequest($request);
167192
}
168193

194+
public function testInvalidCallableHandlerInitialization(): void
195+
{
196+
$type = $this->faker()->slug();
197+
$uri = sprintf(
198+
'http://%s/%s/%s',
199+
$this->faker()->domainName(),
200+
$type,
201+
$this->faker()->numberBetween()
202+
);
203+
$request = new Request('GET', new Uri($uri));
204+
$this->server->addHandler($type, fn () => $this->faker()->text());
205+
206+
$this->expectException(RuntimeException::class);
207+
$this->expectExceptionMessageMatches('/Unable to initialize.*by given callable/');
208+
$this->server->handleRequest($request);
209+
}
210+
169211
public function testInvalidContentType(): void
170212
{
171213
$type = $this->faker()->slug();

0 commit comments

Comments
 (0)