Skip to content

[FrameworkBundle] Integrate PsrHttpMessageBridge #52372

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
use Doctrine\DBAL\Connection;
use Psr\Log\LogLevel;
use Seld\JsonLint\JsonParser;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
use Symfony\Bundle\FullStack;
use Symfony\Component\Asset\Package;
use Symfony\Component\AssetMapper\AssetMapper;
Expand Down Expand Up @@ -192,6 +194,7 @@ public function getConfigTreeBuilder(): TreeBuilder
$this->addHtmlSanitizerSection($rootNode, $enableIfStandalone);
$this->addWebhookSection($rootNode, $enableIfStandalone);
$this->addRemoteEventSection($rootNode, $enableIfStandalone);
$this->addPsrHttpMessageBridgeSection($rootNode, $willBeAvailable);

return $treeBuilder;
}
Expand Down Expand Up @@ -2584,4 +2587,24 @@ private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable
->end()
;
}

private function addPsrHttpMessageBridgeSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable): void
{
$enableMode = 'canBeEnabled';
if (!class_exists(FullStack::class)
&& $willBeAvailable('symfony/psr-http-message-bridge', HttpFoundationFactoryInterface::class)
&& 0 === (new \ReflectionClass(PsrHttpFactory::class))->getConstructor()->getNumberOfRequiredParameters()
) {
$enableMode = 'canBeDisabled';
}

$rootNode
->children()
->arrayNode('psr_http_message_bridge')
->info('PSR HTTP message bridge configuration')
->{$enableMode}()
->end()
->end()
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
use Psr\Http\Client\ClientInterface;
use Psr\Log\LoggerAwareInterface;
use Symfony\Bridge\Monolog\Processor\DebugProcessor;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
use Symfony\Bridge\Twig\Extension\CsrfExtension;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface;
Expand Down Expand Up @@ -576,6 +578,17 @@ public function load(array $configs, ContainerBuilder $container)
$this->registerHtmlSanitizerConfiguration($config['html_sanitizer'], $container, $loader);
}

if ($this->readConfigEnabled('psr_http_message_bridge', $container, $config['psr_http_message_bridge'])) {
if (!interface_exists(HttpFoundationFactoryInterface::class)) {
throw new LogicException('PSR HTTP Message support cannot be enabled as the bridge is not installed. Try running "composer require symfony/psr-http-message-bridge".');
}
if ((new \ReflectionClass(PsrHttpFactory::class))->getConstructor()->getNumberOfRequiredParameters() > 0) {
throw new LogicException('PSR HTTP Message support cannot be enabled for version 2 or earlier. Please update symfony/psr-http-message-bridge to 6.4 or wire all services manually.');
}

$loader->load('psr_http_message_bridge.php');
}

$this->addAnnotatedClassesToCompile([
'**\\Controller\\',
'**\\Entity\\',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
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;

return static function (ContainerConfigurator $container) {
$container->services()
->set('psr_http_message_bridge.http_foundation_factory', HttpFoundationFactory::class)
->alias(HttpFoundationFactoryInterface::class, 'psr_http_message_bridge.http_foundation_factory')

->set('psr_http_message_bridge.psr_http_factory', PsrHttpFactory::class)
->args([
service(ServerRequestFactoryInterface::class)->nullOnInvalid(),
service(StreamFactoryInterface::class)->nullOnInvalid(),
service(UploadedFileFactoryInterface::class)->nullOnInvalid(),
service(ResponseFactoryInterface::class)->nullOnInvalid(),
])
->alias(HttpMessageFactoryInterface::class, 'psr_http_message_bridge.psr_http_factory')

->set('psr_http_message_bridge.psr_server_request_resolver', PsrServerRequestResolver::class)
->args([service('psr_http_message_bridge.psr_http_factory')])
->tag('controller.argument_value_resolver', ['priority' => -100])
Copy link
Member

@nicolas-grekas nicolas-grekas Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to run this PR to see how it goes. I did install a webapp-pack and added the psr bridge.
Then I created a controller and added a PSR RequestInterface as argument.
Because this priority is below ServiceValueResolver , I've an error telling me no such service.
Then I changed the -100 to 50 (same level as RequestValueResolver), and the error is now "no PSR-17 factories have been provided."
This is consistent with the fact that the bridge is ... a bridge between Symfony and ... a library that needs to be installed.
A solution to this could be to require php-http/discovery + psr/http-factory-implementation. That's the only way for us to provide a nice out of the box experience.

-- or --
we keep the psr7-pack + its recipe? 👼


->set('psr_http_message_bridge.psr_response_listener', PsrResponseListener::class)
->args([
service('psr_http_message_bridge.http_foundation_factory'),
])
->tag('kernel.event_subscriber')
;
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Doctrine\DBAL\Connection;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Configuration;
use Symfony\Bundle\FullStack;
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
Expand Down Expand Up @@ -46,7 +47,7 @@ public function testDefaultConfig()
$this->assertEquals(self::getBundleDefaultConfig(), $config);
}

public function getTestValidSessionName()
public function getTestValidSessionName(): array
{
return [
[null],
Expand Down Expand Up @@ -529,7 +530,7 @@ public function testEnabledLockNeedsResources()
]);
}

protected static function getBundleDefaultConfig()
protected static function getBundleDefaultConfig(): array
{
return [
'http_method_override' => false,
Expand Down Expand Up @@ -787,6 +788,9 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
'remote-event' => [
'enabled' => false,
],
'psr_http_message_bridge' => [
'enabled' => !class_exists(FullStack::class) && 0 === (new \ReflectionClass(PsrHttpFactory::class))->getConstructor()->getNumberOfRequiredParameters(),
],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;

final class PsrHttpMessageController
{
public function __construct(
private readonly ResponseFactoryInterface&StreamFactoryInterface $factory,
) {
}

public function __invoke(ServerRequestInterface $request): ResponseInterface
{
$responsePayload = json_encode([
'message' => sprintf('Hello %s!', $request->getQueryParams()['name'] ?? 'World'),
], \JSON_THROW_ON_ERROR);

return $this->factory->createResponse()
->withHeader('Content-Type', 'application/json')
->withBody($this->factory->createStream($responsePayload))
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ http_client_call:
path: /http_client_call
defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\HttpClientController::index }

psr_http_message_bridge:
path: /psr_http
controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\PsrHttpMessageController

uid:
resource: "../../Controller/UidController.php"
type: "attribute"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;

use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;

class PsrHttpMessageBridgeTest extends AbstractWebTestCase
{
public function testBridgeIntegration()
{
if ((new \ReflectionClass(PsrHttpFactory::class))->getConstructor()->getNumberOfRequiredParameters() > 0) {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('PSR HTTP Message support cannot be enabled for version 2 or earlier. Please update symfony/psr-http-message-bridge to 6.4 or wire all services manually.');
}

$client = $this->createClient(['test_case' => 'PsrHttpMessageBridge', 'root_config' => 'config.yml', 'debug' => true]);
$client->request('GET', '/psr_http?name=Symfony');

$this->assertResponseIsSuccessful();
$this->assertResponseHeaderSame('content-type', 'application/json');
$this->assertJsonStringEqualsJsonString('{"message":"Hello Symfony!"}', $client->getResponse()->getContent());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle;

return [
new FrameworkBundle(),
new TestBundle(),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
imports:
- { resource: ../config/default.yml }
- { resource: services.yml }

framework:
http_method_override: false
profiler: ~
psr_http_message_bridge:
enabled: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_emailtest_bundle:
resource: '@TestBundle/Resources/config/routing.yml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
nyholm_psr7_factory:
class: Nyholm\Psr7\Factory\Psr17Factory

Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\PsrHttpMessageController:
public: true
arguments: ['@nyholm_psr7_factory']
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"doctrine/annotations": "^1.13.1|^2",
"doctrine/persistence": "^1.3|^2|^3",
"dragonmantank/cron-expression": "^3.1",
"nyholm/psr7": "^1",
"seld/jsonlint": "^1.10",
"symfony/asset": "^5.4|^6.0|^7.0",
"symfony/asset-mapper": "^6.4|^7.0",
Expand All @@ -56,6 +57,7 @@
"symfony/mime": "^6.4|^7.0",
"symfony/notifier": "^5.4|^6.0|^7.0",
"symfony/process": "^5.4|^6.0|^7.0",
"symfony/psr-http-message-bridge": "^2.3|^6.4|^7.0",
"symfony/rate-limiter": "^5.4|^6.0|^7.0",
"symfony/scheduler": "^6.4|^7.0",
"symfony/security-bundle": "^5.4|^6.0|^7.0",
Expand Down