Skip to content

Commit 63ad2d7

Browse files
committed
feat: add Controller Request Handler and routing contracts
1 parent 3e27d99 commit 63ad2d7

9 files changed

Lines changed: 156 additions & 50 deletions

src/AbstractKernel.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Psr\Http\Message\ResponseInterface;
2727
use Psr\Http\Message\ServerRequestInterface;
2828
use Psr\Http\Message\StreamInterface;
29+
use Psr\Http\Server\RequestHandlerInterface;
2930

3031
abstract class AbstractKernel implements KernelInterface
3132
{
@@ -118,24 +119,29 @@ public function getDispatchConfig(): DispatchConfigInterface
118119
*
119120
* @param ServerRequestInterface $request
120121
* @param StreamInterface $stream
122+
* @param RequestHandlerInterface $finalHandler
121123
* @param array $middlewares
122124
* @return ResponseInterface
123125
* @throws \ReflectionException
124126
*/
125127
protected function initRequestHandler(
126128
ServerRequestInterface $request,
127129
StreamInterface $stream,
130+
RequestHandlerInterface $finalHandler,
128131
array $middlewares = []
129-
) : ResponseInterface {
130-
$factory = new ResponseFactory($stream);
132+
): ResponseInterface {
133+
131134
$this->bindInterfaces([
132-
"ContainerInterface" => $this->container, "RequestInterface" => $request,
133-
"ServerRequestInterface" => $request, "StreamInterface" => $stream,
135+
"ContainerInterface" => $this->container,
136+
"RequestInterface" => $request,
137+
"ServerRequestInterface" => $request,
138+
"StreamInterface" => $stream,
134139
]);
140+
135141
$middlewares = array_merge($this->userMiddlewares, $middlewares);
136-
$handler = new RequestHandler($middlewares, $factory);
142+
$handler = new RequestHandler($middlewares, $finalHandler);
137143
$response = $handler->handle($request);
138-
$this->bindInterfaces(["ResponseInterface" => $response]);
144+
139145
return $response;
140146
}
141147

src/Contracts/DispatchConfigInterface.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace MaplePHP\Emitron\Contracts;
44

55
use Exception;
6-
use MaplePHP\Unitary\Interfaces\RouterDispatchInterface;
6+
use MaplePHP\Unitary\Interfaces\RouterDispatchInterface as UnitaryRouterDispatchInterface;
77

88
interface DispatchConfigInterface
99
{
@@ -35,9 +35,9 @@ public function setProps(array $props): self;
3535
/**
3636
* Get current exit code as int or null if not set
3737
*
38-
* @return RouterDispatchInterface
38+
* @return RouterDispatchInterface|UnitaryRouterDispatchInterface
3939
*/
40-
public function getRouter(): RouterDispatchInterface;
40+
public function getRouter(): RouterDispatchInterface|UnitaryRouterDispatchInterface;
4141

4242
/**
4343
* Add exit after execution of the app has been completed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace MaplePHP\Emitron\Contracts;
4+
5+
interface RouterDispatchInterface
6+
{
7+
/**
8+
* Dispatch matched router
9+
*
10+
* @param callable $call
11+
* @return bool
12+
*/
13+
public function dispatch(callable $call): bool;
14+
}

src/Contracts/RouterInterface.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace MaplePHP\Emitron\Contracts;
4+
5+
interface RouterInterface extends RouterDispatchInterface
6+
{
7+
/**
8+
* Map one or more needles to controller
9+
*/
10+
public function map(string|array $methods, string $pattern, array $controller): void;
11+
}

src/ControllerRequestHandler.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
namespace MaplePHP\Emitron;
3+
4+
5+
use MaplePHP\Container\Reflection;
6+
use Psr\Http\Message\ResponseFactoryInterface;
7+
use Psr\Http\Message\ResponseInterface;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
use Psr\Http\Server\RequestHandlerInterface;
10+
11+
final class ControllerRequestHandler implements RequestHandlerInterface
12+
{
13+
public function __construct(
14+
private readonly ResponseFactoryInterface $factory,
15+
private readonly array $controller, // [$class, $method] or [$class]
16+
) {}
17+
18+
public function handle(ServerRequestInterface $request): ResponseInterface
19+
{
20+
$response = $this->factory->createResponse();
21+
$this->appendInterfaces([
22+
"ResponseInterface" => $response,
23+
]);
24+
25+
$controller = $this->controller;
26+
if (!isset($controller[1])) {
27+
$controller[1] = '__invoke';
28+
}
29+
30+
if (count($controller) !== 2) {
31+
$response->getBody()->write("ERROR: Invalid controller handler.\n");
32+
return $response;
33+
}
34+
35+
[$class, $method] = $controller;
36+
37+
if (!method_exists($class, $method)) {
38+
$response->getBody()->write("ERROR: Could not load Controller {$class}::{$method}().\n");
39+
return $response;
40+
}
41+
42+
// Your DI wiring
43+
$reflect = new Reflection($class);
44+
$classInst = $reflect->dependencyInjector();
45+
46+
47+
// This should INVOKE the method and return its result (ResponseInterface or something else)
48+
$result = $reflect->dependencyInjector($classInst, $method);
49+
50+
if ($result instanceof ResponseInterface) {
51+
return $result;
52+
}
53+
54+
// If controller didn’t return a response, you can decide a convention:
55+
// - treat it as “controller wrote to $response->getBody() somewhere”
56+
// - or treat non-response as error
57+
return $response;
58+
}
59+
60+
61+
protected function appendInterfaces(array $bindings)
62+
{
63+
Reflection::interfaceFactory(function (string $className) use ($bindings) {
64+
return $bindings[$className] ?? null;
65+
});
66+
}
67+
68+
}

src/DispatchConfig.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
use MaplePHP\Emitron\Configs\ConfigPropsFactory;
1919
use MaplePHP\Emitron\Contracts\ConfigPropsInterface;
2020
use MaplePHP\Emitron\Contracts\DispatchConfigInterface;
21-
use MaplePHP\Unitary\Interfaces\RouterDispatchInterface;
22-
use MaplePHP\Unitary\Interfaces\RouterInterface;
21+
use MaplePHP\Unitary\Interfaces\RouterDispatchInterface as UnitaryRouterDispatchInterface;
22+
use MaplePHP\Unitary\Interfaces\RouterInterface as UnitaryRouterInterface;
23+
use MaplePHP\Emitron\Contracts\RouterInterface;
24+
use MaplePHP\Emitron\Contracts\RouterDispatchInterface;
2325

2426
class DispatchConfig implements DispatchConfigInterface
2527
{
2628
private string $dir;
27-
private ?RouterInterface $router = null;
29+
private RouterInterface|UnitaryRouterInterface|null $router = null;
2830
protected ConfigPropsInterface $props;
2931

3032
/**
@@ -100,12 +102,12 @@ public function setProps(array $props): self
100102
/**
101103
* Get current exit code as int or null if not set
102104
*
103-
* @return RouterDispatchInterface
105+
* @return UnitaryRouterInterface|RouterDispatchInterface
104106
*/
105-
public function getRouter(): RouterDispatchInterface
107+
public function getRouter(): UnitaryRouterInterface|RouterDispatchInterface
106108
{
107109
if ($this->router === null) {
108-
return new class () implements RouterDispatchInterface {
110+
return new class () implements UnitaryRouterDispatchInterface, RouterDispatchInterface {
109111
public function dispatch(callable $call): bool
110112
{
111113
$call(['handler' => []], [], [], '');
@@ -127,7 +129,7 @@ public function setRouter(callable $call): self
127129
{
128130
$inst = clone $this;
129131
$inst->router = $call($this->dir);
130-
if (!($inst->router instanceof RouterInterface)) {
132+
if (!($inst->router instanceof RouterInterface || $inst->router instanceof UnitaryRouterInterface)) {
131133
throw new Exception('Router must implement RouterInterface and "return" a it!');
132134
}
133135
return $inst;

src/Emitters/HttpEmitter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ class HttpEmitter implements EmitterInterface
2222
*/
2323
public function emit(ResponseInterface $response, ServerRequestInterface $request): void
2424
{
25+
2526
$body = $response->getBody();
2627
$status = $response->getStatusCode();
2728
$method = strtoupper($request->getMethod());
2829
$skipBody = in_array($status, [204, 304]) || ($status >= 100 && $status < 200) || $method === 'HEAD';
2930

3031
// Default to 204 No Content if nobody was written
31-
if (!$response->getBody()->getSize() && $this->isSuccessfulResponse()) {
32+
if (!$response->getBody()->getSize() && $this->isSuccessfulResponse($response)) {
3233
$response = $response->withStatus(204);
3334
$response->getBody()->write("No Content\n");
3435
}

src/Kernel.php

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
namespace MaplePHP\Emitron;
1616

1717
use MaplePHP\Container\Reflection;
18+
use MaplePHP\Http\ResponseFactory;
1819
use Psr\Http\Message\ResponseInterface;
1920
use Psr\Http\Message\ServerRequestInterface;
2021
use Psr\Http\Message\StreamInterface;
@@ -33,34 +34,41 @@ public function run(ServerRequestInterface $request, ?StreamInterface $stream =
3334
{
3435
$this->dispatchConfig->getRouter()->dispatch(function ($data, $args, $middlewares) use ($request, $stream) {
3536

37+
[$data, $args, $middlewares] = $this->reMap($data, $args, $middlewares);
38+
3639
if (!isset($data['handler'])) {
37-
throw new InvalidArgumentException("The router dispatch method arg 1 is missing the 'handler' key.");
40+
throw new InvalidArgumentException("Missing 'handler' key.");
3841
}
3942

4043
$this->container->set("request", $request);
4144
$this->container->set("args", $args);
4245
$this->container->set("configuration", $this->getDispatchConfig());
4346

44-
$response = $this->initRequestHandler($request, $this->getBody($stream), $middlewares);
47+
$bodyStream = $this->getBody($stream);
48+
$factory = new ResponseFactory($bodyStream);
4549

46-
$controller = $data['handler'];
47-
if (!isset($controller[1])) {
48-
$controller[1] = '__invoke';
49-
}
50-
if (count($controller) === 2) {
51-
[$class, $method] = $controller;
52-
if (method_exists($class, $method)) {
53-
$reflect = new Reflection($class);
54-
$classInst = $reflect->dependencyInjector();
55-
// Can replace the active Response instance through Command instance
56-
$hasNewResponse = $reflect->dependencyInjector($classInst, $method);
57-
$response = ($hasNewResponse instanceof ResponseInterface) ? $hasNewResponse : $response;
50+
$finalHandler = new ControllerRequestHandler($factory, $data['handler']);
51+
52+
$response = $this->initRequestHandler(
53+
request: $request,
54+
stream: $bodyStream,
55+
finalHandler: $finalHandler,
56+
middlewares: $middlewares
57+
);
5858

59-
} else {
60-
$response->getBody()->write("\nERROR: Could not load Controller class {$class} and method {$method}()\n");
61-
}
62-
}
6359
$this->createEmitter()->emit($response, $request);
6460
});
6561
}
62+
63+
64+
function reMap($data, $args, $middlewares) {
65+
66+
if(isset($data[1]) && $middlewares instanceof ServerRequestInterface) {
67+
$item = $data[1];
68+
return [
69+
["handler" => $item['controller']], $_REQUEST, ($item['data'] ?? [])
70+
];
71+
}
72+
return [$data, $args, $middlewares];
73+
}
6674
}

src/RequestHandler.php

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,30 @@
33
namespace MaplePHP\Emitron;
44

55
use MaplePHP\Container\Reflection;
6-
use Psr\Http\Message\ResponseFactoryInterface;
76
use Psr\Http\Message\ResponseInterface;
87
use Psr\Http\Message\ServerRequestInterface;
8+
use Psr\Http\Server\MiddlewareInterface;
99
use Psr\Http\Server\RequestHandlerInterface;
1010

11-
class RequestHandler implements RequestHandlerInterface
11+
final class RequestHandler implements RequestHandlerInterface
1212
{
1313
private int $index = 0;
14+
15+
/** @var list<MiddlewareInterface> */
1416
private array $middlewareQueue;
15-
private ResponseFactoryInterface $responseSource;
1617

17-
public function __construct(array $middlewares, ResponseFactoryInterface $responseFactory)
18-
{
18+
public function __construct(
19+
array $middlewares,
20+
private readonly RequestHandlerInterface $finalHandler
21+
) {
1922
$this->middlewareQueue = $middlewares;
20-
$this->responseSource = $responseFactory;
2123
}
2224

23-
/**
24-
* Process middlewares and middleware that is a string class then it will use the
25-
* dependency injector in the constructor.
26-
*
27-
* @param ServerRequestInterface $request
28-
* @return ResponseInterface
29-
* @throws \ReflectionException
30-
*/
3125
public function handle(ServerRequestInterface $request): ResponseInterface
3226
{
27+
// End of chain -> call controller (or whatever final handler you set)
3328
if (!isset($this->middlewareQueue[$this->index])) {
34-
return $this->responseSource->createResponse(200);
29+
return $this->finalHandler->handle($request);
3530
}
3631

3732
$middleware = $this->middlewareQueue[$this->index];
@@ -41,6 +36,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface
4136
$reflect = new Reflection($middleware);
4237
$middleware = $reflect->dependencyInjector();
4338
}
39+
4440
return $middleware->process($request, $this);
4541
}
4642
}

0 commit comments

Comments
 (0)