Skip to content

Commit 73a5587

Browse files
authored
Merge pull request #5 from apisearch-io/feature/twig-support
Introduced Twig support
2 parents 1e44acd + bffda8d commit 73a5587

6 files changed

+202
-4
lines changed

AsyncHttpKernel.php

+93-3
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@
3131
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
3232
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
3333
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
34+
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
3435
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
3536
use Symfony\Component\HttpKernel\Exception\AsyncEventDispatcherNeededException;
3637
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
38+
use Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException;
3739
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
3840
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
41+
use Throwable;
3942

4043
/**
4144
* Class AsyncHttpKernel.
@@ -113,7 +116,7 @@ public function handleAsync(
113116
$type
114117
)
115118
->then(null,
116-
function (Exception $exception) use ($request, $type, $catch) {
119+
function (Throwable $exception) use ($request, $type, $catch) {
117120
if ($exception instanceof RequestExceptionInterface) {
118121
$exception = new BadRequestHttpException($exception->getMessage(), $exception);
119122
}
@@ -197,11 +200,53 @@ private function callAsyncController(Request $request, int $type): PromiseInterf
197200
->then(function () use ($controller, $arguments) {
198201
return $controller(...$arguments);
199202
})
203+
->then(function ($response) use ($request, $type, $controller) {
204+
if (!$response instanceof Response) {
205+
return $this->callAsyncView($request, $response, $controller, $type);
206+
}
207+
208+
return $response;
209+
})
200210
->then(function ($response) use ($request, $type) {
201211
return $this->filterResponsePromise($response, $request, $type);
202212
});
203213
}
204214

215+
/**
216+
* Call async view.
217+
*
218+
* @param Request $request
219+
* @param mixed $response
220+
* @param callable $controller
221+
* @param int $type
222+
*
223+
* @return PromiseInterface
224+
*/
225+
private function callAsyncView(
226+
Request $request,
227+
$response,
228+
callable $controller,
229+
int $type
230+
): PromiseInterface {
231+
return (new FulfilledPromise())
232+
->then(function () use ($request, $response, $controller, $type) {
233+
$event = new GetResponseForControllerResultEvent($this, $request, $type, $response);
234+
$this->dispatcher->dispatch(KernelEvents::VIEW, $event);
235+
236+
if ($event->hasResponse()) {
237+
return $event->getResponse();
238+
} else {
239+
$msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this->varToString($response));
240+
// the user may have forgotten to return something
241+
if (null === $response) {
242+
$msg .= ' Did you forget to add a return statement somewhere in your controller?';
243+
}
244+
245+
throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17);
246+
}
247+
});
248+
}
249+
205250
/**
206251
* Filters a response object.
207252
*
@@ -247,7 +292,7 @@ private function finishRequestPromise(Request $request, int $type)
247292
/**
248293
* Handles an exception by trying to convert it to a Response.
249294
*
250-
* @param Exception $exception
295+
* @param Throwable $exception
251296
* @param Request $request
252297
* @param int $type
253298
*
@@ -256,10 +301,17 @@ private function finishRequestPromise(Request $request, int $type)
256301
* @throws Exception
257302
*/
258303
private function handleExceptionPromise(
259-
Exception $exception,
304+
Throwable $exception,
260305
Request $request,
261306
int $type
262307
): PromiseInterface {
308+
if (!$exception instanceof Exception) {
309+
$exception = new Exception(
310+
$exception->getMessage(),
311+
$exception->getCode()
312+
);
313+
}
314+
263315
$event = new GetResponseForExceptionEvent($this, $request, $type, $exception);
264316

265317
return $this
@@ -291,4 +343,42 @@ private function handleExceptionPromise(
291343
return $this->filterResponsePromise($response, $request, $type);
292344
});
293345
}
346+
347+
/**
348+
* Returns a human-readable string for the specified variable.
349+
*/
350+
private function varToString($var): string
351+
{
352+
if (\is_object($var)) {
353+
return sprintf('an object of type %s', \get_class($var));
354+
}
355+
if (\is_array($var)) {
356+
$a = [];
357+
foreach ($var as $k => $v) {
358+
$a[] = sprintf('%s => ...', $k);
359+
}
360+
361+
return sprintf('an array ([%s])', mb_substr(implode(', ', $a), 0, 255));
362+
}
363+
if (\is_resource($var)) {
364+
return sprintf('a resource (%s)', get_resource_type($var));
365+
}
366+
if (null === $var) {
367+
return 'null';
368+
}
369+
if (false === $var) {
370+
return 'a boolean value (false)';
371+
}
372+
if (true === $var) {
373+
return 'a boolean value (true)';
374+
}
375+
if (\is_string($var)) {
376+
return sprintf('a string ("%s%s")', mb_substr($var, 0, 255), mb_strlen($var) > 255 ? '...' : '');
377+
}
378+
if (is_numeric($var)) {
379+
return sprintf('a number (%s)', (string) $var);
380+
}
381+
382+
return (string) $var;
383+
}
294384
}

Tests/AsyncKernelFunctionalTest.php

+5
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ protected static function getKernel(): KernelInterface
8989
Controller::class.':getPromiseException',
9090
'promise-exception',
9191
],
92+
[
93+
'/simple-result',
94+
Controller::class.':getSimpleResult',
95+
'simple-result',
96+
],
9297
];
9398

9499
return new AsyncBaseKernel(

Tests/Controller.php

+10
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,14 @@ public function getPromiseException(): PromiseInterface
6565
{
6666
return new RejectedPromise(new Exception('E2'));
6767
}
68+
69+
/**
70+
* Return array.
71+
*
72+
* @return array
73+
*/
74+
public function getSimpleResult(): array
75+
{
76+
return ['a', 'b'];
77+
}
6878
}

Tests/GetResponsePromiseFunctionalTest.php

+5
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ public function testSyncKernel()
8787
'EXC',
8888
$exception->getMessage()
8989
);
90+
91+
$this->assertEquals(
92+
404,
93+
$exception->getCode()
94+
);
9095
});
9196

9297
$loop->run();

Tests/Listener.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
use React\Promise\FulfilledPromise;
1919
use React\Promise\PromiseInterface;
20+
use Symfony\Component\HttpFoundation\JsonResponse;
2021
use Symfony\Component\HttpFoundation\Response;
2122
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
23+
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
2224
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
2325

2426
/**
@@ -85,7 +87,7 @@ public function handleGetExceptionA(GetResponseForExceptionEvent $event)
8587
{
8688
return (new FulfilledPromise())
8789
->then(function () use ($event) {
88-
$event->setResponse(new Response('EXC'));
90+
$event->setResponse(new Response('EXC', 404));
8991
});
9092
}
9193

@@ -124,4 +126,14 @@ public function handleGetResponsePromise3(GetResponseEvent $event)
124126
{
125127
$_GET['partial'] .= '3';
126128
}
129+
130+
/**
131+
* Handle view.
132+
*
133+
* @param GetResponseForControllerResultEvent $event
134+
*/
135+
public function handleView(GetResponseForControllerResultEvent $event)
136+
{
137+
$event->setResponse(new JsonResponse($event->getControllerResult()));
138+
}
127139
}

Tests/TwigTest.php

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony Async Kernel
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*
9+
* Feel free to edit as you please, and have fun.
10+
*
11+
* @author Marc Morera <[email protected]>
12+
*/
13+
14+
declare(strict_types=1);
15+
16+
namespace Symfony\Component\HttpKernel\Tests;
17+
18+
use Clue\React\Block;
19+
use React\EventLoop\StreamSelectLoop;
20+
use Symfony\Component\HttpFoundation\Request;
21+
use Symfony\Component\HttpFoundation\Response;
22+
23+
/**
24+
* Class TwigTest.
25+
*/
26+
class TwigTest extends AsyncKernelFunctionalTest
27+
{
28+
/**
29+
* Decorate configuration.
30+
*
31+
* @param array $configuration
32+
*
33+
* @return array
34+
*/
35+
protected static function decorateConfiguration(array $configuration): array
36+
{
37+
$configuration = parent::decorateConfiguration($configuration);
38+
$configuration['services']['listener'] = [
39+
'class' => Listener::class,
40+
'tags' => [
41+
[
42+
'name' => 'kernel.event_listener',
43+
'event' => 'kernel.view',
44+
'method' => 'handleView',
45+
],
46+
],
47+
];
48+
49+
return $configuration;
50+
}
51+
52+
/**
53+
* Everything should work as before in the world of sync requests.
54+
*/
55+
public function testSyncKernel()
56+
{
57+
$loop = new StreamSelectLoop();
58+
$request = new Request([], [], [], [], [], [
59+
'REQUEST_METHOD' => 'GET',
60+
'REQUEST_URI' => '/simple-result',
61+
]);
62+
63+
$_GET['partial'] = '';
64+
$promise = self::$kernel
65+
->handleAsync($request)
66+
->then(function (Response $response) {
67+
$this->assertEquals(
68+
json_encode(['a', 'b']),
69+
$response->getContent()
70+
);
71+
});
72+
73+
$loop->run();
74+
Block\await($promise, $loop);
75+
}
76+
}

0 commit comments

Comments
 (0)