-
-
Notifications
You must be signed in to change notification settings - Fork 57
PSR HTTP message converters for controllers #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ composer.lock | |
phpunit.xml | ||
.php_cs.cache | ||
.phpunit.result.cache | ||
/Tests/Fixtures/App/var |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
|
||
namespace Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver; | ||
|
||
use Psr\Http\Message\MessageInterface; | ||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; | ||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; | ||
|
||
/** | ||
* Injects the RequestInterface, MessageInterface or ServerRequestInterface when requested. | ||
* | ||
* @author Iltar van der Berg <kjarli@gmail.com> | ||
* @author Alexander M. Turek <me@derrabus.de> | ||
*/ | ||
final class PsrServerRequestResolver implements ArgumentValueResolverInterface | ||
{ | ||
private const SUPPORTED_TYPES = [ | ||
ServerRequestInterface::class => true, | ||
RequestInterface::class => true, | ||
MessageInterface::class => true, | ||
]; | ||
|
||
private $httpMessageFactory; | ||
|
||
public function __construct(HttpMessageFactoryInterface $httpMessageFactory) | ||
{ | ||
$this->httpMessageFactory = $httpMessageFactory; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function supports(Request $request, ArgumentMetadata $argument): bool | ||
{ | ||
return self::SUPPORTED_TYPES[$argument->getType()] ?? false; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function resolve(Request $request, ArgumentMetadata $argument): \Traversable | ||
{ | ||
yield $this->httpMessageFactory->createRequest($request); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
namespace Symfony\Bridge\PsrHttpMessage\EventListener; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; | ||
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; | ||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
use Symfony\Component\HttpKernel\Event\ViewEvent; | ||
use Symfony\Component\HttpKernel\KernelEvents; | ||
|
||
/** | ||
* Converts PSR-7 Response to HttpFoundation Response using the bridge. | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
* @author Alexander M. Turek <me@derrabus.de> | ||
*/ | ||
final class PsrResponseListener implements EventSubscriberInterface | ||
{ | ||
private $httpFoundationFactory; | ||
|
||
public function __construct(HttpFoundationFactoryInterface $httpFoundationFactory = null) | ||
{ | ||
$this->httpFoundationFactory = $httpFoundationFactory ?? new HttpFoundationFactory(); | ||
} | ||
|
||
/** | ||
* Do the conversion if applicable and update the response of the event. | ||
*/ | ||
public function onKernelView(ViewEvent $event): void | ||
{ | ||
$controllerResult = $event->getControllerResult(); | ||
|
||
if (!$controllerResult instanceof ResponseInterface) { | ||
return; | ||
} | ||
|
||
$event->setResponse($this->httpFoundationFactory->createResponse($controllerResult)); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public static function getSubscribedEvents(): array | ||
{ | ||
return [ | ||
KernelEvents::VIEW => 'onKernelView', | ||
]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?php | ||
|
||
namespace Symfony\Bridge\PsrHttpMessage\Tests\ArgumentValueResolver; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Psr\Http\Message\MessageInterface; | ||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver\PsrServerRequestResolver; | ||
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpKernel\Controller\ArgumentResolver; | ||
|
||
/** | ||
* @author Alexander M. Turek <me@derrabus.de> | ||
*/ | ||
final class PsrServerRequestResolverTest extends TestCase | ||
{ | ||
public function testServerRequest() | ||
{ | ||
$symfonyRequest = $this->createMock(Request::class); | ||
$psrRequest = $this->createMock(ServerRequestInterface::class); | ||
|
||
$resolver = $this->bootstrapResolver($symfonyRequest, $psrRequest); | ||
|
||
self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (ServerRequestInterface $serverRequest): void {})); | ||
} | ||
|
||
public function testRequest() | ||
{ | ||
$symfonyRequest = $this->createMock(Request::class); | ||
$psrRequest = $this->createMock(ServerRequestInterface::class); | ||
|
||
$resolver = $this->bootstrapResolver($symfonyRequest, $psrRequest); | ||
|
||
self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (RequestInterface $request): void {})); | ||
} | ||
|
||
public function testMessage() | ||
{ | ||
$symfonyRequest = $this->createMock(Request::class); | ||
$psrRequest = $this->createMock(ServerRequestInterface::class); | ||
|
||
$resolver = $this->bootstrapResolver($symfonyRequest, $psrRequest); | ||
|
||
self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (MessageInterface $request): void {})); | ||
} | ||
|
||
private function bootstrapResolver(Request $symfonyRequest, ServerRequestInterface $psrRequest): ArgumentResolver | ||
{ | ||
$messageFactory = $this->createMock(HttpMessageFactoryInterface::class); | ||
$messageFactory->expects(self::once()) | ||
->method('createRequest') | ||
->with(self::identicalTo($symfonyRequest)) | ||
->willReturn($psrRequest); | ||
|
||
return new ArgumentResolver(null, [new PsrServerRequestResolver($messageFactory)]); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
|
||
namespace Symfony\Bridge\PsrHttpMessage\Tests\EventListener; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Bridge\PsrHttpMessage\EventListener\PsrResponseListener; | ||
use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Response; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpKernel\Event\ViewEvent; | ||
use Symfony\Component\HttpKernel\HttpKernelInterface; | ||
|
||
/** | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
class PsrResponseListenerTest extends TestCase | ||
{ | ||
public function testConvertsControllerResult() | ||
{ | ||
$listener = new PsrResponseListener(); | ||
$event = $this->createEventMock(new Response()); | ||
$listener->onKernelView($event); | ||
|
||
self::assertTrue($event->hasResponse()); | ||
} | ||
|
||
public function testDoesNotConvertControllerResult() | ||
{ | ||
$listener = new PsrResponseListener(); | ||
$event = $this->createEventMock([]); | ||
|
||
$listener->onKernelView($event); | ||
self::assertFalse($event->hasResponse()); | ||
|
||
$event = $this->createEventMock(null); | ||
|
||
$listener->onKernelView($event); | ||
self::assertFalse($event->hasResponse()); | ||
} | ||
|
||
private function createEventMock($controllerResult): ViewEvent | ||
{ | ||
return new ViewEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST, $controllerResult); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
|
||
namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App\Controller; | ||
|
||
use Psr\Http\Message\MessageInterface; | ||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseFactoryInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Message\StreamFactoryInterface; | ||
|
||
final class PsrRequestController | ||
{ | ||
private $responseFactory; | ||
private $streamFactory; | ||
|
||
public function __construct(ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory) | ||
{ | ||
$this->responseFactory = $responseFactory; | ||
$this->streamFactory = $streamFactory; | ||
} | ||
|
||
public function serverRequestAction(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
return $this->responseFactory | ||
->createResponse() | ||
->withBody($this->streamFactory->createStream(sprintf('<html><body>%s</body></html>', $request->getMethod()))); | ||
} | ||
|
||
public function requestAction(RequestInterface $request): ResponseInterface | ||
{ | ||
return $this->responseFactory | ||
->createResponse() | ||
->withStatus(403) | ||
->withBody($this->streamFactory->createStream(sprintf('<html><body>%s %s</body></html>', $request->getMethod(), $request->getBody()->getContents()))); | ||
} | ||
|
||
public function messageAction(MessageInterface $request): ResponseInterface | ||
{ | ||
return $this->responseFactory | ||
->createResponse() | ||
->withStatus(422) | ||
->withBody($this->streamFactory->createStream(sprintf('<html><body>%s</body></html>', $request->getHeader('X-My-Header')[0]))); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?php | ||
|
||
namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App; | ||
|
||
use Nyholm\Psr7\Factory\Psr17Factory; | ||
use Psr\Http\Message\ResponseFactoryInterface; | ||
use Psr\Http\Message\ServerRequestFactoryInterface; | ||
use Psr\Http\Message\StreamFactoryInterface; | ||
use Psr\Http\Message\UploadedFileFactoryInterface; | ||
use Psr\Log\NullLogger; | ||
use Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver\PsrServerRequestResolver; | ||
use Symfony\Bridge\PsrHttpMessage\EventListener\PsrResponseListener; | ||
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; | ||
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; | ||
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; | ||
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; | ||
use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App\Controller\PsrRequestController; | ||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle; | ||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; | ||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; | ||
use Symfony\Component\HttpKernel\Kernel as SymfonyKernel; | ||
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; | ||
|
||
class Kernel extends SymfonyKernel | ||
{ | ||
use MicroKernelTrait; | ||
|
||
public function registerBundles(): iterable | ||
{ | ||
yield new FrameworkBundle(); | ||
} | ||
|
||
public function getProjectDir(): string | ||
{ | ||
return __DIR__; | ||
} | ||
|
||
protected function configureRoutes(RoutingConfigurator $routes): void | ||
{ | ||
$routes | ||
->add('server_request', '/server-request')->controller([PsrRequestController::class, 'serverRequestAction'])->methods(['GET']) | ||
->add('request', '/request')->controller([PsrRequestController::class, 'requestAction'])->methods(['POST']) | ||
->add('message', '/message')->controller([PsrRequestController::class, 'messageAction'])->methods(['PUT']) | ||
; | ||
} | ||
|
||
protected function configureContainer(ContainerConfigurator $container): void | ||
{ | ||
$container->extension('framework', [ | ||
'router' => ['utf8' => true], | ||
'secret' => 'for your eyes only', | ||
'test' => true, | ||
]); | ||
|
||
$container->services() | ||
->set('nyholm.psr_factory', Psr17Factory::class) | ||
->alias(ResponseFactoryInterface::class, 'nyholm.psr_factory') | ||
->alias(ServerRequestFactoryInterface::class, 'nyholm.psr_factory') | ||
->alias(StreamFactoryInterface::class, 'nyholm.psr_factory') | ||
->alias(UploadedFileFactoryInterface::class, 'nyholm.psr_factory') | ||
; | ||
Comment on lines
+55
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 The recipe for |
||
|
||
$container->services() | ||
->defaults()->autowire()->autoconfigure() | ||
->set(HttpFoundationFactoryInterface::class, HttpFoundationFactory::class) | ||
->set(HttpMessageFactoryInterface::class, PsrHttpFactory::class) | ||
->set(PsrResponseListener::class) | ||
->set(PsrServerRequestResolver::class) | ||
; | ||
Comment on lines
+63
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 A new recipe for |
||
|
||
$container->services() | ||
->set('logger', NullLogger::class) | ||
->set(PsrRequestController::class)->public()->autowire() | ||
; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.