Skip to content

Commit 81cf26b

Browse files
committed
[PsrHttpMessageBridge] Support php-http/discovery for auto-detecting PSR-17 factories
1 parent fa0d781 commit 81cf26b

File tree

4 files changed

+67
-28
lines changed

4 files changed

+67
-28
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Import the bridge into the Symfony monorepo and synchronize releases
88
* Remove `ArgumentValueResolverInterface` from `PsrServerRequestResolver`
9+
* Support `php-http/discovery` for auto-detecting PSR-17 factories
910

1011
2.3.1
1112
-----

Factory/PsrHttpFactory.php

+28-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Bridge\PsrHttpMessage\Factory;
1313

14+
use Http\Discovery\Psr17Factory as DiscoveryPsr17Factory;
15+
use Nyholm\Psr7\Factory\Psr17Factory as NyholmPsr17Factory;
1416
use Psr\Http\Message\ResponseFactoryInterface;
1517
use Psr\Http\Message\ResponseInterface;
1618
use Psr\Http\Message\ServerRequestFactoryInterface;
@@ -33,12 +35,34 @@
3335
*/
3436
class PsrHttpFactory implements HttpMessageFactoryInterface
3537
{
38+
private readonly ServerRequestFactoryInterface $serverRequestFactory;
39+
private readonly StreamFactoryInterface $streamFactory;
40+
private readonly UploadedFileFactoryInterface $uploadedFileFactory;
41+
private readonly ResponseFactoryInterface $responseFactory;
42+
3643
public function __construct(
37-
private readonly ServerRequestFactoryInterface $serverRequestFactory,
38-
private readonly StreamFactoryInterface $streamFactory,
39-
private readonly UploadedFileFactoryInterface $uploadedFileFactory,
40-
private readonly ResponseFactoryInterface $responseFactory,
44+
ServerRequestFactoryInterface $serverRequestFactory = null,
45+
StreamFactoryInterface $streamFactory = null,
46+
UploadedFileFactoryInterface $uploadedFileFactory = null,
47+
ResponseFactoryInterface $responseFactory = null,
4148
) {
49+
if (null === $serverRequestFactory || null === $streamFactory || null === $uploadedFileFactory || null === $responseFactory) {
50+
$psr17Factory = match (true) {
51+
class_exists(DiscoveryPsr17Factory::class) => new DiscoveryPsr17Factory(),
52+
class_exists(NyholmPsr17Factory::class) => new NyholmPsr17Factory(),
53+
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)),
54+
};
55+
56+
$serverRequestFactory ??= $psr17Factory;
57+
$streamFactory ??= $psr17Factory;
58+
$uploadedFileFactory ??= $psr17Factory;
59+
$responseFactory ??= $psr17Factory;
60+
}
61+
62+
$this->serverRequestFactory = $serverRequestFactory;
63+
$this->streamFactory = $streamFactory;
64+
$this->uploadedFileFactory = $uploadedFileFactory;
65+
$this->responseFactory = $responseFactory;
4266
}
4367

4468
public function createRequest(Request $symfonyRequest): ServerRequestInterface

Tests/Factory/PsrHttpFactoryTest.php

+32-22
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Nyholm\Psr7\Factory\Psr17Factory;
1515
use PHPUnit\Framework\TestCase;
1616
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
17-
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
1817
use Symfony\Component\HttpFoundation\BinaryFileResponse;
1918
use Symfony\Component\HttpFoundation\Cookie;
2019
use Symfony\Component\HttpFoundation\File\UploadedFile;
@@ -29,23 +28,17 @@
2928
*/
3029
class PsrHttpFactoryTest extends TestCase
3130
{
32-
private HttpMessageFactoryInterface $factory;
3331
private string $tmpDir;
3432

35-
protected function buildHttpMessageFactory(): HttpMessageFactoryInterface
36-
{
37-
$factory = new Psr17Factory();
38-
39-
return new PsrHttpFactory($factory, $factory, $factory, $factory);
40-
}
41-
4233
protected function setUp(): void
4334
{
44-
$this->factory = $this->buildHttpMessageFactory();
4535
$this->tmpDir = sys_get_temp_dir();
4636
}
4737

48-
public function testCreateRequest()
38+
/**
39+
* @dataProvider provideFactories
40+
*/
41+
public function testCreateRequest(PsrHttpFactory $factory)
4942
{
5043
$stdClass = new \stdClass();
5144
$request = new Request(
@@ -83,7 +76,7 @@ public function testCreateRequest()
8376
);
8477
$request->headers->set(' X-Broken', 'abc');
8578

86-
$psrRequest = $this->factory->createRequest($request);
79+
$psrRequest = $factory->createRequest($request);
8780

8881
$this->assertSame('Content', $psrRequest->getBody()->__toString());
8982

@@ -130,7 +123,7 @@ public function testGetContentCanBeCalledAfterRequestCreation()
130123
$header = ['HTTP_HOST' => 'dunglas.fr'];
131124
$request = new Request([], [], [], [], [], $header, 'Content');
132125

133-
$psrRequest = $this->factory->createRequest($request);
126+
$psrRequest = self::buildHttpMessageFactory()->createRequest($request);
134127

135128
$this->assertSame('Content', $psrRequest->getBody()->__toString());
136129
$this->assertSame('Content', $request->getContent());
@@ -144,7 +137,10 @@ private function createUploadedFile(string $content, string $originalName, strin
144137
return new UploadedFile($path, $originalName, $mimeType, $error, true);
145138
}
146139

147-
public function testCreateResponse()
140+
/**
141+
* @dataProvider provideFactories
142+
*/
143+
public function testCreateResponse(PsrHttpFactory $factory)
148144
{
149145
$response = new Response(
150146
'Response content.',
@@ -156,7 +152,7 @@ public function testCreateResponse()
156152
);
157153
$response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, 'lax'));
158154

159-
$psrResponse = $this->factory->createResponse($response);
155+
$psrResponse = $factory->createResponse($response);
160156
$this->assertSame('Response content.', $psrResponse->getBody()->__toString());
161157
$this->assertSame(202, $psrResponse->getStatusCode());
162158
$this->assertSame(['3.4'], $psrResponse->getHeader('x-symfony'));
@@ -179,7 +175,7 @@ public function testCreateResponseFromStreamed()
179175
flush();
180176
});
181177

182-
$psrResponse = $this->factory->createResponse($response);
178+
$psrResponse = self::buildHttpMessageFactory()->createResponse($response);
183179

184180
$this->assertSame("Line 1\nLine 2\n", $psrResponse->getBody()->__toString());
185181
}
@@ -191,7 +187,7 @@ public function testCreateResponseFromBinaryFile()
191187

192188
$response = new BinaryFileResponse($path);
193189

194-
$psrResponse = $this->factory->createResponse($response);
190+
$psrResponse = self::buildHttpMessageFactory()->createResponse($response);
195191

196192
$this->assertSame('Binary', $psrResponse->getBody()->__toString());
197193
}
@@ -207,7 +203,7 @@ public function testCreateResponseFromBinaryFileWithRange()
207203
$response = new BinaryFileResponse($path, 200, ['Content-Type' => 'plain/text']);
208204
$response->prepare($request);
209205

210-
$psrResponse = $this->factory->createResponse($response);
206+
$psrResponse = self::buildHttpMessageFactory()->createResponse($response);
211207

212208
$this->assertSame('inar', $psrResponse->getBody()->__toString());
213209
$this->assertSame('bytes 1-4/6', $psrResponse->getHeaderLine('Content-Range'));
@@ -237,7 +233,7 @@ public function testUploadErrNoFile()
237233
'Content'
238234
);
239235

240-
$psrRequest = $this->factory->createRequest($request);
236+
$psrRequest = self::buildHttpMessageFactory()->createRequest($request);
241237

242238
$uploadedFiles = $psrRequest->getUploadedFiles();
243239

@@ -256,7 +252,7 @@ public function testJsonContent()
256252
'CONTENT_TYPE' => 'application/json',
257253
];
258254
$request = new Request([], [], [], [], [], $headers, '{"city":"Paris","country":"France"}');
259-
$psrRequest = $this->factory->createRequest($request);
255+
$psrRequest = self::buildHttpMessageFactory()->createRequest($request);
260256

261257
$this->assertSame(['city' => 'Paris', 'country' => 'France'], $psrRequest->getParsedBody());
262258
}
@@ -272,7 +268,7 @@ public function testEmptyJsonContent()
272268
'CONTENT_TYPE' => 'application/json',
273269
];
274270
$request = new Request([], [], [], [], [], $headers, '{}');
275-
$psrRequest = $this->factory->createRequest($request);
271+
$psrRequest = self::buildHttpMessageFactory()->createRequest($request);
276272

277273
$this->assertSame([], $psrRequest->getParsedBody());
278274
}
@@ -288,8 +284,22 @@ public function testWrongJsonContent()
288284
'CONTENT_TYPE' => 'application/json',
289285
];
290286
$request = new Request([], [], [], [], [], $headers, '{"city":"Paris"');
291-
$psrRequest = $this->factory->createRequest($request);
287+
$psrRequest = self::buildHttpMessageFactory()->createRequest($request);
292288

293289
$this->assertNull($psrRequest->getParsedBody());
294290
}
291+
292+
public static function provideFactories(): \Generator
293+
{
294+
yield 'Discovery' => [new PsrHttpFactory()];
295+
yield 'incomplete dependencies' => [new PsrHttpFactory(responseFactory: new Psr17Factory())];
296+
yield 'Nyholm' => [self::buildHttpMessageFactory()];
297+
}
298+
299+
private static function buildHttpMessageFactory(): PsrHttpFactory
300+
{
301+
$factory = new Psr17Factory();
302+
303+
return new PsrHttpFactory($factory, $factory, $factory, $factory);
304+
}
295305
}

composer.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,17 @@
2727
"symfony/framework-bundle": "^6.2|^7.0",
2828
"symfony/http-kernel": "^6.2|^7.0",
2929
"nyholm/psr7": "^1.1",
30+
"php-http/discovery": "^1.15",
3031
"psr/log": "^1.1.4|^2|^3"
3132
},
3233
"conflict": {
34+
"php-http/discovery": "<1.15",
3335
"symfony/http-kernel": "<6.2"
3436
},
35-
"suggest": {
36-
"nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
37+
"config": {
38+
"allow-plugins": {
39+
"php-http/discovery": false
40+
}
3741
},
3842
"autoload": {
3943
"psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" },

0 commit comments

Comments
 (0)