From 47b4992cba94a03f3ce17e815499161aa116d1a8 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 31 Jul 2023 18:07:34 +0200 Subject: [PATCH] [PsrHttpMessageBridge] Support `php-http/discovery` for auto-detecting PSR-17 factories --- .../Bridge/PsrHttpMessage/CHANGELOG.md | 1 + .../PsrHttpMessage/Factory/PsrHttpFactory.php | 32 +++++++++-- .../Tests/Factory/PsrHttpFactoryTest.php | 54 +++++++++++-------- .../Bridge/PsrHttpMessage/composer.json | 8 ++- 4 files changed, 67 insertions(+), 28 deletions(-) diff --git a/src/Symfony/Bridge/PsrHttpMessage/CHANGELOG.md b/src/Symfony/Bridge/PsrHttpMessage/CHANGELOG.md index 392833d4ccb95..b53760aa67624 100644 --- a/src/Symfony/Bridge/PsrHttpMessage/CHANGELOG.md +++ b/src/Symfony/Bridge/PsrHttpMessage/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Import the bridge into the Symfony monorepo and synchronize releases * Remove `ArgumentValueResolverInterface` from `PsrServerRequestResolver` + * Support `php-http/discovery` for auto-detecting PSR-17 factories 2.3.1 ----- diff --git a/src/Symfony/Bridge/PsrHttpMessage/Factory/PsrHttpFactory.php b/src/Symfony/Bridge/PsrHttpMessage/Factory/PsrHttpFactory.php index 5e378d18d90c5..bf245602ab16f 100644 --- a/src/Symfony/Bridge/PsrHttpMessage/Factory/PsrHttpFactory.php +++ b/src/Symfony/Bridge/PsrHttpMessage/Factory/PsrHttpFactory.php @@ -11,6 +11,8 @@ namespace Symfony\Bridge\PsrHttpMessage\Factory; +use Http\Discovery\Psr17Factory as DiscoveryPsr17Factory; +use Nyholm\Psr7\Factory\Psr17Factory as NyholmPsr17Factory; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; @@ -33,12 +35,34 @@ */ class PsrHttpFactory implements HttpMessageFactoryInterface { + private readonly ServerRequestFactoryInterface $serverRequestFactory; + private readonly StreamFactoryInterface $streamFactory; + private readonly UploadedFileFactoryInterface $uploadedFileFactory; + private readonly ResponseFactoryInterface $responseFactory; + public function __construct( - private readonly ServerRequestFactoryInterface $serverRequestFactory, - private readonly StreamFactoryInterface $streamFactory, - private readonly UploadedFileFactoryInterface $uploadedFileFactory, - private readonly ResponseFactoryInterface $responseFactory, + ServerRequestFactoryInterface $serverRequestFactory = null, + StreamFactoryInterface $streamFactory = null, + UploadedFileFactoryInterface $uploadedFileFactory = null, + ResponseFactoryInterface $responseFactory = null, ) { + if (null === $serverRequestFactory || null === $streamFactory || null === $uploadedFileFactory || null === $responseFactory) { + $psr17Factory = match (true) { + class_exists(DiscoveryPsr17Factory::class) => new DiscoveryPsr17Factory(), + class_exists(NyholmPsr17Factory::class) => new NyholmPsr17Factory(), + default => throw new \LogicException(sprintf('You cannot use the "%s" as no PSR-17 factories have been provided. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', self::class)), + }; + + $serverRequestFactory ??= $psr17Factory; + $streamFactory ??= $psr17Factory; + $uploadedFileFactory ??= $psr17Factory; + $responseFactory ??= $psr17Factory; + } + + $this->serverRequestFactory = $serverRequestFactory; + $this->streamFactory = $streamFactory; + $this->uploadedFileFactory = $uploadedFileFactory; + $this->responseFactory = $responseFactory; } public function createRequest(Request $symfonyRequest): ServerRequestInterface diff --git a/src/Symfony/Bridge/PsrHttpMessage/Tests/Factory/PsrHttpFactoryTest.php b/src/Symfony/Bridge/PsrHttpMessage/Tests/Factory/PsrHttpFactoryTest.php index 238b17b7810b6..b0976cb468678 100644 --- a/src/Symfony/Bridge/PsrHttpMessage/Tests/Factory/PsrHttpFactoryTest.php +++ b/src/Symfony/Bridge/PsrHttpMessage/Tests/Factory/PsrHttpFactoryTest.php @@ -14,7 +14,6 @@ use Nyholm\Psr7\Factory\Psr17Factory; use PHPUnit\Framework\TestCase; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; -use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -29,23 +28,17 @@ */ class PsrHttpFactoryTest extends TestCase { - private HttpMessageFactoryInterface $factory; private string $tmpDir; - protected function buildHttpMessageFactory(): HttpMessageFactoryInterface - { - $factory = new Psr17Factory(); - - return new PsrHttpFactory($factory, $factory, $factory, $factory); - } - protected function setUp(): void { - $this->factory = $this->buildHttpMessageFactory(); $this->tmpDir = sys_get_temp_dir(); } - public function testCreateRequest() + /** + * @dataProvider provideFactories + */ + public function testCreateRequest(PsrHttpFactory $factory) { $stdClass = new \stdClass(); $request = new Request( @@ -83,7 +76,7 @@ public function testCreateRequest() ); $request->headers->set(' X-Broken', 'abc'); - $psrRequest = $this->factory->createRequest($request); + $psrRequest = $factory->createRequest($request); $this->assertSame('Content', $psrRequest->getBody()->__toString()); @@ -130,7 +123,7 @@ public function testGetContentCanBeCalledAfterRequestCreation() $header = ['HTTP_HOST' => 'dunglas.fr']; $request = new Request([], [], [], [], [], $header, 'Content'); - $psrRequest = $this->factory->createRequest($request); + $psrRequest = self::buildHttpMessageFactory()->createRequest($request); $this->assertSame('Content', $psrRequest->getBody()->__toString()); $this->assertSame('Content', $request->getContent()); @@ -144,7 +137,10 @@ private function createUploadedFile(string $content, string $originalName, strin return new UploadedFile($path, $originalName, $mimeType, $error, true); } - public function testCreateResponse() + /** + * @dataProvider provideFactories + */ + public function testCreateResponse(PsrHttpFactory $factory) { $response = new Response( 'Response content.', @@ -156,7 +152,7 @@ public function testCreateResponse() ); $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, 'lax')); - $psrResponse = $this->factory->createResponse($response); + $psrResponse = $factory->createResponse($response); $this->assertSame('Response content.', $psrResponse->getBody()->__toString()); $this->assertSame(202, $psrResponse->getStatusCode()); $this->assertSame(['3.4'], $psrResponse->getHeader('x-symfony')); @@ -179,7 +175,7 @@ public function testCreateResponseFromStreamed() flush(); }); - $psrResponse = $this->factory->createResponse($response); + $psrResponse = self::buildHttpMessageFactory()->createResponse($response); $this->assertSame("Line 1\nLine 2\n", $psrResponse->getBody()->__toString()); } @@ -191,7 +187,7 @@ public function testCreateResponseFromBinaryFile() $response = new BinaryFileResponse($path); - $psrResponse = $this->factory->createResponse($response); + $psrResponse = self::buildHttpMessageFactory()->createResponse($response); $this->assertSame('Binary', $psrResponse->getBody()->__toString()); } @@ -207,7 +203,7 @@ public function testCreateResponseFromBinaryFileWithRange() $response = new BinaryFileResponse($path, 200, ['Content-Type' => 'plain/text']); $response->prepare($request); - $psrResponse = $this->factory->createResponse($response); + $psrResponse = self::buildHttpMessageFactory()->createResponse($response); $this->assertSame('inar', $psrResponse->getBody()->__toString()); $this->assertSame('bytes 1-4/6', $psrResponse->getHeaderLine('Content-Range')); @@ -237,7 +233,7 @@ public function testUploadErrNoFile() 'Content' ); - $psrRequest = $this->factory->createRequest($request); + $psrRequest = self::buildHttpMessageFactory()->createRequest($request); $uploadedFiles = $psrRequest->getUploadedFiles(); @@ -256,7 +252,7 @@ public function testJsonContent() 'CONTENT_TYPE' => 'application/json', ]; $request = new Request([], [], [], [], [], $headers, '{"city":"Paris","country":"France"}'); - $psrRequest = $this->factory->createRequest($request); + $psrRequest = self::buildHttpMessageFactory()->createRequest($request); $this->assertSame(['city' => 'Paris', 'country' => 'France'], $psrRequest->getParsedBody()); } @@ -272,7 +268,7 @@ public function testEmptyJsonContent() 'CONTENT_TYPE' => 'application/json', ]; $request = new Request([], [], [], [], [], $headers, '{}'); - $psrRequest = $this->factory->createRequest($request); + $psrRequest = self::buildHttpMessageFactory()->createRequest($request); $this->assertSame([], $psrRequest->getParsedBody()); } @@ -288,8 +284,22 @@ public function testWrongJsonContent() 'CONTENT_TYPE' => 'application/json', ]; $request = new Request([], [], [], [], [], $headers, '{"city":"Paris"'); - $psrRequest = $this->factory->createRequest($request); + $psrRequest = self::buildHttpMessageFactory()->createRequest($request); $this->assertNull($psrRequest->getParsedBody()); } + + public static function provideFactories(): \Generator + { + yield 'Discovery' => [new PsrHttpFactory()]; + yield 'incomplete dependencies' => [new PsrHttpFactory(responseFactory: new Psr17Factory())]; + yield 'Nyholm' => [self::buildHttpMessageFactory()]; + } + + private static function buildHttpMessageFactory(): PsrHttpFactory + { + $factory = new Psr17Factory(); + + return new PsrHttpFactory($factory, $factory, $factory, $factory); + } } diff --git a/src/Symfony/Bridge/PsrHttpMessage/composer.json b/src/Symfony/Bridge/PsrHttpMessage/composer.json index 612de1228a1e0..9169a4b1ee9f9 100644 --- a/src/Symfony/Bridge/PsrHttpMessage/composer.json +++ b/src/Symfony/Bridge/PsrHttpMessage/composer.json @@ -27,13 +27,17 @@ "symfony/framework-bundle": "^6.2|^7.0", "symfony/http-kernel": "^6.2|^7.0", "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", "psr/log": "^1.1.4|^2|^3" }, "conflict": { + "php-http/discovery": "<1.15", "symfony/http-kernel": "<6.2" }, - "suggest": { - "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + "config": { + "allow-plugins": { + "php-http/discovery": false + } }, "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" },