Skip to content

Commit 916ef49

Browse files
committed
add coroutine support
1 parent b08a951 commit 916ef49

File tree

9 files changed

+158
-28
lines changed

9 files changed

+158
-28
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
}
1515
],
1616
"require": {
17-
"php": "^7.4|^8.0",
17+
"php": "^8.0",
1818
"antidot-fw/framework": "^0.2.0",
1919
"beberlei/assert": "^3.3",
2020
"drift/server": "^0.1.20",

docs/README.md

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ It allows executing promises inside PSR-15 and PSR-7 Middlewares and request han
100100

101101
### PSR-15 Middleware
102102

103+
**Promise Based**
104+
103105
```php
104106
<?php
105107
declare(strict_types = 1);
@@ -111,21 +113,55 @@ use Psr\Http\Message\ResponseInterface;
111113
use Psr\Http\Message\ServerRequestInterface;
112114
use Psr\Http\Server\MiddlewareInterface;
113115
use Psr\Http\Server\RequestHandlerInterface;
116+
use function React\Promise\resolve;
114117

115118
class SomeMiddleware implements MiddlewareInterface
116119
{
117120
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
118121
{
119122
return new PromiseResponse(
120123
resolve($request)
121-
->then(static fn(ServerrequestInsterface $request) => $handler->handle($request))
124+
->then(static fn(ServerRequestInterface $request) => $handler->handle($request))
125+
);
126+
}
127+
}
128+
```
129+
130+
**Coroutine based**
131+
132+
```php
133+
<?php
134+
declare(strict_types = 1);
135+
136+
namespace App;
137+
138+
use Antidot\React\PromiseResponse;
139+
use Psr\Http\Message\ResponseInterface;
140+
use Psr\Http\Message\ServerRequestInterface;
141+
use Psr\Http\Server\MiddlewareInterface;
142+
use Psr\Http\Server\RequestHandlerInterface;
143+
use function React\Promise\resolve;
144+
145+
class SomeMiddleware implements MiddlewareInterface
146+
{
147+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
148+
{
149+
return PromiseResponse::fromGeneratorCallback(
150+
static function(ServerRequestInterface $request) {
151+
$value = yield resolve('Some promised value.');
152+
$request = $request->withAttribute('some_attribute', $value);
153+
154+
return $handler->handle($request));
155+
}
122156
);
123157
}
124158
}
125159
```
126160

127161
### PSR-7 Request Handler
128162

163+
**Promise Based**
164+
129165
```php
130166
<?php
131167
declare(strict_types = 1);
@@ -136,16 +172,46 @@ use Antidot\React\PromiseResponse;
136172
use Psr\Http\Message\ResponseInterface;
137173
use Psr\Http\Message\ServerRequestInterface;
138174
use Psr\Http\Server\RequestHandlerInterface;
175+
use function React\Promise\resolve;
139176

140-
class SomeMiddleware implements RequestHandlerInterface
177+
class SomeRequestHandler implements RequestHandlerInterface
141178
{
142-
public function process(ServerRequestInterface $request): ResponseInterface
179+
public function handle(ServerRequestInterface $request): ResponseInterface
143180
{
144-
return resolve($request)->then(
145-
function(ServerrequestInterface $request): ResponseInterface {
181+
return new PromiseResponse(resolve($request)->then(
182+
function(ServerRequestInterface $request): ResponseInterface {
146183
return new Response('Hello World!!!');
147184
}
148-
);;
185+
));
186+
}
187+
}
188+
```
189+
190+
**Coroutine based**
191+
192+
```php
193+
<?php
194+
declare(strict_types = 1);
195+
196+
namespace App;
197+
198+
use Antidot\React\PromiseResponse;
199+
use Psr\Http\Message\ResponseInterface;
200+
use Psr\Http\Message\ServerRequestInterface;
201+
use Psr\Http\Server\RequestHandlerInterface;
202+
use function React\Promise\resolve;
203+
204+
class SomeRequestHandler implements RequestHandlerInterface
205+
{
206+
public function handle(ServerRequestInterface $request): ResponseInterface
207+
{
208+
return PromiseResponse::fromGeneratorCallback(
209+
function(ServerRequestInterface $request): ResponseInterface {
210+
$message = yield resolve('Hello World!!!');
211+
212+
return new Response($message);
213+
}
214+
));
149215
}
150216
}
151217
```

src/DriftKernelAdapter.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,18 @@ public static function create(
6868
}
6969

7070
return resolve(new self($serverContext, $mimeTypeChecker, $rootPath, $filesystem))
71-
->then(fn (KernelAdapter $adapter): KernelAdapter => $adapter);
71+
->then(static fn(KernelAdapter $adapter): KernelAdapter => $adapter);
7272
}
7373

7474
/**
75-
* @psalm-suppress LessSpecificImplementedReturnType
7675
* @param ServerRequestInterface $request
77-
* @return PromiseResponse
76+
* @psalm-suppress LessSpecificImplementedReturnType
7877
*/
79-
public function handle(ServerRequestInterface $request): PromiseResponse
78+
public function handle(ServerRequestInterface $request): PromiseInterface
8079
{
81-
return $this->application->handle($request);
80+
return $this->application
81+
->handle($request)
82+
->then([ResolveGenerator::class, 'toResponse']);
8283
}
8384

8485
public static function getStaticFolder(): ?string

src/MiddlewarePipeline.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use Psr\Http\Server\MiddlewareInterface;
1414
use Psr\Http\Server\RequestHandlerInterface;
1515
use Ramsey\Uuid\Uuid;
16-
use SplQueue;
1716
use Throwable;
1817
use function React\Promise\reject;
1918
use function React\Promise\resolve;
@@ -53,7 +52,6 @@ function (ServerRequestInterface $request) {
5352
/** @var string $requestId */
5453
$requestId = $request->getAttribute('request_id');
5554
try {
56-
/** @var MiddlewareInterface $middleware */
5755
$middleware = $this->concurrentPipelines[$requestId]->dequeue();
5856

5957
$response = $middleware->process($request, $this);
@@ -84,7 +82,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
8482
/** @var string $requestId */
8583
$requestId = $request->getAttribute('request_id');
8684
try {
87-
/** @var MiddlewareQueue $queue */
8885
$queue = $this->concurrentPipelines[$requestId];
8986
$next = new NextHandler($queue, $handler);
9087

src/PromiseResponse.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
namespace Antidot\React;
66

7+
use Generator;
78
use React\Promise\PromiseInterface;
89
use RingCentral\Psr7\Response;
10+
use function React\Promise\resolve;
911

10-
class PromiseResponse extends Response implements PromiseInterface
12+
final class PromiseResponse extends Response implements PromiseInterface
1113
{
1214
private PromiseInterface $promise;
1315
protected $stream;
@@ -28,6 +30,25 @@ public function __construct(
2830
$this->promise = $promise;
2931
}
3032

33+
/**
34+
* @param callable<Generator> $callback
35+
* @return static
36+
*/
37+
public static function fromGeneratorCallback(callable $callback): self
38+
{
39+
return new self(resolve(function () use ($callback): Generator {
40+
/** @var Generator $generator */
41+
$generator = $callback();
42+
43+
return $generator;
44+
}));
45+
}
46+
47+
/**
48+
* @param callable|null $onFulfilled
49+
* @param callable|null $onRejected
50+
* @param callable|null $onProgress
51+
*/
3152
final public function then(
3253
callable $onFulfilled = null,
3354
callable $onRejected = null,

src/ResolveGenerator.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Antidot\React;
6+
7+
use Generator;
8+
use Psr\Http\Message\ResponseInterface;
9+
use React\Promise\Promise;
10+
use React\Promise\PromiseInterface;
11+
use Throwable;
12+
13+
final class ResolveGenerator
14+
{
15+
/**
16+
* @param callable<Generator>|ResponseInterface|PromiseInterface $generator
17+
*/
18+
public static function toResponse(
19+
callable|ResponseInterface|PromiseInterface $callback
20+
): PromiseInterface|ResponseInterface {
21+
if (false === is_callable($callback)) {
22+
return $callback;
23+
}
24+
25+
return new Promise(
26+
static function (callable $resolve, callable $reject) use ($callback): void {
27+
try {
28+
/** @var Generator<PromiseInterface|ResponseInterface> $generator */
29+
$generator = $callback();
30+
while ($generator->valid()) {
31+
$item = $generator->current();
32+
if ($item instanceof PromiseInterface) {
33+
$item->then(static fn($solved) => $generator->send($solved));
34+
continue;
35+
}
36+
$generator->send($item);
37+
}
38+
$resolve($generator->getReturn());
39+
} catch (Throwable $exception) {
40+
$reject($exception);
41+
}
42+
}
43+
);
44+
}
45+
}

src/RunServerCommandFactory.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ public function __invoke(ContainerInterface $container): RunServerCommand
2828
$adapter = $definition->getOption('adapter');
2929
$adapter->setDefault(DriftKernelAdapter::class);
3030
$staticFolder = $definition->getOption('static-folder');
31-
$staticFolder->setDefault($config['static_folder']);
31+
$staticFolder->setDefault((string)$config['static_folder']);
3232
$workers = $definition->getOption('workers');
33-
$workers->setDefault($config['workers']);
33+
$workers->setDefault((string)$config['workers']);
3434
$concurrentRequests = $definition->getOption('concurrent-requests');
35-
$concurrentRequests->setDefault($config['max_concurrency']);
35+
$concurrentRequests->setDefault((string)$config['max_concurrency']);
3636
$bufferSize = $definition->getOption('request-body-buffer');
37-
$bufferSize->setDefault($config['buffer_size']);
37+
$bufferSize->setDefault((string)$config['buffer_size']);
3838

3939
return $command;
4040
}

src/ServerFactory.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@
1414
use React\Http\Middleware\RequestBodyBufferMiddleware;
1515
use React\Http\Middleware\RequestBodyParserMiddleware;
1616
use React\Http\Middleware\StreamingRequestMiddleware;
17-
use React\Promise\PromiseInterface;
18-
use function PHPUnit\Framework\assertArrayHasKey;
19-
use function PHPUnit\Framework\assertIsInt;
20-
use function React\Promise\resolve;
2117

2218
class ServerFactory
2319
{
@@ -42,7 +38,11 @@ public function __invoke(ContainerInterface $container): Server
4238
new LimitConcurrentRequestsMiddleware($config['max_concurrency']),
4339
new RequestBodyBufferMiddleware($config['buffer_size']),
4440
new RequestBodyParserMiddleware(),
45-
static fn (ServerRequestInterface $request): PromiseInterface => resolve($application->handle($request))
41+
static function (ServerRequestInterface $request) use ($application) {
42+
return $application
43+
->handle($request)
44+
->then([ResolveGenerator::class, 'toResponse']);
45+
}
4646
);
4747

4848
return $server;

src/WatchServerCommandFactory.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ public function __invoke(ContainerInterface $container): WatchServerCommand
3131
$definition->setArguments([$path]);
3232

3333
$staticFolder = $definition->getOption('static-folder');
34-
$staticFolder->setDefault($config['static_folder']);
34+
$staticFolder->setDefault((string)$config['static_folder']);
3535
$concurrentRequests = $definition->getOption('concurrent-requests');
36-
$concurrentRequests->setDefault($config['max_concurrency']);
36+
$concurrentRequests->setDefault((string)$config['max_concurrency']);
3737
$bufferSize = $definition->getOption('request-body-buffer');
38-
$bufferSize->setDefault($config['buffer_size']);
38+
$bufferSize->setDefault((string)$config['buffer_size']);
3939

4040
return $command;
4141
}

0 commit comments

Comments
 (0)