Skip to content

Commit c58af99

Browse files
committed
feat: add controller support with mixed respone
1 parent a49fe36 commit c58af99

8 files changed

Lines changed: 123 additions & 104 deletions

File tree

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,19 @@ composer require maplephp/emitron
3232
Emitron includes a robust request handler that executes PSR-15 middlewares in sequence, returning a fully PSR-7 compliant response.
3333

3434
```php
35-
use MaplePHP\Emitron\RequestHandler;use MaplePHP\Http\Environment;use MaplePHP\Http\ServerRequest;use MaplePHP\Http\Uri;
35+
use Psr\Http\Message\RequestInterface;
36+
use Psr\Http\Message\ResponseInterface;
37+
use MaplePHP\Emitron\RequestHandler;
38+
use MaplePHP\Emitron\Emitters\HttpEmitter;
39+
use MaplePHP\Http\Environment;
40+
use MaplePHP\Http\ServerRequest;
41+
use MaplePHP\Http\Uri;
42+
use MaplePHP\Emitron\Middlewares\{
43+
ContentLengthMiddleware,
44+
GzipMiddleware,
45+
HeadRequestMiddleware
46+
};
47+
use App\Controllers\MyController;
3648

3749
// Use MaplePHP HTTP library or any other PSR-7 implementation
3850
$env = new Environment();
@@ -48,8 +60,23 @@ $middlewares = [
4860
];
4961

5062
// Run the middleware stack
51-
$handler = new RequestHandler($middlewares, $factory);
63+
64+
65+
$factory = new ResponseFactory($bodyStream);
66+
// $finalHandler = new ControllerRequestHandler($factory, [MyController::class, "index"]);
67+
$finalHandler = new ControllerRequestHandler($factory, function(RequestInterface $request, ResponseInterface $response) {
68+
$response->getBody()->write("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
69+
return $response;
70+
});
71+
72+
$handler = new RequestHandler($middlewares, $finalHandler);
5273
$response = $handler->handle($request);
74+
75+
// Emit the execute headers and response correctly
76+
//$emit = new CliEmitter($response, $request);
77+
$emit = new HttpEmitter();
78+
$emit->emit($response, $request);
79+
5380
```
5481

5582
Each middleware conforms to `Psr\Http\Server\MiddlewareInterface`, allowing you to plug in your own or third-party middlewares with no additional setup.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace MaplePHP\Emitron\Contracts;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use Psr\Http\Message\ServerRequestInterface;
7+
8+
interface ControllerResponseInterface
9+
{
10+
/**
11+
* Controller response can be empty, string, array, object
12+
*
13+
* @param ServerRequestInterface $request
14+
* @return mixed
15+
*/
16+
public function handle(ServerRequestInterface $request, ResponseInterface $response): mixed;
17+
}

src/Contracts/KernelInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
namespace MaplePHP\Emitron\Contracts;
44

55
use Psr\Http\Message\ServerRequestInterface;
6+
use Psr\Http\Message\StreamInterface;
67

78
interface KernelInterface
89
{
910
/**
1011
* Run the emitter and init all routes, middlewares and configs
1112
*
1213
* @param ServerRequestInterface $request
14+
* @param StreamInterface|null $stream
1315
* @return void
1416
*/
15-
public function run(ServerRequestInterface $request): void;
17+
public function run(ServerRequestInterface $request, ?StreamInterface $stream): void;
1618

1719
}

src/ControllerRequestHandler.php

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
<?php
22
namespace MaplePHP\Emitron;
33

4-
5-
use FastRoute\Dispatcher;
64
use MaplePHP\Container\Reflection;
7-
use MaplePHP\Core\Router\RouterDispatcher;
5+
use MaplePHP\Http\StreamFactory;
86
use Psr\Http\Message\ResponseFactoryInterface;
97
use Psr\Http\Message\ResponseInterface;
108
use Psr\Http\Message\ServerRequestInterface;
9+
use Psr\Http\Message\StreamInterface;
1110
use Psr\Http\Server\RequestHandlerInterface;
1211

1312
final class ControllerRequestHandler implements RequestHandlerInterface
1413
{
1514
public function __construct(
1615
private readonly ResponseFactoryInterface $factory,
17-
private readonly array $controller, // [$class, $method] or [$class]
16+
private readonly array|\Closure $controller
1817
) {}
1918

2019
public function handle(ServerRequestInterface $request): ResponseInterface
@@ -26,6 +25,10 @@ public function handle(ServerRequestInterface $request): ResponseInterface
2625
]);
2726

2827
$controller = $this->controller;
28+
if(is_callable($controller)) {
29+
return $controller($request, $response);
30+
}
31+
2932
if (!isset($controller[1])) {
3033
$controller[1] = '__invoke';
3134
}
@@ -46,21 +49,62 @@ public function handle(ServerRequestInterface $request): ResponseInterface
4649
$reflect = new Reflection($class);
4750
$classInst = $reflect->dependencyInjector();
4851

49-
5052
// This should INVOKE the method and return its result (ResponseInterface or something else)
5153
$result = $reflect->dependencyInjector($classInst, $method);
5254

55+
return $this->createResponse($response, $result);
56+
}
57+
58+
59+
/**
60+
* Will create a PSR valid Response instance form mixed result
61+
*
62+
* @param ResponseInterface $response
63+
* @param mixed $result
64+
* @return ResponseInterface
65+
*/
66+
protected function createResponse(ResponseInterface $response, mixed $result): ResponseInterface
67+
{
5368
if ($result instanceof ResponseInterface) {
5469
return $result;
5570
}
5671

57-
// If controller didn’t return a response:
58-
// - treat it as “controller wrote to $response->getBody() somewhere”
59-
// - or treat non-response as error
72+
if($result instanceof StreamInterface) {
73+
return $response->withBody($result);
74+
}
75+
76+
if(is_array($result) || is_object($result)) {
77+
return $this->createStream($response, json_encode($result, JSON_UNESCAPED_UNICODE))
78+
->withHeader("Content-Type", "application/json");
79+
}
80+
81+
if(is_string($result) || is_numeric($result)) {
82+
return $this->createStream($response, $result);
83+
}
6084
return $response;
6185
}
6286

87+
/**
88+
* A helper method to create a new stream instance
89+
*
90+
* @param ResponseInterface $response
91+
* @param mixed $result
92+
* @return ResponseInterface
93+
*/
94+
protected function createStream(ResponseInterface $response, mixed $result): ResponseInterface
95+
{
96+
$streamFactory = new StreamFactory();
97+
$stream = $streamFactory->createStream($result);
98+
return $response->withBody($stream);
99+
100+
}
63101

102+
/**
103+
* Append interface helper method
104+
*
105+
* @param array $bindings
106+
* @return void
107+
*/
64108
protected function appendInterfaces(array $bindings)
65109
{
66110
Reflection::interfaceFactory(function (string $className) use ($bindings) {

src/Kernel.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
namespace MaplePHP\Emitron;
1616

17-
use FastRoute\Dispatcher;
18-
use MaplePHP\Core\Router\RouterDispatcher;
1917
use MaplePHP\Http\ResponseFactory;
2018
use Psr\Http\Message\ServerRequestInterface;
2119
use Psr\Http\Message\StreamInterface;
@@ -35,7 +33,7 @@ public function run(ServerRequestInterface $request, ?StreamInterface $stream =
3533

3634
$this->dispatchConfig->getRouter()->dispatch(function ($data, $args, $middlewares) use ($request, $stream) {
3735

38-
$dispatchCode = $data[0] ?? RouterDispatcher::FOUND;
36+
//$dispatchCode = $data[0] ?? RouterDispatcher::FOUND;
3937

4038
[$data, $args, $middlewares] = $this->reMap($data, $args, $middlewares);
4139

@@ -60,13 +58,15 @@ public function run(ServerRequestInterface $request, ?StreamInterface $stream =
6058
middlewares: $middlewares
6159
);
6260

61+
/*
6362
if ($dispatchCode === Dispatcher::NOT_FOUND) {
6463
$response = $response->withStatus(404);
6564
}
6665
6766
if ($dispatchCode === Dispatcher::METHOD_NOT_ALLOWED) {
6867
$response = $response->withStatus(405);
6968
}
69+
*/
7070

7171
$this->createEmitter()->emit($response, $request);
7272
});

src/Middlewares/OutputMiddleware.php

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/RequestHandler.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public function __construct(
2424

2525
public function handle(ServerRequestInterface $request): ResponseInterface
2626
{
27+
2728
// End of chain -> call controller (or whatever final handler you set)
2829
if (!isset($this->middlewareQueue[$this->index])) {
2930
return $this->finalHandler->handle($request);

tests/unitary-emitron.php

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,60 @@
11
<?php
22

3+
use MaplePHP\Emitron\ControllerRequestHandler;
34
use MaplePHP\Emitron\Emitters\HttpEmitter;
45
use MaplePHP\Emitron\Middlewares\ContentLengthMiddleware;
56
use MaplePHP\Emitron\Middlewares\GzipMiddleware;
67
use MaplePHP\Emitron\Middlewares\HeadRequestMiddleware;
7-
use MaplePHP\Emitron\Middlewares\OutputMiddleware;
88
use MaplePHP\Emitron\RequestHandler;
99
use MaplePHP\Http\Environment;
1010
use MaplePHP\Http\ResponseFactory;
1111
use MaplePHP\Http\ServerRequest;
1212
use MaplePHP\Http\Stream;
1313
use MaplePHP\Http\Uri;
1414
use MaplePHP\Unitary\{Config\TestConfig, Expect, TestCase};
15+
use Psr\Http\Message\RequestInterface;
16+
use Psr\Http\Message\ResponseInterface;
1517

1618
$config = TestConfig::make()->withName("emitron");
1719
group($config->withSubject("Testing middleware and emitter"), function (TestCase $case) {
1820

19-
$stream = new Stream(Stream::TEMP);
20-
$stream->write("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras eleifend ligula vel diam tincidunt finibus. In dapibus dictum lectus a malesuada.");
21+
22+
2123

2224
// It will reverse the order
2325
$middlewares = [
2426
new ContentLengthMiddleware(),
2527
new GzipMiddleware(),
26-
new OutputMiddleware($stream),
2728
new HeadRequestMiddleware(),
2829
];
2930

3031
$env = new Environment();
3132
$uri = new Uri($env->getUriParts());
3233
$request = new ServerRequest($uri, $env);
3334

34-
// Do not exist in CLI so need to mock it
35+
// This is something that is usually set by the browser
36+
// So this does not exist in CLI so need to mock it
3537
$request = $request->withHeader("Accept-Encoding", "gzip");
3638

37-
$factory = new ResponseFactory();
38-
$factory->createResponse();
39-
$handler = new RequestHandler($middlewares, $factory);
40-
$response = $handler->handle($request);
39+
$stream = new Stream(Stream::TEMP);
40+
$factory = new ResponseFactory($stream);
41+
$factory->createResponse(200, "OK");
4142

43+
$finalHandler = new ControllerRequestHandler($factory, function(RequestInterface $req, ResponseInterface $res) {
44+
$res->getBody()->write("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras eleifend ligula vel diam tincidunt finibus. In dapibus dictum lectus a malesuada.");
45+
return $res;
46+
});
4247

48+
$handler = new RequestHandler($middlewares, $finalHandler);
49+
$response = $handler->handle($request);
4350
$emit = new HttpEmitter();
4451

4552
ob_start();
46-
$emit->emit($response->withBody($stream), $request);
53+
$emit->emit($response, $request);
4754
$out = ob_get_clean();
4855

49-
5056
$case->validate($out, function (Expect $expect) {
51-
$expect->isLength(143);
57+
$expect->isLength(119);
5258
});
5359

5460
$headers = $response->getHeaders();

0 commit comments

Comments
 (0)