Skip to content

Commit

Permalink
Instantiate request handlers on demand (#12)
Browse files Browse the repository at this point in the history
* feat: instantiate request handlers on demand (#11)
  • Loading branch information
chris-doehring authored Nov 29, 2021
1 parent 53c3450 commit 9eee240
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v3.1.0 - 2021-11-XX

- add ability to register request handlers as callable to instantiate them on demand to reduce memory load

## v3.0.0 - 2021-10-04

- require `dogado/json-api-common:^3.0`
Expand Down
18 changes: 9 additions & 9 deletions docs/01-json-api-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

`Dogado\JsonApi\Server\JsonApiServer`:

| Method | Return Type | Description |
|-------------------------------------------------------------|------------------------|----------------------------------------------------------------|
| addHandler(string $type, RequestHandlerInterface $handler) | void | Adds a request handler |
| createRequestBody(?string $requestBody) | DocumentInterface/null | Creates a document from the given string |
| handleRequest(RequestInterface $request) | ResponseInterface | Handles a request to generate a response |
| createResponseBody(ResponseInterface $response) | string | Creates the (http) response body for a given json api response |
| handleException(\Throwable $throwable, bool $debug = false) | ResponseInterface | Creates a response for an exception |
| Method | Return Type | Description
|---------------------------------------------------------------------|------------------------|----------------------------------------------------------------
| 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.
| createRequestBody(?string $requestBody) | DocumentInterface/null | Creates a document from the given string
| handleRequest(RequestInterface $request) | ResponseInterface | Handles a request to generate a response
| createResponseBody(ResponseInterface $response) | string | Creates the (http) response body for a given json api response
| handleException(\Throwable $throwable, bool $debug = false) | ResponseInterface | Creates a response for an exception

## Table of contents

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

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

// create the json api request
$request = new Request(
Expand Down
26 changes: 23 additions & 3 deletions src/JsonApiServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Dogado\JsonApi\Serializer\Serializer;
use Dogado\JsonApi\Server\Decorator\ResponseDecorator;
use Dogado\JsonApi\Server\RequestHandler\RequestHandlerInterface;
use RuntimeException;
use Throwable;

class JsonApiServer
Expand All @@ -31,8 +32,11 @@ class JsonApiServer
protected DocumentSerializerInterface $serializer;
protected ResponseDecorator $responseDecorator;

/** @var RequestHandlerInterface[]|callable[] */
private array $handlers = [];

/** @var RequestHandlerInterface[] */
protected array $handlers = [];
private array $handlerInstances = [];

public function __construct(
?DocumentDeserializerInterface $deserializer = null,
Expand All @@ -44,22 +48,38 @@ public function __construct(
$this->responseDecorator = $responseDecorator ?? new ResponseDecorator();
}

public function addHandler(string $type, RequestHandlerInterface $handler): self
public function addHandler(string $type, RequestHandlerInterface|callable $handler): self
{
$this->handlers[$type] = $handler;
return $this;
}

/**
* @throws UnsupportedTypeException
* @throws RuntimeException
*/
private function getHandler(string $type): RequestHandlerInterface
{
if (array_key_exists($type, $this->handlerInstances)) {
return $this->handlerInstances[$type];
}

if (!array_key_exists($type, $this->handlers)) {
throw new UnsupportedTypeException($type);
}

return $this->handlers[$type];
$handler = $this->handlers[$type];
if (is_callable($handler)) {
$handler = $handler();

if (!$handler instanceof RequestHandlerInterface) {
throw new RuntimeException(
'Unable to initialize request handler for type "' . $type . '" by given callable.'
);
}
}

return ($this->handlerInstances[$type] = $handler);
}

/**
Expand Down
44 changes: 43 additions & 1 deletion tests/JsonApiServerTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace Dogado\JsonApi\Server\Tests;

use Dogado\JsonApi\Exception\DocumentSerializerException;
Expand All @@ -21,6 +22,7 @@
use Generator;
use GuzzleHttp\Psr7\Uri;
use PHPUnit\Framework\MockObject\MockObject;
use RuntimeException;
use Throwable;

class JsonApiServerTest extends TestCase
Expand All @@ -34,7 +36,7 @@ class JsonApiServerTest extends TestCase
/** @var ResponseDecorator|MockObject */
protected $responseDecorator;

/** @var RequestHandlerInterface|MockObject */
/** @var RequestHandlerInterface|callable|MockObject */
protected $requestHandler;

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

public function testCallableHandlerAssociation(): void
{
$type = $this->faker()->slug();
$uri = sprintf(
'http://%s/%s/%s',
$this->faker()->domainName(),
$type,
$this->faker()->numberBetween()
);

$this->server->addHandler($type, fn () => $this->requestHandler);

$request = new Request('GET', new Uri($uri));
$response = $this->createMock(ResponseInterface::class);
$this->requestHandler->expects(self::exactly(2))->method('fetchResource')->with($request)
->willReturn($response);

$this->responseDecorator->expects(self::exactly(2))->method('handle')->with($request, $response);
$this->assertEquals($response, $this->server->handleRequest($request));
// execute a second time to test caching behaviour
$this->assertEquals($response, $this->server->handleRequest($request));
}

public function testUnknownType(): void
{
$type = $this->faker()->slug();
Expand All @@ -166,6 +191,23 @@ public function testUnknownType(): void
$this->server->handleRequest($request);
}

public function testInvalidCallableHandlerInitialization(): void
{
$type = $this->faker()->slug();
$uri = sprintf(
'http://%s/%s/%s',
$this->faker()->domainName(),
$type,
$this->faker()->numberBetween()
);
$request = new Request('GET', new Uri($uri));
$this->server->addHandler($type, fn () => $this->faker()->text());

$this->expectException(RuntimeException::class);
$this->expectExceptionMessageMatches('/Unable to initialize.*by given callable/');
$this->server->handleRequest($request);
}

public function testInvalidContentType(): void
{
$type = $this->faker()->slug();
Expand Down

0 comments on commit 9eee240

Please sign in to comment.