Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 81db2d4

Browse filesBrowse files
committed
feature #89 PSR HTTP message converters for controllers (derrabus)
This PR was merged into the 2.0-dev branch. Discussion ---------- PSR HTTP message converters for controllers This PR proposes to add two classes to `symfony/psr-http-message-bridge` that have been removed from `sensio/framework-extra-bundle`. By adding `PsrServerRequestResolver` and `PsrResponseListener` to its service container, with autowring and autoconfiguring enabled, an application gains the ability to operate controllers on PSR-7 message objects instead of HttpFoundation. This is especially useful if a developer wants to reuse generic packages like `league/oauth2-server`. Configuration files for autowiring the two classes can be provided as Flex recipes. Commits ------- aa26e61 PSR HTTP message converters for controllers
2 parents e62b239 + aa26e61 commit 81db2d4
Copy full SHA for 81db2d4

File tree

11 files changed

+450
-2
lines changed
Filter options

11 files changed

+450
-2
lines changed

‎.gitignore

Copy file name to clipboardExpand all lines: .gitignore
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ composer.lock
33
phpunit.xml
44
.php_cs.cache
55
.phpunit.result.cache
6+
/Tests/Fixtures/App/var
+49Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver;
4+
5+
use Psr\Http\Message\MessageInterface;
6+
use Psr\Http\Message\RequestInterface;
7+
use Psr\Http\Message\ServerRequestInterface;
8+
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
9+
use Symfony\Component\HttpFoundation\Request;
10+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
11+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
12+
13+
/**
14+
* Injects the RequestInterface, MessageInterface or ServerRequestInterface when requested.
15+
*
16+
* @author Iltar van der Berg <kjarli@gmail.com>
17+
* @author Alexander M. Turek <me@derrabus.de>
18+
*/
19+
final class PsrServerRequestResolver implements ArgumentValueResolverInterface
20+
{
21+
private const SUPPORTED_TYPES = [
22+
ServerRequestInterface::class => true,
23+
RequestInterface::class => true,
24+
MessageInterface::class => true,
25+
];
26+
27+
private $httpMessageFactory;
28+
29+
public function __construct(HttpMessageFactoryInterface $httpMessageFactory)
30+
{
31+
$this->httpMessageFactory = $httpMessageFactory;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function supports(Request $request, ArgumentMetadata $argument): bool
38+
{
39+
return self::SUPPORTED_TYPES[$argument->getType()] ?? false;
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function resolve(Request $request, ArgumentMetadata $argument): \Traversable
46+
{
47+
yield $this->httpMessageFactory->createRequest($request);
48+
}
49+
}

‎CHANGELOG.md

Copy file name to clipboardExpand all lines: CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
# 2.1.0 (2021-02-17)
5+
6+
* Added a `PsrResponseListener` to automatically convert PSR-7 responses returned by controllers
7+
* Added a `PsrServerRequestResolver` that allows injecting PSR-7 request objects into controllers
8+
49
# 2.0.2 (2020-09-29)
510

611
* Fix populating server params from URI in HttpFoundationFactory

‎EventListener/PsrResponseListener.php

Copy file name to clipboard
+50Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\PsrHttpMessage\EventListener;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
7+
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
8+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
9+
use Symfony\Component\HttpKernel\Event\ViewEvent;
10+
use Symfony\Component\HttpKernel\KernelEvents;
11+
12+
/**
13+
* Converts PSR-7 Response to HttpFoundation Response using the bridge.
14+
*
15+
* @author Kévin Dunglas <dunglas@gmail.com>
16+
* @author Alexander M. Turek <me@derrabus.de>
17+
*/
18+
final class PsrResponseListener implements EventSubscriberInterface
19+
{
20+
private $httpFoundationFactory;
21+
22+
public function __construct(HttpFoundationFactoryInterface $httpFoundationFactory = null)
23+
{
24+
$this->httpFoundationFactory = $httpFoundationFactory ?? new HttpFoundationFactory();
25+
}
26+
27+
/**
28+
* Do the conversion if applicable and update the response of the event.
29+
*/
30+
public function onKernelView(ViewEvent $event): void
31+
{
32+
$controllerResult = $event->getControllerResult();
33+
34+
if (!$controllerResult instanceof ResponseInterface) {
35+
return;
36+
}
37+
38+
$event->setResponse($this->httpFoundationFactory->createResponse($controllerResult));
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public static function getSubscribedEvents(): array
45+
{
46+
return [
47+
KernelEvents::VIEW => 'onKernelView',
48+
];
49+
}
50+
}
+59Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\PsrHttpMessage\Tests\ArgumentValueResolver;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Psr\Http\Message\MessageInterface;
7+
use Psr\Http\Message\RequestInterface;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
use Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver\PsrServerRequestResolver;
10+
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
11+
use Symfony\Component\HttpFoundation\Request;
12+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
13+
14+
/**
15+
* @author Alexander M. Turek <me@derrabus.de>
16+
*/
17+
final class PsrServerRequestResolverTest extends TestCase
18+
{
19+
public function testServerRequest()
20+
{
21+
$symfonyRequest = $this->createMock(Request::class);
22+
$psrRequest = $this->createMock(ServerRequestInterface::class);
23+
24+
$resolver = $this->bootstrapResolver($symfonyRequest, $psrRequest);
25+
26+
self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (ServerRequestInterface $serverRequest): void {}));
27+
}
28+
29+
public function testRequest()
30+
{
31+
$symfonyRequest = $this->createMock(Request::class);
32+
$psrRequest = $this->createMock(ServerRequestInterface::class);
33+
34+
$resolver = $this->bootstrapResolver($symfonyRequest, $psrRequest);
35+
36+
self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (RequestInterface $request): void {}));
37+
}
38+
39+
public function testMessage()
40+
{
41+
$symfonyRequest = $this->createMock(Request::class);
42+
$psrRequest = $this->createMock(ServerRequestInterface::class);
43+
44+
$resolver = $this->bootstrapResolver($symfonyRequest, $psrRequest);
45+
46+
self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (MessageInterface $request): void {}));
47+
}
48+
49+
private function bootstrapResolver(Request $symfonyRequest, ServerRequestInterface $psrRequest): ArgumentResolver
50+
{
51+
$messageFactory = $this->createMock(HttpMessageFactoryInterface::class);
52+
$messageFactory->expects(self::once())
53+
->method('createRequest')
54+
->with(self::identicalTo($symfonyRequest))
55+
->willReturn($psrRequest);
56+
57+
return new ArgumentResolver(null, [new PsrServerRequestResolver($messageFactory)]);
58+
}
59+
}
+44Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\PsrHttpMessage\Tests\EventListener;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Bridge\PsrHttpMessage\EventListener\PsrResponseListener;
7+
use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Response;
8+
use Symfony\Component\HttpFoundation\Request;
9+
use Symfony\Component\HttpKernel\Event\ViewEvent;
10+
use Symfony\Component\HttpKernel\HttpKernelInterface;
11+
12+
/**
13+
* @author Kévin Dunglas <dunglas@gmail.com>
14+
*/
15+
class PsrResponseListenerTest extends TestCase
16+
{
17+
public function testConvertsControllerResult()
18+
{
19+
$listener = new PsrResponseListener();
20+
$event = $this->createEventMock(new Response());
21+
$listener->onKernelView($event);
22+
23+
self::assertTrue($event->hasResponse());
24+
}
25+
26+
public function testDoesNotConvertControllerResult()
27+
{
28+
$listener = new PsrResponseListener();
29+
$event = $this->createEventMock([]);
30+
31+
$listener->onKernelView($event);
32+
self::assertFalse($event->hasResponse());
33+
34+
$event = $this->createEventMock(null);
35+
36+
$listener->onKernelView($event);
37+
self::assertFalse($event->hasResponse());
38+
}
39+
40+
private function createEventMock($controllerResult): ViewEvent
41+
{
42+
return new ViewEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST, $controllerResult);
43+
}
44+
}
+45Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App\Controller;
4+
5+
use Psr\Http\Message\MessageInterface;
6+
use Psr\Http\Message\RequestInterface;
7+
use Psr\Http\Message\ResponseFactoryInterface;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use Psr\Http\Message\StreamFactoryInterface;
11+
12+
final class PsrRequestController
13+
{
14+
private $responseFactory;
15+
private $streamFactory;
16+
17+
public function __construct(ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory)
18+
{
19+
$this->responseFactory = $responseFactory;
20+
$this->streamFactory = $streamFactory;
21+
}
22+
23+
public function serverRequestAction(ServerRequestInterface $request): ResponseInterface
24+
{
25+
return $this->responseFactory
26+
->createResponse()
27+
->withBody($this->streamFactory->createStream(sprintf('<html><body>%s</body></html>', $request->getMethod())));
28+
}
29+
30+
public function requestAction(RequestInterface $request): ResponseInterface
31+
{
32+
return $this->responseFactory
33+
->createResponse()
34+
->withStatus(403)
35+
->withBody($this->streamFactory->createStream(sprintf('<html><body>%s %s</body></html>', $request->getMethod(), $request->getBody()->getContents())));
36+
}
37+
38+
public function messageAction(MessageInterface $request): ResponseInterface
39+
{
40+
return $this->responseFactory
41+
->createResponse()
42+
->withStatus(422)
43+
->withBody($this->streamFactory->createStream(sprintf('<html><body>%s</body></html>', $request->getHeader('X-My-Header')[0])));
44+
}
45+
}

‎Tests/Fixtures/App/Kernel.php

Copy file name to clipboard
+76Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App;
4+
5+
use Nyholm\Psr7\Factory\Psr17Factory;
6+
use Psr\Http\Message\ResponseFactoryInterface;
7+
use Psr\Http\Message\ServerRequestFactoryInterface;
8+
use Psr\Http\Message\StreamFactoryInterface;
9+
use Psr\Http\Message\UploadedFileFactoryInterface;
10+
use Psr\Log\NullLogger;
11+
use Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver\PsrServerRequestResolver;
12+
use Symfony\Bridge\PsrHttpMessage\EventListener\PsrResponseListener;
13+
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
14+
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
15+
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
16+
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
17+
use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App\Controller\PsrRequestController;
18+
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
19+
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
20+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
21+
use Symfony\Component\HttpKernel\Kernel as SymfonyKernel;
22+
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
23+
24+
class Kernel extends SymfonyKernel
25+
{
26+
use MicroKernelTrait;
27+
28+
public function registerBundles(): iterable
29+
{
30+
yield new FrameworkBundle();
31+
}
32+
33+
public function getProjectDir(): string
34+
{
35+
return __DIR__;
36+
}
37+
38+
protected function configureRoutes(RoutingConfigurator $routes): void
39+
{
40+
$routes
41+
->add('server_request', '/server-request')->controller([PsrRequestController::class, 'serverRequestAction'])->methods(['GET'])
42+
->add('request', '/request')->controller([PsrRequestController::class, 'requestAction'])->methods(['POST'])
43+
->add('message', '/message')->controller([PsrRequestController::class, 'messageAction'])->methods(['PUT'])
44+
;
45+
}
46+
47+
protected function configureContainer(ContainerConfigurator $container): void
48+
{
49+
$container->extension('framework', [
50+
'router' => ['utf8' => true],
51+
'secret' => 'for your eyes only',
52+
'test' => true,
53+
]);
54+
55+
$container->services()
56+
->set('nyholm.psr_factory', Psr17Factory::class)
57+
->alias(ResponseFactoryInterface::class, 'nyholm.psr_factory')
58+
->alias(ServerRequestFactoryInterface::class, 'nyholm.psr_factory')
59+
->alias(StreamFactoryInterface::class, 'nyholm.psr_factory')
60+
->alias(UploadedFileFactoryInterface::class, 'nyholm.psr_factory')
61+
;
62+
63+
$container->services()
64+
->defaults()->autowire()->autoconfigure()
65+
->set(HttpFoundationFactoryInterface::class, HttpFoundationFactory::class)
66+
->set(HttpMessageFactoryInterface::class, PsrHttpFactory::class)
67+
->set(PsrResponseListener::class)
68+
->set(PsrServerRequestResolver::class)
69+
;
70+
71+
$container->services()
72+
->set('logger', NullLogger::class)
73+
->set(PsrRequestController::class)->public()->autowire()
74+
;
75+
}
76+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.