diff --git a/.gitattributes b/.gitattributes index 14c3c35..2df4520 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,6 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.git* export-ignore +/.gitattributes export-ignore +/.github export-ignore +/.gitignore export-ignore +/.php_cs.dist export-ignore diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 4689c4d..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,8 +0,0 @@ -Please do not submit any Pull Requests here. They will be closed. ---- - -Please submit your PR here instead: -https://github.com/symfony/symfony - -This repository is what we call a "subtree split": a read-only subset of that main repository. -We're looking forward to your PR there! diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6a67387 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: CI + +on: + pull_request: + push: + +jobs: + test: + name: 'Test ${{ matrix.deps }} on PHP ${{ matrix.php }}' + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + include: + - php: '7.4' + deps: lowest + deprecations: max[self]=0 + - php: '8.1' + deps: highest + deprecations: max[indirect]=5 + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '${{ matrix.php }}' + coverage: none + + - name: Configure composer + if: "${{ matrix.deps == 'highest' }}" + run: composer config minimum-stability dev + + - name: Composer install + uses: ramsey/composer-install@v1 + with: + dependency-versions: '${{ matrix.deps }}' + + - name: Install PHPUnit + run: vendor/bin/simple-phpunit install + + - name: Run tests + run: vendor/bin/simple-phpunit + env: + SYMFONY_DEPRECATIONS_HELPER: '${{ matrix.deprecations }}' diff --git a/.github/workflows/close-pull-request.yml b/.github/workflows/close-pull-request.yml deleted file mode 100644 index e55b478..0000000 --- a/.github/workflows/close-pull-request.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Close Pull Request - -on: - pull_request_target: - types: [opened] - -jobs: - run: - runs-on: ubuntu-latest - steps: - - uses: superbrothers/close-pull-request@v3 - with: - comment: | - Thanks for your Pull Request! We love contributions. - - However, you should instead open your PR on the main repository: - https://github.com/symfony/symfony - - This repository is what we call a "subtree split": a read-only subset of that main repository. - We're looking forward to your PR there! diff --git a/.gitignore b/.gitignore index d4bfce0..2fb17b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ vendor/ composer.lock phpunit.xml +.php-cs-fixer.cache +.php-cs-fixer.php +.phpunit.result.cache /Tests/Fixtures/App/var diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..e9b256a --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,25 @@ +setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHPUnit48Migration:risky' => true, + 'php_unit_no_expectation_annotation' => false, // part of `PHPUnitXYMigration:risky` ruleset, to be enabled when PHPUnit 4.x support will be dropped, as we don't want to rewrite exceptions handling twice + 'array_syntax' => ['syntax' => 'short'], + 'fopen_flags' => false, + 'ordered_imports' => true, + 'protected_to_private' => false, + // Part of @Symfony:risky in PHP-CS-Fixer 2.13.0. To be removed from the config file once upgrading + 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced'], + // Part of future @Symfony ruleset in PHP-CS-Fixer To be removed from the config file once upgrading + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + ]) + ->setRiskyAllowed(true) + ->setFinder( + (new PhpCsFixer\Finder()) + ->in(__DIR__) + ->exclude('vendor') + ->name('*.php') + ) +; diff --git a/ArgumentValueResolver/PsrServerRequestResolver.php b/ArgumentValueResolver/PsrServerRequestResolver.php index 79fefd8..61cd8c5 100644 --- a/ArgumentValueResolver/PsrServerRequestResolver.php +++ b/ArgumentValueResolver/PsrServerRequestResolver.php @@ -16,7 +16,8 @@ use Psr\Http\Message\ServerRequestInterface; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface as BaseValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -25,7 +26,7 @@ * @author Iltar van der Berg * @author Alexander M. Turek */ -final class PsrServerRequestResolver implements ValueResolverInterface +final class PsrServerRequestResolver implements ArgumentValueResolverInterface, ValueResolverInterface { private const SUPPORTED_TYPES = [ ServerRequestInterface::class => true, @@ -33,11 +34,28 @@ final class PsrServerRequestResolver implements ValueResolverInterface MessageInterface::class => true, ]; - public function __construct( - private readonly HttpMessageFactoryInterface $httpMessageFactory, - ) { + private $httpMessageFactory; + + public function __construct(HttpMessageFactoryInterface $httpMessageFactory) + { + $this->httpMessageFactory = $httpMessageFactory; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument): bool + { + if ($this instanceof BaseValueResolverInterface) { + trigger_deprecation('symfony/psr-http-message-bridge', '2.3', 'Method "%s" is deprecated, call "resolve()" without calling "supports()" first.', __METHOD__); + } + + return self::SUPPORTED_TYPES[$argument->getType()] ?? false; } + /** + * {@inheritdoc} + */ public function resolve(Request $request, ArgumentMetadata $argument): \Traversable { if (!isset(self::SUPPORTED_TYPES[$argument->getType()])) { diff --git a/ArgumentValueResolver/ValueResolverInterface.php b/ArgumentValueResolver/ValueResolverInterface.php new file mode 100644 index 0000000..83a321a --- /dev/null +++ b/ArgumentValueResolver/ValueResolverInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver; + +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface as BaseValueResolverInterface; + +if (interface_exists(BaseValueResolverInterface::class)) { + /** @internal */ + interface ValueResolverInterface extends BaseValueResolverInterface + { + } +} else { + /** @internal */ + interface ValueResolverInterface + { + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index b53760a..f32c06f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,109 +1,85 @@ CHANGELOG ========= -6.4 ---- +# 2.3.1 (2023-07-26) - * Import the bridge into the Symfony monorepo and synchronize releases - * Remove `ArgumentValueResolverInterface` from `PsrServerRequestResolver` - * Support `php-http/discovery` for auto-detecting PSR-17 factories +* Don't rely on `Request::getPayload()` to populate the parsed body -2.3.1 ------ +# 2.3.0 (2023-07-25) - * Don't rely on `Request::getPayload()` to populate the parsed body +* Leverage `Request::getPayload()` to populate the parsed body of PSR-7 requests +* Implement `ValueResolverInterface` introduced with Symfony 6.2 -2.3.0 ------ +# 2.2.0 (2023-04-21) - * Leverage `Request::getPayload()` to populate the parsed body of PSR-7 requests - * Implement `ValueResolverInterface` introduced with Symfony 6.2 +* Drop support for Symfony 4 +* Bump minimum version of PHP to 7.2 +* Support version 2 of the psr/http-message contracts -2.2.0 ------ +# 2.1.3 (2022-09-05) - * Drop support for Symfony 4 - * Bump minimum version of PHP to 7.2 - * Support version 2 of the psr/http-message contracts +* Ignore invalid HTTP headers when creating PSR7 objects +* Fix for wrong type passed to `moveTo()` -2.1.3 ------ +# 2.1.2 (2021-11-05) - * Ignore invalid HTTP headers when creating PSR7 objects - * Fix for wrong type passed to `moveTo()` +* Allow Symfony 6 -2.1.2 ------ +# 2.1.0 (2021-02-17) - * Allow Symfony 6 + * Added a `PsrResponseListener` to automatically convert PSR-7 responses returned by controllers + * Added a `PsrServerRequestResolver` that allows injecting PSR-7 request objects into controllers -2.1.0 ------ +# 2.0.2 (2020-09-29) - * Added a `PsrResponseListener` to automatically convert PSR-7 responses returned by controllers - * Added a `PsrServerRequestResolver` that allows injecting PSR-7 request objects into controllers + * Fix populating server params from URI in HttpFoundationFactory + * Create cookies as raw in HttpFoundationFactory + * Fix BinaryFileResponse with Content-Range PsrHttpFactory -2.0.2 ------ +# 2.0.1 (2020-06-25) - * Fix populating server params from URI in HttpFoundationFactory - * Create cookies as raw in HttpFoundationFactory - * Fix BinaryFileResponse with Content-Range PsrHttpFactory + * Don't normalize query string in PsrHttpFactory + * Fix conversion for HTTPS requests + * Fix populating default port and headers in HttpFoundationFactory -2.0.1 ------ +# 2.0.0 (2020-01-02) - * Don't normalize query string in PsrHttpFactory - * Fix conversion for HTTPS requests - * Fix populating default port and headers in HttpFoundationFactory + * Remove DiactorosFactory -2.0.0 ------ +# 1.3.0 (2019-11-25) - * Remove DiactorosFactory + * Added support for streamed requests + * Added support for Symfony 5.0+ + * Fixed bridging UploadedFile objects + * Bumped minimum version of Symfony to 4.4 -1.3.0 ------ +# 1.2.0 (2019-03-11) - * Added support for streamed requests - * Added support for Symfony 5.0+ - * Fixed bridging UploadedFile objects - * Bumped minimum version of Symfony to 4.4 + * Added new documentation links + * Bumped minimum version of PHP to 7.1 + * Added support for streamed responses -1.2.0 ------ +# 1.1.2 (2019-04-03) - * Added new documentation links - * Bumped minimum version of PHP to 7.1 - * Added support for streamed responses + * Fixed createResponse -1.1.2 ------ +# 1.1.1 (2019-03-11) - * Fixed createResponse + * Deprecated DiactorosFactory, use PsrHttpFactory instead + * Removed triggering of deprecation -1.1.1 ------ +# 1.1.0 (2018-08-30) - * Deprecated DiactorosFactory, use PsrHttpFactory instead - * Removed triggering of deprecation + * Added support for creating PSR-7 messages using PSR-17 factories -1.1.0 ------ +# 1.0.2 (2017-12-19) - * Added support for creating PSR-7 messages using PSR-17 factories + * Fixed request target in PSR7 Request (mtibben) -1.0.2 ------ +# 1.0.1 (2017-12-04) - * Fixed request target in PSR7 Request (mtibben) + * Added support for Symfony 4 (dunglas) -1.0.1 ------ +# 1.0.0 (2016-09-14) - * Added support for Symfony 4 (dunglas) - -1.0.0 ------ - - * Initial release + * Initial release diff --git a/EventListener/PsrResponseListener.php b/EventListener/PsrResponseListener.php index dd7ef6c..ee0e047 100644 --- a/EventListener/PsrResponseListener.php +++ b/EventListener/PsrResponseListener.php @@ -1,14 +1,5 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Symfony\Bridge\PsrHttpMessage\EventListener; use Psr\Http\Message\ResponseInterface; @@ -26,9 +17,9 @@ */ final class PsrResponseListener implements EventSubscriberInterface { - private readonly HttpFoundationFactoryInterface $httpFoundationFactory; + private $httpFoundationFactory; - public function __construct(?HttpFoundationFactoryInterface $httpFoundationFactory = null) + public function __construct(HttpFoundationFactoryInterface $httpFoundationFactory = null) { $this->httpFoundationFactory = $httpFoundationFactory ?? new HttpFoundationFactory(); } @@ -47,6 +38,9 @@ public function onKernelView(ViewEvent $event): void $event->setResponse($this->httpFoundationFactory->createResponse($controllerResult)); } + /** + * {@inheritdoc} + */ public static function getSubscribedEvents(): array { return [ diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 3c3e272..a69e8ff 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -15,6 +15,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; +use Psr\Http\Message\UriInterface; use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; @@ -22,34 +23,45 @@ use Symfony\Component\HttpFoundation\StreamedResponse; /** + * {@inheritdoc} + * * @author Kévin Dunglas */ class HttpFoundationFactory implements HttpFoundationFactoryInterface { /** - * @param int $responseBufferMaxLength The maximum output buffering size for each iteration when sending the response + * @var int The maximum output buffering size for each iteration when sending the response */ - public function __construct( - private readonly int $responseBufferMaxLength = 16372, - ) { + private $responseBufferMaxLength; + + public function __construct(int $responseBufferMaxLength = 16372) + { + $this->responseBufferMaxLength = $responseBufferMaxLength; } - public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false): Request + /** + * {@inheritdoc} + * + * @return Request + */ + public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false) { $server = []; $uri = $psrRequest->getUri(); - $server['SERVER_NAME'] = $uri->getHost(); - $server['SERVER_PORT'] = $uri->getPort() ?: ('https' === $uri->getScheme() ? 443 : 80); - $server['REQUEST_URI'] = $uri->getPath(); - $server['QUERY_STRING'] = $uri->getQuery(); + if ($uri instanceof UriInterface) { + $server['SERVER_NAME'] = $uri->getHost(); + $server['SERVER_PORT'] = $uri->getPort() ?: ('https' === $uri->getScheme() ? 443 : 80); + $server['REQUEST_URI'] = $uri->getPath(); + $server['QUERY_STRING'] = $uri->getQuery(); - if ('' !== $server['QUERY_STRING']) { - $server['REQUEST_URI'] .= '?'.$server['QUERY_STRING']; - } + if ('' !== $server['QUERY_STRING']) { + $server['REQUEST_URI'] .= '?'.$server['QUERY_STRING']; + } - if ('https' === $uri->getScheme()) { - $server['HTTPS'] = 'on'; + if ('https' === $uri->getScheme()) { + $server['HTTPS'] = 'on'; + } } $server['REQUEST_METHOD'] = $psrRequest->getMethod(); @@ -101,13 +113,20 @@ private function createUploadedFile(UploadedFileInterface $psrUploadedFile): Upl /** * Gets a temporary file path. + * + * @return string */ - protected function getTemporaryPath(): string + protected function getTemporaryPath() { - return tempnam(sys_get_temp_dir(), 'symfony'); + return tempnam(sys_get_temp_dir(), uniqid('symfony', true)); } - public function createResponse(ResponseInterface $psrResponse, bool $streamed = false): Response + /** + * {@inheritdoc} + * + * @return Response + */ + public function createResponse(ResponseInterface $psrResponse, bool $streamed = false) { $cookies = $psrResponse->getHeader('Set-Cookie'); $psrResponse = $psrResponse->withoutHeader('Set-Cookie'); @@ -129,12 +148,89 @@ public function createResponse(ResponseInterface $psrResponse, bool $streamed = $response->setProtocolVersion($psrResponse->getProtocolVersion()); foreach ($cookies as $cookie) { - $response->headers->setCookie(Cookie::fromString($cookie)); + $response->headers->setCookie($this->createCookie($cookie)); } return $response; } + /** + * Creates a Cookie instance from a cookie string. + * + * Some snippets have been taken from the Guzzle project: https://github.com/guzzle/guzzle/blob/5.3/src/Cookie/SetCookie.php#L34 + * + * @throws \InvalidArgumentException + */ + private function createCookie(string $cookie): Cookie + { + foreach (explode(';', $cookie) as $part) { + $part = trim($part); + + $data = explode('=', $part, 2); + $name = $data[0]; + $value = isset($data[1]) ? trim($data[1], " \n\r\t\0\x0B\"") : null; + + if (!isset($cookieName)) { + $cookieName = $name; + $cookieValue = $value; + + continue; + } + + if ('expires' === strtolower($name) && null !== $value) { + $cookieExpire = new \DateTime($value); + + continue; + } + + if ('path' === strtolower($name) && null !== $value) { + $cookiePath = $value; + + continue; + } + + if ('domain' === strtolower($name) && null !== $value) { + $cookieDomain = $value; + + continue; + } + + if ('secure' === strtolower($name)) { + $cookieSecure = true; + + continue; + } + + if ('httponly' === strtolower($name)) { + $cookieHttpOnly = true; + + continue; + } + + if ('samesite' === strtolower($name) && null !== $value) { + $samesite = $value; + + continue; + } + } + + if (!isset($cookieName)) { + throw new \InvalidArgumentException('The value of the Set-Cookie header is malformed.'); + } + + return new Cookie( + $cookieName, + $cookieValue, + $cookieExpire ?? 0, + $cookiePath ?? '/', + $cookieDomain ?? null, + isset($cookieSecure), + isset($cookieHttpOnly), + true, + $samesite ?? null + ); + } + private function createStreamedResponseCallback(StreamInterface $body): callable { return function () use ($body) { diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index d3b5467..09c4360 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -11,8 +11,6 @@ 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; @@ -35,37 +33,25 @@ */ class PsrHttpFactory implements HttpMessageFactoryInterface { - private readonly ServerRequestFactoryInterface $serverRequestFactory; - private readonly StreamFactoryInterface $streamFactory; - private readonly UploadedFileFactoryInterface $uploadedFileFactory; - private readonly ResponseFactoryInterface $responseFactory; - - public function __construct( - ?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; - } + private $serverRequestFactory; + private $streamFactory; + private $uploadedFileFactory; + private $responseFactory; + public function __construct(ServerRequestFactoryInterface $serverRequestFactory, StreamFactoryInterface $streamFactory, UploadedFileFactoryInterface $uploadedFileFactory, ResponseFactoryInterface $responseFactory) + { $this->serverRequestFactory = $serverRequestFactory; $this->streamFactory = $streamFactory; $this->uploadedFileFactory = $uploadedFileFactory; $this->responseFactory = $responseFactory; } - public function createRequest(Request $symfonyRequest): ServerRequestInterface + /** + * {@inheritdoc} + * + * @return ServerRequestInterface + */ + public function createRequest(Request $symfonyRequest) { $uri = $symfonyRequest->server->get('QUERY_STRING', ''); $uri = $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getBaseUrl().$symfonyRequest->getPathInfo().('' !== $uri ? '?'.$uri : ''); @@ -85,7 +71,12 @@ public function createRequest(Request $symfonyRequest): ServerRequestInterface } $body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true)); - $format = $symfonyRequest->getContentTypeFormat(); + + if (method_exists(Request::class, 'getContentTypeFormat')) { + $format = $symfonyRequest->getContentTypeFormat(); + } else { + $format = $symfonyRequest->getContentType(); + } if ('json' === $format) { $parsedBody = json_decode($symfonyRequest->getContent(), true, 512, \JSON_BIGINT_AS_STRING); @@ -150,7 +141,12 @@ private function createUploadedFile(UploadedFile $symfonyUploadedFile): Uploaded ); } - public function createResponse(Response $symfonyResponse): ResponseInterface + /** + * {@inheritdoc} + * + * @return ResponseInterface + */ + public function createResponse(Response $symfonyResponse) { $response = $this->responseFactory->createResponse($symfonyResponse->getStatusCode(), Response::$statusTexts[$symfonyResponse->getStatusCode()] ?? ''); @@ -178,7 +174,7 @@ public function createResponse(Response $symfonyResponse): ResponseInterface $headers = $symfonyResponse->headers->all(); $cookies = $symfonyResponse->headers->getCookies(); - if ($cookies) { + if (!empty($cookies)) { $headers['Set-Cookie'] = []; foreach ($cookies as $cookie) { @@ -195,7 +191,8 @@ public function createResponse(Response $symfonyResponse): ResponseInterface } $protocolVersion = $symfonyResponse->getProtocolVersion(); + $response = $response->withProtocolVersion($protocolVersion); - return $response->withProtocolVersion($protocolVersion); + return $response; } } diff --git a/Factory/UploadedFile.php b/Factory/UploadedFile.php index 34d4058..4d38a1f 100644 --- a/Factory/UploadedFile.php +++ b/Factory/UploadedFile.php @@ -21,12 +21,11 @@ */ class UploadedFile extends BaseUploadedFile { - private bool $test = false; + private $psrUploadedFile; + private $test = false; - public function __construct( - private readonly UploadedFileInterface $psrUploadedFile, - callable $getTemporaryPath, - ) { + public function __construct(UploadedFileInterface $psrUploadedFile, callable $getTemporaryPath) + { $error = $psrUploadedFile->getError(); $path = ''; @@ -46,9 +45,14 @@ public function __construct( $psrUploadedFile->getError(), $this->test ); + + $this->psrUploadedFile = $psrUploadedFile; } - public function move(string $directory, ?string $name = null): File + /** + * {@inheritdoc} + */ + public function move(string $directory, string $name = null): File { if (!$this->isValid() || $this->test) { return parent::move($directory, $name); @@ -59,7 +63,7 @@ public function move(string $directory, ?string $name = null): File try { $this->psrUploadedFile->moveTo((string) $target); } catch (\RuntimeException $e) { - throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, $e->getMessage()), 0, $e); + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, $e->getMessage()), 0, $e); } @chmod($target, 0666 & ~umask()); diff --git a/HttpFoundationFactoryInterface.php b/HttpFoundationFactoryInterface.php index 2bf5e38..a3f9043 100644 --- a/HttpFoundationFactoryInterface.php +++ b/HttpFoundationFactoryInterface.php @@ -25,11 +25,15 @@ interface HttpFoundationFactoryInterface { /** * Creates a Symfony Request instance from a PSR-7 one. + * + * @return Request */ - public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false): Request; + public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false); /** * Creates a Symfony Response instance from a PSR-7 one. + * + * @return Response */ - public function createResponse(ResponseInterface $psrResponse, bool $streamed = false): Response; + public function createResponse(ResponseInterface $psrResponse, bool $streamed = false); } diff --git a/HttpMessageFactoryInterface.php b/HttpMessageFactoryInterface.php index ebee037..f7b964e 100644 --- a/HttpMessageFactoryInterface.php +++ b/HttpMessageFactoryInterface.php @@ -25,11 +25,15 @@ interface HttpMessageFactoryInterface { /** * Creates a PSR-7 Request instance from a Symfony one. + * + * @return ServerRequestInterface */ - public function createRequest(Request $symfonyRequest): ServerRequestInterface; + public function createRequest(Request $symfonyRequest); /** * Creates a PSR-7 Response instance from a Symfony one. + * + * @return ResponseInterface */ - public function createResponse(Response $symfonyResponse): ResponseInterface; + public function createResponse(Response $symfonyResponse); } diff --git a/LICENSE b/LICENSE index 0138f8f..9ff2d0d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-present Fabien Potencier +Copyright (c) 2004-2021 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f1fd3fe..dcbc09a 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,14 @@ Provides integration for PSR7. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/psr7.html) - * [Contributing](https://symfony.com/doc/current/contributing/index.html) - * [Report issues](https://github.com/symfony/symfony/issues) and - [send Pull Requests](https://github.com/symfony/symfony/pulls) - in the [main Symfony repository](https://github.com/symfony/symfony) + * [Documentation](https://symfony.com/doc/current/components/psr7.html) + +Running the tests +----------------- + +If you want to run the unit tests, install dev dependencies before +running PHPUnit: + + $ cd path/to/Symfony/Bridge/PsrHttpMessage/ + $ composer.phar install + $ phpunit diff --git a/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php b/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php index 662b186..3ff2871 100644 --- a/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php +++ b/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php @@ -15,16 +15,21 @@ use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ServerRequestInterface; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver\PsrServerRequestResolver; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** * @author Alexander M. Turek */ final class PsrServerRequestResolverTest extends TestCase { + use ExpectDeprecationTrait; + public function testServerRequest() { $symfonyRequest = $this->createMock(Request::class); @@ -55,6 +60,21 @@ public function testMessage() self::assertSame([$psrRequest], $resolver->getArguments($symfonyRequest, static function (MessageInterface $request): void {})); } + /** + * @group legacy + */ + public function testDeprecatedSupports() + { + if (!interface_exists(ValueResolverInterface::class)) { + $this->markTestSkipped('Requires symfony/http-kernel 6.2.'); + } + + $resolver = new PsrServerRequestResolver($this->createStub(HttpMessageFactoryInterface::class)); + + $this->expectDeprecation('Since symfony/psr-http-message-bridge 2.3: Method "Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver\PsrServerRequestResolver::supports" is deprecated, call "resolve()" without calling "supports()" first.'); + $resolver->supports($this->createStub(Request::class), $this->createStub(ArgumentMetadata::class)); + } + private function bootstrapResolver(Request $symfonyRequest, ServerRequestInterface $psrRequest): ArgumentResolver { $messageFactory = $this->createMock(HttpMessageFactoryInterface::class); diff --git a/Tests/EventListener/PsrResponseListenerTest.php b/Tests/EventListener/PsrResponseListenerTest.php index fc41585..c700cca 100644 --- a/Tests/EventListener/PsrResponseListenerTest.php +++ b/Tests/EventListener/PsrResponseListenerTest.php @@ -46,7 +46,10 @@ public function testDoesNotConvertControllerResult() self::assertFalse($event->hasResponse()); } - private function createEventMock(mixed $controllerResult): ViewEvent + /** + * @param mixed $controllerResult + */ + private function createEventMock($controllerResult): ViewEvent { return new ViewEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $controllerResult); } diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index ed71b36..cc389a5 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -172,33 +172,33 @@ public function testCreateUploadedFile() $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); $size = $symfonyUploadedFile->getSize(); - $filename = 'upload'; - $symfonyUploadedFile->move($this->tmpDir, $filename); + $uniqid = uniqid(); + $symfonyUploadedFile->move($this->tmpDir, $uniqid); $this->assertEquals($uploadedFile->getSize(), $size); $this->assertEquals(\UPLOAD_ERR_OK, $symfonyUploadedFile->getError()); $this->assertEquals('myfile.txt', $symfonyUploadedFile->getClientOriginalName()); $this->assertEquals('txt', $symfonyUploadedFile->getClientOriginalExtension()); $this->assertEquals('text/plain', $symfonyUploadedFile->getClientMimeType()); - $this->assertEquals('An uploaded file.', file_get_contents($this->tmpDir.'/'.$filename)); + $this->assertEquals('An uploaded file.', file_get_contents($this->tmpDir.'/'.$uniqid)); } public function testCreateUploadedFileWithError() { + $this->expectException(FileException::class); + $this->expectExceptionMessage('The file "e" could not be written on disk.'); + $uploadedFile = $this->createUploadedFile('Error.', \UPLOAD_ERR_CANT_WRITE, 'e', 'text/plain'); $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); $this->assertEquals(\UPLOAD_ERR_CANT_WRITE, $symfonyUploadedFile->getError()); - $this->expectException(FileException::class); - $this->expectExceptionMessage('The file "e" could not be written on disk.'); - $symfonyUploadedFile->move($this->tmpDir, 'shouldFail.txt'); } private function createUploadedFile(string $content, int $error, string $clientFileName, string $clientMediaType): UploadedFile { - $filePath = tempnam($this->tmpDir, 'sftest'); + $filePath = tempnam($this->tmpDir, uniqid()); file_put_contents($filePath, $content); return new UploadedFile($filePath, filesize($filePath), $error, $clientFileName, $clientMediaType); @@ -208,6 +208,7 @@ private function callCreateUploadedFile(UploadedFileInterface $uploadedFile): Ht { $reflection = new \ReflectionClass($this->factory); $createUploadedFile = $reflection->getMethod('createUploadedFile'); + $createUploadedFile->setAccessible(true); return $createUploadedFile->invokeArgs($this->factory, [$uploadedFile]); } diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php index f5b09c8..9d4c4c9 100644 --- a/Tests/Factory/PsrHttpFactoryTest.php +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -14,6 +14,7 @@ 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; @@ -28,17 +29,26 @@ */ class PsrHttpFactoryTest extends TestCase { - private string $tmpDir; + /** @var HttpMessageFactoryInterface */ + private $factory; + + /** @var string */ + private $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(); } - /** - * @dataProvider provideFactories - */ - public function testCreateRequest(PsrHttpFactory $factory) + public function testCreateRequest() { $stdClass = new \stdClass(); $request = new Request( @@ -76,7 +86,7 @@ public function testCreateRequest(PsrHttpFactory $factory) ); $request->headers->set(' X-Broken', 'abc'); - $psrRequest = $factory->createRequest($request); + $psrRequest = $this->factory->createRequest($request); $this->assertSame('Content', $psrRequest->getBody()->__toString()); @@ -123,7 +133,7 @@ public function testGetContentCanBeCalledAfterRequestCreation() $header = ['HTTP_HOST' => 'dunglas.fr']; $request = new Request([], [], [], [], [], $header, 'Content'); - $psrRequest = self::buildHttpMessageFactory()->createRequest($request); + $psrRequest = $this->factory->createRequest($request); $this->assertSame('Content', $psrRequest->getBody()->__toString()); $this->assertSame('Content', $request->getContent()); @@ -131,16 +141,13 @@ public function testGetContentCanBeCalledAfterRequestCreation() private function createUploadedFile(string $content, string $originalName, string $mimeType, int $error): UploadedFile { - $path = $this->createTempFile(); + $path = tempnam($this->tmpDir, uniqid()); file_put_contents($path, $content); return new UploadedFile($path, $originalName, $mimeType, $error, true); } - /** - * @dataProvider provideFactories - */ - public function testCreateResponse(PsrHttpFactory $factory) + public function testCreateResponse() { $response = new Response( 'Response content.', @@ -152,7 +159,7 @@ public function testCreateResponse(PsrHttpFactory $factory) ); $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, 'lax')); - $psrResponse = $factory->createResponse($response); + $psrResponse = $this->factory->createResponse($response); $this->assertSame('Response content.', $psrResponse->getBody()->__toString()); $this->assertSame(202, $psrResponse->getStatusCode()); $this->assertSame(['3.4'], $psrResponse->getHeader('x-symfony')); @@ -175,26 +182,26 @@ public function testCreateResponseFromStreamed() flush(); }); - $psrResponse = self::buildHttpMessageFactory()->createResponse($response); + $psrResponse = $this->factory->createResponse($response); $this->assertSame("Line 1\nLine 2\n", $psrResponse->getBody()->__toString()); } public function testCreateResponseFromBinaryFile() { - $path = $this->createTempFile(); + $path = tempnam($this->tmpDir, uniqid()); file_put_contents($path, 'Binary'); $response = new BinaryFileResponse($path); - $psrResponse = self::buildHttpMessageFactory()->createResponse($response); + $psrResponse = $this->factory->createResponse($response); $this->assertSame('Binary', $psrResponse->getBody()->__toString()); } public function testCreateResponseFromBinaryFileWithRange() { - $path = $this->createTempFile(); + $path = tempnam($this->tmpDir, uniqid()); file_put_contents($path, 'Binary'); $request = new Request(); @@ -203,7 +210,7 @@ public function testCreateResponseFromBinaryFileWithRange() $response = new BinaryFileResponse($path, 200, ['Content-Type' => 'plain/text']); $response->prepare($request); - $psrResponse = self::buildHttpMessageFactory()->createResponse($response); + $psrResponse = $this->factory->createResponse($response); $this->assertSame('inar', $psrResponse->getBody()->__toString()); $this->assertSame('bytes 1-4/6', $psrResponse->getHeaderLine('Content-Range')); @@ -211,7 +218,10 @@ public function testCreateResponseFromBinaryFileWithRange() public function testUploadErrNoFile() { - $file = new UploadedFile(__FILE__, '', null, \UPLOAD_ERR_NO_FILE, true); + $file = new UploadedFile('', '', null, \UPLOAD_ERR_NO_FILE, true); + + $this->assertSame(\UPLOAD_ERR_NO_FILE, $file->getError()); + $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); $request = new Request( [], @@ -219,18 +229,18 @@ public function testUploadErrNoFile() [], [], [ - 'f1' => $file, - 'f2' => ['name' => null, 'type' => null, 'tmp_name' => null, 'error' => \UPLOAD_ERR_NO_FILE, 'size' => 0], - ], + 'f1' => $file, + 'f2' => ['name' => null, 'type' => null, 'tmp_name' => null, 'error' => \UPLOAD_ERR_NO_FILE, 'size' => 0], + ], [ - 'REQUEST_METHOD' => 'POST', - 'HTTP_HOST' => 'dunglas.fr', - 'HTTP_X_SYMFONY' => '2.8', - ], + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + ], 'Content' ); - $psrRequest = self::buildHttpMessageFactory()->createRequest($request); + $psrRequest = $this->factory->createRequest($request); $uploadedFiles = $psrRequest->getUploadedFiles(); @@ -240,56 +250,49 @@ public function testUploadErrNoFile() public function testJsonContent() { + if (!method_exists(Request::class, 'getPayload')) { + $this->markTestSkipped(); + } + $headers = [ 'HTTP_HOST' => 'http_host.fr', 'CONTENT_TYPE' => 'application/json', ]; $request = new Request([], [], [], [], [], $headers, '{"city":"Paris","country":"France"}'); - $psrRequest = self::buildHttpMessageFactory()->createRequest($request); + $psrRequest = $this->factory->createRequest($request); $this->assertSame(['city' => 'Paris', 'country' => 'France'], $psrRequest->getParsedBody()); } public function testEmptyJsonContent() { + if (!method_exists(Request::class, 'getPayload')) { + $this->markTestSkipped(); + } + $headers = [ 'HTTP_HOST' => 'http_host.fr', 'CONTENT_TYPE' => 'application/json', ]; $request = new Request([], [], [], [], [], $headers, '{}'); - $psrRequest = self::buildHttpMessageFactory()->createRequest($request); + $psrRequest = $this->factory->createRequest($request); $this->assertSame([], $psrRequest->getParsedBody()); } public function testWrongJsonContent() { + if (!method_exists(Request::class, 'getPayload')) { + $this->markTestSkipped(); + } + $headers = [ 'HTTP_HOST' => 'http_host.fr', 'CONTENT_TYPE' => 'application/json', ]; $request = new Request([], [], [], [], [], $headers, '{"city":"Paris"'); - $psrRequest = self::buildHttpMessageFactory()->createRequest($request); + $psrRequest = $this->factory->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); - } - - private function createTempFile(): string - { - return tempnam($this->tmpDir, 'sftest'); - } } diff --git a/Tests/Fixtures/App/Controller/PsrRequestController.php b/Tests/Fixtures/App/Controller/PsrRequestController.php index a3936cd..18b7741 100644 --- a/Tests/Fixtures/App/Controller/PsrRequestController.php +++ b/Tests/Fixtures/App/Controller/PsrRequestController.php @@ -11,10 +11,13 @@ final class PsrRequestController { - public function __construct( - private readonly ResponseFactoryInterface $responseFactory, - private readonly StreamFactoryInterface $streamFactory, - ) { + private $responseFactory; + private $streamFactory; + + public function __construct(ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory) + { + $this->responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; } public function serverRequestAction(ServerRequestInterface $request): ResponseInterface diff --git a/Tests/Fixtures/App/Kernel.php b/Tests/Fixtures/App/Kernel.php index 1b72293..611154d 100644 --- a/Tests/Fixtures/App/Kernel.php +++ b/Tests/Fixtures/App/Kernel.php @@ -50,10 +50,7 @@ protected function configureContainer(ContainerConfigurator $container): void 'router' => ['utf8' => true], 'secret' => 'for your eyes only', 'test' => true, - 'annotations' => false, 'http_method_override' => false, - 'handle_all_throwables' => true, - 'php_errors' => ['log' => true], ]); $container->services() diff --git a/Tests/Fixtures/Message.php b/Tests/Fixtures/Message.php index 69eda7e..8fc18f7 100644 --- a/Tests/Fixtures/Message.php +++ b/Tests/Fixtures/Message.php @@ -21,11 +21,15 @@ */ class Message implements MessageInterface { - public function __construct( - private readonly string $version = '1.1', - private array $headers = [], - private readonly StreamInterface $body = new Stream(), - ) { + private $version = '1.1'; + private $headers = []; + private $body; + + public function __construct(string $version = '1.1', array $headers = [], StreamInterface $body = null) + { + $this->version = $version; + $this->headers = $headers; + $this->body = $body ?? new Stream(); } public function getProtocolVersion(): string @@ -33,7 +37,12 @@ public function getProtocolVersion(): string return $this->version; } - public function withProtocolVersion($version): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withProtocolVersion($version): MessageInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -58,19 +67,34 @@ public function getHeaderLine($name): string return $this->hasHeader($name) ? implode(',', $this->headers[$name]) : ''; } - public function withHeader($name, $value): static + /** + * {@inheritdoc} + * + * @return static + */ + public function withHeader($name, $value): MessageInterface { $this->headers[$name] = (array) $value; return $this; } - public function withAddedHeader($name, $value): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withAddedHeader($name, $value): MessageInterface { throw new \BadMethodCallException('Not implemented.'); } - public function withoutHeader($name): static + /** + * {@inheritdoc} + * + * @return static + */ + public function withoutHeader($name): MessageInterface { unset($this->headers[$name]); @@ -84,8 +108,10 @@ public function getBody(): StreamInterface /** * {@inheritdoc} + * + * @return static */ - public function withBody(StreamInterface $body): never + public function withBody(StreamInterface $body): MessageInterface { throw new \BadMethodCallException('Not implemented.'); } diff --git a/Tests/Fixtures/Response.php b/Tests/Fixtures/Response.php index 4ce2836..39a1b56 100644 --- a/Tests/Fixtures/Response.php +++ b/Tests/Fixtures/Response.php @@ -19,13 +19,13 @@ */ class Response extends Message implements ResponseInterface { - public function __construct( - string $version = '1.1', - array $headers = [], - StreamInterface $body = new Stream(), - private readonly int $statusCode = 200, - ) { + private $statusCode; + + public function __construct(string $version = '1.1', array $headers = [], StreamInterface $body = null, int $statusCode = 200) + { parent::__construct($version, $headers, $body); + + $this->statusCode = $statusCode; } public function getStatusCode(): int @@ -33,12 +33,15 @@ public function getStatusCode(): int return $this->statusCode; } - public function withStatus($code, $reasonPhrase = ''): never + /** + * @return static + */ + public function withStatus($code, $reasonPhrase = ''): ResponseInterface { throw new \BadMethodCallException('Not implemented.'); } - public function getReasonPhrase(): never + public function getReasonPhrase(): string { throw new \BadMethodCallException('Not implemented.'); } diff --git a/Tests/Fixtures/ServerRequest.php b/Tests/Fixtures/ServerRequest.php index f7ea108..8cfc59f 100644 --- a/Tests/Fixtures/ServerRequest.php +++ b/Tests/Fixtures/ServerRequest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UriInterface; @@ -20,29 +21,37 @@ */ class ServerRequest extends Message implements ServerRequestInterface { - private readonly UriInterface $uri; - - public function __construct( - string $version = '1.1', - array $headers = [], - ?StreamInterface $body = null, - private readonly string $requestTarget = '/', - private readonly string $method = 'GET', - UriInterface|string|null $uri = null, - private readonly array $server = [], - private readonly array $cookies = [], - private readonly array $query = [], - private readonly array $uploadedFiles = [], - private readonly array|object|null $data = null, - private readonly array $attributes = [], - ) { + private $requestTarget; + private $method; + private $uri; + private $server; + private $cookies; + private $query; + private $uploadedFiles; + private $data; + private $attributes; + + /** + * @param UriInterface|string|null $uri + * @param array|object|null $data + */ + public function __construct(string $version = '1.1', array $headers = [], StreamInterface $body = null, string $requestTarget = '/', string $method = 'GET', $uri = null, array $server = [], array $cookies = [], array $query = [], array $uploadedFiles = [], $data = null, array $attributes = []) + { parent::__construct($version, $headers, $body); if (!$uri instanceof UriInterface) { $uri = new Uri((string) $uri); } + $this->requestTarget = $requestTarget; + $this->method = $method; $this->uri = $uri; + $this->server = $server; + $this->cookies = $cookies; + $this->query = $query; + $this->uploadedFiles = $uploadedFiles; + $this->data = $data; + $this->attributes = $attributes; } public function getRequestTarget(): string @@ -50,7 +59,10 @@ public function getRequestTarget(): string return $this->requestTarget; } - public function withRequestTarget($requestTarget): never + /** + * {@inheritdoc} + */ + public function withRequestTarget($requestTarget): RequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -60,17 +72,28 @@ public function getMethod(): string return $this->method; } - public function withMethod($method): never + /** + * {@inheritdoc} + */ + public function withMethod($method): RequestInterface { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + */ public function getUri(): UriInterface { return $this->uri; } - public function withUri(UriInterface $uri, $preserveHost = false): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -85,7 +108,12 @@ public function getCookieParams(): array return $this->cookies; } - public function withCookieParams(array $cookies): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withCookieParams(array $cookies): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -95,7 +123,12 @@ public function getQueryParams(): array return $this->query; } - public function withQueryParams(array $query): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withQueryParams(array $query): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -105,17 +138,32 @@ public function getUploadedFiles(): array return $this->uploadedFiles; } - public function withUploadedFiles(array $uploadedFiles): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } - public function getParsedBody(): array|object|null + /** + * {@inheritdoc} + * + * @return array|object|null + */ + public function getParsedBody() { return $this->data; } - public function withParsedBody($data): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withParsedBody($data): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -125,17 +173,32 @@ public function getAttributes(): array return $this->attributes; } - public function getAttribute($name, mixed $default = null): mixed + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getAttribute($name, $default = null) { return $this->attributes[$name] ?? $default; } - public function withAttribute($name, $value): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withAttribute($name, $value): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } - public function withoutAttribute($name): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withoutAttribute($name): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } diff --git a/Tests/Fixtures/Stream.php b/Tests/Fixtures/Stream.php index b44b0e3..34e243f 100644 --- a/Tests/Fixtures/Stream.php +++ b/Tests/Fixtures/Stream.php @@ -18,11 +18,12 @@ */ class Stream implements StreamInterface { - private bool $eof = true; + private $stringContent; + private $eof = true; - public function __construct( - private readonly string $stringContent = '', - ) { + public function __construct(string $stringContent = '') + { + $this->stringContent = $stringContent; } public function __toString(): string @@ -95,7 +96,12 @@ public function getContents(): string return $this->stringContent; } - public function getMetadata($key = null): mixed + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) { return null; } diff --git a/Tests/Fixtures/UploadedFile.php b/Tests/Fixtures/UploadedFile.php index dcfdd76..92254fc 100644 --- a/Tests/Fixtures/UploadedFile.php +++ b/Tests/Fixtures/UploadedFile.php @@ -19,13 +19,19 @@ */ class UploadedFile implements UploadedFileInterface { - public function __construct( - private readonly string $filePath, - private readonly ?int $size = null, - private readonly int $error = \UPLOAD_ERR_OK, - private readonly ?string $clientFileName = null, - private readonly ?string $clientMediaType = null, - ) { + private $filePath; + private $size; + private $error; + private $clientFileName; + private $clientMediaType; + + public function __construct(string $filePath, int $size = null, int $error = \UPLOAD_ERR_OK, string $clientFileName = null, string $clientMediaType = null) + { + $this->filePath = $filePath; + $this->size = $size; + $this->error = $error; + $this->clientFileName = $clientFileName; + $this->clientMediaType = $clientMediaType; } public function getStream(): StreamInterface diff --git a/Tests/Fixtures/Uri.php b/Tests/Fixtures/Uri.php index 6643149..d03e032 100644 --- a/Tests/Fixtures/Uri.php +++ b/Tests/Fixtures/Uri.php @@ -18,18 +18,18 @@ */ class Uri implements UriInterface { - private readonly string $scheme; - private readonly string $userInfo; - private readonly string $host; - private readonly ?string $port; - private readonly string $path; - private readonly string $query; - private readonly string $fragment; - - public function __construct( - private readonly string $uriString, - ) { - $parts = parse_url($uriString); + private $scheme = ''; + private $userInfo = ''; + private $host = ''; + private $port; + private $path = ''; + private $query = ''; + private $fragment = ''; + private $uriString; + + public function __construct(string $uri = '') + { + $parts = parse_url($uri); $this->scheme = $parts['scheme'] ?? ''; $this->userInfo = $parts['user'] ?? ''; @@ -38,6 +38,7 @@ public function __construct( $this->path = $parts['path'] ?? ''; $this->query = $parts['query'] ?? ''; $this->fragment = $parts['fragment'] ?? ''; + $this->uriString = $uri; } public function getScheme(): string @@ -47,13 +48,13 @@ public function getScheme(): string public function getAuthority(): string { - if (!$this->host) { + if (empty($this->host)) { return ''; } $authority = $this->host; - if ($this->userInfo) { + if (!empty($this->userInfo)) { $authority = $this->userInfo.'@'.$authority; } @@ -92,37 +93,72 @@ public function getFragment(): string return $this->fragment; } - public function withScheme($scheme): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withScheme($scheme): UriInterface { throw new \BadMethodCallException('Not implemented.'); } - public function withUserInfo($user, $password = null): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withUserInfo($user, $password = null): UriInterface { throw new \BadMethodCallException('Not implemented.'); } - public function withHost($host): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withHost($host): UriInterface { throw new \BadMethodCallException('Not implemented.'); } - public function withPort($port): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withPort($port): UriInterface { throw new \BadMethodCallException('Not implemented.'); } - public function withPath($path): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withPath($path): UriInterface { throw new \BadMethodCallException('Not implemented.'); } - public function withQuery($query): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withQuery($query): UriInterface { throw new \BadMethodCallException('Not implemented.'); } - public function withFragment($fragment): never + /** + * {@inheritdoc} + * + * @return static + */ + public function withFragment($fragment): UriInterface { throw new \BadMethodCallException('Not implemented.'); } diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php index 23bdbb9..25bbdc9 100644 --- a/Tests/Functional/CovertTest.php +++ b/Tests/Functional/CovertTest.php @@ -43,8 +43,12 @@ protected function setUp(): void /** * @dataProvider requestProvider + * + * @param Request|ServerRequestInterface $request + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory */ - public function testConvertRequestMultipleTimes(ServerRequestInterface|Request $request, HttpMessageFactoryInterface|HttpFoundationFactoryInterface $firstFactory, HttpMessageFactoryInterface|HttpFoundationFactoryInterface $secondFactory) + public function testConvertRequestMultipleTimes($request, $firstFactory, $secondFactory) { $temporaryRequest = $firstFactory->createRequest($request); $finalRequest = $secondFactory->createRequest($temporaryRequest); @@ -153,8 +157,12 @@ public static function requestProvider(): array /** * @dataProvider responseProvider + * + * @param Response|ResponseInterface $response + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory */ - public function testConvertResponseMultipleTimes(ResponseInterface|Response $response, HttpMessageFactoryInterface|HttpFoundationFactoryInterface $firstFactory, HttpMessageFactoryInterface|HttpFoundationFactoryInterface $secondFactory) + public function testConvertResponseMultipleTimes($response, $firstFactory, $secondFactory) { $temporaryResponse = $firstFactory->createResponse($response); $finalResponse = $secondFactory->createResponse($temporaryResponse); @@ -195,7 +203,11 @@ public static function responseProvider(): array ['x-symfony' => ['3.4']] ); - $cookie = Cookie::create('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT')); + if (method_exists(Cookie::class, 'create')) { + $cookie = Cookie::create('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT')); + } else { + $cookie = new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT')); + } $sfResponse->headers->setCookie($cookie); $body = Psr7Stream::create(); @@ -217,7 +229,7 @@ public static function responseProvider(): array private static function createUploadedFile(string $content, string $originalName, string $mimeType, int $error): UploadedFile { - $path = tempnam(sys_get_temp_dir(), 'sftest'); + $path = tempnam(sys_get_temp_dir(), uniqid()); file_put_contents($path, $content); return new UploadedFile($path, $originalName, $mimeType, $error, true); diff --git a/composer.json b/composer.json index a34dfb1..b705eb2 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "symfony-bridge", "description": "PSR HTTP message bridge", "keywords": ["http", "psr-7", "psr-17", "http-message"], - "homepage": "https://symfony.com", + "homepage": "http://symfony.com", "license": "MIT", "authors": [ { @@ -12,32 +12,27 @@ }, { "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "homepage": "http://symfony.com/contributors" } ], "require": { - "php": ">=8.2", - "psr/http-message": "^1.0|^2.0", - "symfony/http-foundation": "^6.4|^7.0" + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/http-foundation": "^5.4 || ^6.0" }, "require-dev": { - "symfony/browser-kit": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/framework-bundle": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", + "symfony/browser-kit": "^5.4 || ^6.0", + "symfony/config": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/framework-bundle": "^5.4 || ^6.0", + "symfony/http-kernel": "^5.4 || ^6.0", + "symfony/phpunit-bridge": "^6.2", "nyholm/psr7": "^1.1", - "php-http/discovery": "^1.15", - "psr/log": "^1.1.4|^2|^3" + "psr/log": "^1.1 || ^2 || ^3" }, - "conflict": { - "php-http/discovery": "<1.15", - "symfony/http-kernel": "<6.4" - }, - "config": { - "allow-plugins": { - "php-http/discovery": false - } + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" }, "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" }, @@ -45,5 +40,9 @@ "/Tests/" ] }, - "minimum-stability": "dev" + "extra": { + "branch-alias": { + "dev-main": "2.3-dev" + } + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fdfe483..43aeaa3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,31 +1,29 @@ - - - - - ./Tests/ - - + + ./ - - - ./Resources - ./Tests - ./vendor - - + + ./Resources + ./Tests + ./vendor + + +