From 01b110b6551f4f8e9995f1ae0925a866a4193385 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 21 May 2015 18:30:33 +0200 Subject: [PATCH 01/91] added the initial set of files --- CHANGELOG.md | 7 +++++++ LICENSE | 19 +++++++++++++++++++ README.md | 14 ++++++++++++++ composer.json | 35 +++++++++++++++++++++++++++++++++++ phpunit.xml.dist | 30 ++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit.xml.dist diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..569522f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +2.8.0 +----- + + * added the component diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0b3292c --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2014 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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2b2d37 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +PSR-7 Bridge +============ + +Provides integration for PSR7. + +Resources +--------- + +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/composer.json b/composer.json new file mode 100644 index 0000000..700becc --- /dev/null +++ b/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/psr-http-message-bridge", + "type": "symfony-bridge", + "description": "PSR HTTP message bridge", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + }, + "suggest": { + }, + "autoload": { + "psr-0": { "Symfony\\Bridge\\PsrHttpMessage\\": "" } + }, + "target-dir": "Symfony/Bridge/PsrHttpMessage", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..9a6e477 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + From ca411468f28be7b7f62f1602a644ee08832f5351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 21 May 2015 22:21:32 +0200 Subject: [PATCH 02/91] Initial support --- .gitignore | 3 + .travis.yml | 43 ++++ Factory/DiactorosFactory.php | 164 +++++++++++++++ Factory/HttpFoundationFactory.php | 199 ++++++++++++++++++ HttpFoundationFactoryInterface.php | 43 ++++ HttpMessageFactoryInterface.php | 43 ++++ LICENSE | 2 +- Tests/Factory/DiactorosFactoryTest.php | 164 +++++++++++++++ Tests/Factory/HttpFoundationFactoryTest.php | 211 ++++++++++++++++++++ Tests/Fixtures/Message.php | 89 +++++++++ Tests/Fixtures/Response.php | 45 +++++ Tests/Fixtures/ServerRequest.php | 141 +++++++++++++ Tests/Fixtures/Stream.php | 95 +++++++++ Tests/Fixtures/UploadedFile.php | 65 ++++++ composer.json | 11 +- 15 files changed, 1313 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Factory/DiactorosFactory.php create mode 100644 Factory/HttpFoundationFactory.php create mode 100644 HttpFoundationFactoryInterface.php create mode 100644 HttpMessageFactoryInterface.php create mode 100644 Tests/Factory/DiactorosFactoryTest.php create mode 100644 Tests/Factory/HttpFoundationFactoryTest.php create mode 100644 Tests/Fixtures/Message.php create mode 100644 Tests/Fixtures/Response.php create mode 100644 Tests/Fixtures/ServerRequest.php create mode 100644 Tests/Fixtures/Stream.php create mode 100644 Tests/Fixtures/UploadedFile.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..39e7eb6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +language: php + +sudo: false + +matrix: + include: + - php: 5.3 + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 5.3 + env: deps=low + - php: 5.6 + env: deps=high + - php: nightly + - php: hhvm + allow_failures: + - php: nightly + - php: hhvm + fast_finish: true + +env: + global: + - deps=no + - SYMFONY_DEPRECATIONS_HELPER=weak + +before_install: + - composer self-update + - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi; + - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi; + - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php -i; fi; + # Set the COMPOSER_ROOT_VERSION to the right version according to the branch being built + - if [ "$TRAVIS_BRANCH" = "master" ]; then export COMPOSER_ROOT_VERSION=dev-master; else export COMPOSER_ROOT_VERSION="$TRAVIS_BRANCH".x-dev; fi; + +install: + - if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]] && [[ "$TRAVIS_PHP_VERSION" != "5.4" ]]; then composer require --no-update zendframework/zend-diactoros; fi; + - if [ "$deps" = "no" ]; then export SYMFONY_DEPRECATIONS_HELPER=strict; fi; + - if [ "$deps" = "no" ]; then composer --prefer-source install; fi; + - if [ "$deps" = "high" ]; then composer --prefer-source update; fi; + - if [ "$deps" = "low" ]; then composer --prefer-source --prefer-lowest --prefer-stable update; fi; + +script: + - phpunit diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php new file mode 100644 index 0000000..31726f1 --- /dev/null +++ b/Factory/DiactorosFactory.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Zend\Diactoros\Response as DiactorosResponse; +use Zend\Diactoros\ServerRequest; +use Zend\Diactoros\ServerRequestFactory as DiactorosRequestFactory; +use Zend\Diactoros\Stream as DiactorosStream; +use Zend\Diactoros\UploadedFile as DiactorosUploadedFile; + +/** + * Builds Psr\HttpMessage instances using the Zend Diactoros implementation. + * + * @author Kévin Dunglas + */ +class DiactorosFactory implements HttpMessageFactoryInterface +{ + public function __construct() + { + if (!class_exists('Zend\Diactoros\ServerRequestFactory')) { + throw new \RuntimeException('Zend Diactoros must be installed to use the DiactorosFactory.'); + } + } + + /** + * {@inheritdoc} + */ + public function createRequest(Request $symfonyRequest) + { + $server = DiactorosRequestFactory::normalizeServer($symfonyRequest->server->all()); + $headers = $symfonyRequest->headers->all(); + + try { + $body = new DiactorosStream($symfonyRequest->getContent(true)); + } catch (\LogicException $e) { + $body = new DiactorosStream('php://temp', 'wb+'); + $body->write($symfonyRequest->getContent()); + } + + $request = new ServerRequest( + $server, + DiactorosRequestFactory::normalizeFiles($this->getFiles($symfonyRequest->files->all())), + $symfonyRequest->getUri(), + $symfonyRequest->getMethod(), + $body, + $headers + ); + + $request = $request + ->withCookieParams($symfonyRequest->cookies->all()) + ->withQueryParams($symfonyRequest->query->all()) + ->withParsedBody($symfonyRequest->request->all()) + ; + + foreach ($symfonyRequest->attributes->all() as $key => $value) { + $request = $request->withAttribute($key, $value); + } + + return $request; + } + + /** + * Converts Symfony uploaded files array to the PSR one. + * + * @param array $uploadedFiles + * + * @return array + */ + private function getFiles(array $uploadedFiles) + { + $files = array(); + + foreach ($uploadedFiles as $key => $value) { + if ($value instanceof UploadedFile) { + $files[$key] = $this->createUploadedFile($value); + } else { + $files[$key] = $this->getFiles($value); + } + } + + return $files; + } + + /** + * Creates a PSR-7 UploadedFile instance from a Symfony one. + * + * @param UploadedFile $symfonyUploadedFile + * + * @return UploadedFileInterface + */ + private function createUploadedFile(UploadedFile $symfonyUploadedFile) + { + return new DiactorosUploadedFile( + $symfonyUploadedFile->getRealPath(), + $symfonyUploadedFile->getSize(), + $symfonyUploadedFile->getError(), + $symfonyUploadedFile->getClientOriginalName(), + $symfonyUploadedFile->getClientMimeType() + ); + } + + /** + * {@inheritdoc} + */ + public function createResponse(Response $symfonyResponse) + { + if ($symfonyResponse instanceof BinaryFileResponse) { + $stream = new DiactorosStream($symfonyResponse->getFile()->getPathname(), 'r'); + } else { + $stream = new DiactorosStream('php://temp', 'wb+'); + if ($symfonyResponse instanceof StreamedResponse) { + ob_start(function ($buffer) use ($stream) { + $stream->write($buffer); + + return false; + }); + + $symfonyResponse->sendContent(); + ob_end_clean(); + } else { + $stream->write($symfonyResponse->getContent()); + } + } + + $headers = $symfonyResponse->headers->all(); + + $cookies = $symfonyResponse->headers->getCookies(); + if (!empty($cookies)) { + $headers['Set-Cookie'] = array(); + + foreach ($cookies as $cookie) { + $headers['Set-Cookie'][] = $cookie->__toString(); + } + } + + $response = new DiactorosResponse( + $stream, + $symfonyResponse->getStatusCode(), + $headers + ); + + $protocolVersion = $symfonyResponse->getProtocolVersion(); + if ('1.1' !== $protocolVersion) { + $response = $response->withProtocolVersion($protocolVersion); + } + + return $response; + } +} diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php new file mode 100644 index 0000000..2c356fd --- /dev/null +++ b/Factory/HttpFoundationFactory.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * {@inheritdoc} + * + * @author Kévin Dunglas + */ +class HttpFoundationFactory implements HttpFoundationFactoryInterface +{ + /** + * {@inheritdoc} + */ + public function createRequest(ServerRequestInterface $psrRequest) + { + $parsedBody = $psrRequest->getParsedBody(); + $parsedBody = is_array($parsedBody) ? $parsedBody : array(); + + $request = new Request( + $psrRequest->getQueryParams(), + $parsedBody, + $psrRequest->getAttributes(), + $psrRequest->getCookieParams(), + $this->getFiles($psrRequest->getUploadedFiles()), + $psrRequest->getServerParams(), + $psrRequest->getBody()->__toString() + ); + $request->headers->replace($psrRequest->getHeaders()); + + return $request; + } + + /** + * Converts to the input array to $_FILES structure. + * + * @param array $uploadedFiles + * + * @return array + */ + private function getFiles(array $uploadedFiles) + { + $files = array(); + + foreach ($uploadedFiles as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $files[$key] = $this->createUploadedFile($value); + } else { + $files[$key] = $this->getFiles($value); + } + } + + return $files; + } + + /** + * Creates Symfony UploadedFile instance from PSR-7 ones. + * + * @param UploadedFileInterface $psrUploadedFile + * + * @return UploadedFile + */ + private function createUploadedFile(UploadedFileInterface $psrUploadedFile) + { + $temporaryPath = $this->getTemporaryPath(); + $psrUploadedFile->moveTo($temporaryPath); + + $clientFileName = $psrUploadedFile->getClientFilename(); + + return new UploadedFile( + $temporaryPath, + null === $clientFileName ? '' : $clientFileName, + $psrUploadedFile->getClientMediaType(), + $psrUploadedFile->getSize(), + $psrUploadedFile->getError(), + true + ); + } + + /** + * Gets a temporary file path. + * + * @return string + */ + protected function getTemporaryPath() + { + return tempnam(sys_get_temp_dir(), uniqid('symfony', true)); + } + + /** + * {@inheritdoc} + */ + public function createResponse(ResponseInterface $psrResponse) + { + $response = new Response( + $psrResponse->getBody()->__toString(), + $psrResponse->getStatusCode(), + $psrResponse->getHeaders() + ); + $response->setProtocolVersion($psrResponse->getProtocolVersion()); + + foreach ($psrResponse->getHeader('Set-Cookie') as $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 + * + * @param string $cookie + * + * @return Cookie + * + * @throws \InvalidArgumentException + */ + private function createCookie($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 (!isset($cookieName)) { + throw new \InvalidArgumentException('The value of the Set-Cookie header is malformed.'); + } + + return new Cookie( + $cookieName, + $cookieValue, + isset($cookieExpire) ? $cookieExpire : 0, + isset($cookiePath) ? $cookiePath : '/', + isset($cookieDomain) ? $cookieDomain : null, + isset($cookieSecure), + isset($cookieHttpOnly) + ); + } +} diff --git a/HttpFoundationFactoryInterface.php b/HttpFoundationFactoryInterface.php new file mode 100644 index 0000000..32ec456 --- /dev/null +++ b/HttpFoundationFactoryInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Creates Symfony Request and Response instances from PSR-7 ones. + * + * @author Kévin Dunglas + */ +interface HttpFoundationFactoryInterface +{ + /** + * Creates a Symfony Request instance from a PSR-7 one. + * + * @param ServerRequestInterface $psrRequest + * + * @return Request + */ + public function createRequest(ServerRequestInterface $psrRequest); + + /** + * Creates a Symfony Response instance from a PSR-7 one. + * + * @param ResponseInterface $psrResponse + * + * @return Response + */ + public function createResponse(ResponseInterface $psrResponse); +} diff --git a/HttpMessageFactoryInterface.php b/HttpMessageFactoryInterface.php new file mode 100644 index 0000000..1367c8c --- /dev/null +++ b/HttpMessageFactoryInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\ResponseInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Creates PSR HTTP Request and Response instances from Symfony ones. + * + * @author Kévin Dunglas + */ +interface HttpMessageFactoryInterface +{ + /** + * Creates a PSR-7 Request instance from a Symfony one. + * + * @param Request $symfonyRequest + * + * @return ServerRequestInterface + */ + public function createRequest(Request $symfonyRequest); + + /** + * Creates a PSR-7 Response instance from a Symfony one. + * + * @param Response $symfonyResponse + * + * @return ResponseInterface + */ + public function createResponse(Response $symfonyResponse); +} diff --git a/LICENSE b/LICENSE index 0b3292c..43028bc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2014 Fabien Potencier +Copyright (c) 2004-2015 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/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php new file mode 100644 index 0000000..a4c32bd --- /dev/null +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * @author Kévin Dunglas + */ +class DiactorosFactoryTest extends \PHPUnit_Framework_TestCase +{ + private $factory; + private $tmpDir; + + public function setup() + { + if (!class_exists('Zend\Diactoros\ServerRequestFactory')) { + $this->markTestSkipped('Zend Diactoros is not installed.'); + } + + $this->factory = new DiactorosFactory(); + $this->tmpDir = sys_get_temp_dir(); + } + + public function testCreateRequest() + { + $stdClass = new \stdClass(); + $request = new Request( + array( + 'foo' => '1', + 'bar' => array('baz' => '42'), + ), + array( + 'twitter' => array( + '@dunglas' => 'Kévin Dunglas', + '@coopTilleuls' => 'Les-Tilleuls.coop', + ), + 'baz' => '2', + ), + array( + 'a1' => $stdClass, + 'a2' => array('foo' => 'bar'), + ), + array( + 'c1' => 'foo', + 'c2' => array('c3' => 'bar'), + ), + array( + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK), + 'foo' => array('f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)), + ), + array( + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + ), + 'Content' + ); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertEquals('Content', $psrRequest->getBody()->__toString()); + + $queryParams = $psrRequest->getQueryParams(); + $this->assertEquals('1', $queryParams['foo']); + $this->assertEquals('42', $queryParams['bar']['baz']); + + $parsedBody = $psrRequest->getParsedBody(); + $this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']); + $this->assertEquals('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']); + $this->assertEquals('2', $parsedBody['baz']); + + $attributes = $psrRequest->getAttributes(); + $this->assertEquals($stdClass, $attributes['a1']); + $this->assertEquals('bar', $attributes['a2']['foo']); + + $cookies = $psrRequest->getCookieParams(); + $this->assertEquals('foo', $cookies['c1']); + $this->assertEquals('bar', $cookies['c2']['c3']); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + $this->assertEquals('F1', $uploadedFiles['f1']->getStream()->__toString()); + $this->assertEquals('f1.txt', $uploadedFiles['f1']->getClientFilename()); + $this->assertEquals('text/plain', $uploadedFiles['f1']->getClientMediaType()); + $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['f1']->getError()); + + $this->assertEquals('F2', $uploadedFiles['foo']['f2']->getStream()->__toString()); + $this->assertEquals('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename()); + $this->assertEquals('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType()); + $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError()); + + $serverParams = $psrRequest->getServerParams(); + $this->assertEquals('POST', $serverParams['REQUEST_METHOD']); + $this->assertEquals('2.8', $serverParams['HTTP_X_SYMFONY']); + $this->assertEquals('POST', $psrRequest->getMethod()); + $this->assertEquals(array('2.8'), $psrRequest->getHeader('X-Symfony')); + } + + private function createUploadedFile($content, $originalName, $mimeType, $error) + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, $content); + + return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true); + } + + public function testCreateResponse() + { + $response = new Response( + 'Response content.', + 202, + array('X-Symfony' => array('2.8')) + ); + $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'))); + + $psrResponse = $this->factory->createResponse($response); + $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); + $this->assertEquals(202, $psrResponse->getStatusCode()); + $this->assertEquals(array('2.8'), $psrResponse->getHeader('X-Symfony')); + $this->assertEquals(array('city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT; path=/; httponly'), $psrResponse->getHeader('Set-Cookie')); + } + + public function testCreateResponseFromStreamed() + { + $response = new StreamedResponse(function () { + echo "Line 1\n"; + flush(); + + echo "Line 2\n"; + flush(); + }); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals("Line 1\nLine 2\n", $psrResponse->getBody()->__toString()); + } + + public function testCreateResponseFromBinaryFile() + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, 'Binary'); + + $response = new BinaryFileResponse($path); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals('Binary', $psrResponse->getBody()->__toString()); + } +} diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php new file mode 100644 index 0000000..412e287 --- /dev/null +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Response; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\ServerRequest; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Stream; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\UploadedFile; + +/** + * @author Kévin Dunglas + */ +class HttpFoundationFactoryTest extends \PHPUnit_Framework_TestCase +{ + private $factory; + private $tmpDir; + + public function setup() + { + $this->factory = new HttpFoundationFactory(); + $this->tmpDir = sys_get_temp_dir(); + } + + public function testCreateRequest() + { + $stdClass = new \stdClass(); + $serverRequest = new ServerRequest( + '1.1', + array( + 'X-Dunglas-API-Platform' => '1.0', + 'X-data' => array('a', 'b'), + ), + new Stream('The body'), + '/about/kevin', + 'GET', + 'http://les-tilleuls.coop/about/kevin', + array('country' => 'France'), + array('city' => 'Lille'), + array('url' => 'http://les-tilleuls.coop'), + array( + 'doc1' => $this->createUploadedFile('Doc 1', UPLOAD_ERR_OK, 'doc1.txt', 'text/plain'), + 'nested' => array( + 'docs' => array( + $this->createUploadedFile('Doc 2', UPLOAD_ERR_OK, 'doc2.txt', 'text/plain'), + $this->createUploadedFile('Doc 3', UPLOAD_ERR_OK, 'doc3.txt', 'text/plain'), + ), + ), + ), + array('url' => 'http://dunglas.fr'), + array('custom' => $stdClass) + ); + + $symfonyRequest = $this->factory->createRequest($serverRequest); + + $this->assertEquals('http://les-tilleuls.coop', $symfonyRequest->query->get('url')); + $this->assertEquals('doc1.txt', $symfonyRequest->files->get('doc1')->getClientOriginalName()); + $this->assertEquals('doc2.txt', $symfonyRequest->files->get('nested[docs][0]', null, true)->getClientOriginalName()); + $this->assertEquals('doc3.txt', $symfonyRequest->files->get('nested[docs][1]', null, true)->getClientOriginalName()); + $this->assertEquals('http://dunglas.fr', $symfonyRequest->request->get('url')); + $this->assertEquals($stdClass, $symfonyRequest->attributes->get('custom')); + $this->assertEquals('Lille', $symfonyRequest->cookies->get('city')); + $this->assertEquals('France', $symfonyRequest->server->get('country')); + $this->assertEquals('The body', $symfonyRequest->getContent()); + $this->assertEquals('1.0', $symfonyRequest->headers->get('X-Dunglas-API-Platform')); + $this->assertEquals(array('a', 'b'), $symfonyRequest->headers->get('X-data', null, false)); + } + + public function testCreateRequestWithNullParsedBody() + { + $serverRequest = new ServerRequest( + '1.1', + array(), + new Stream(), + '/', + 'GET', + null, + array(), + array(), + array(), + array(), + null, + array() + ); + + $this->assertCount(0, $this->factory->createRequest($serverRequest)->request); + } + + public function testCreateRequestWithObjectParsedBody() + { + $serverRequest = new ServerRequest( + '1.1', + array(), + new Stream(), + '/', + 'GET', + null, + array(), + array(), + array(), + array(), + new \stdClass(), + array() + ); + + $this->assertCount(0, $this->factory->createRequest($serverRequest)->request); + } + + public function testCreateUploadedFile() + { + $uploadedFile = $this->createUploadedFile('An uploaded file.', UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); + $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); + + $uniqid = uniqid(); + $symfonyUploadedFile->move($this->tmpDir, $uniqid); + + $this->assertEquals($uploadedFile->getSize(), $symfonyUploadedFile->getClientSize()); + $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.'/'.$uniqid)); + } + + /** + * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException + * @expectedExceptionMessage The file "e" could not be written on disk. + */ + public function testCreateUploadedFileWithError() + { + $uploadedFile = $this->createUploadedFile('Error.', UPLOAD_ERR_CANT_WRITE, 'e', 'text/plain'); + $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); + + $this->assertEquals(UPLOAD_ERR_CANT_WRITE, $symfonyUploadedFile->getError()); + + $symfonyUploadedFile->move($this->tmpDir, 'shouldFail.txt'); + } + + private function createUploadedFile($content, $error, $clientFileName, $clientMediaType) + { + $filePath = tempnam($this->tmpDir, uniqid()); + file_put_contents($filePath, $content); + + return new UploadedFile($filePath, filesize($filePath), $error, $clientFileName, $clientMediaType); + } + + private function callCreateUploadedFile(UploadedFileInterface $uploadedFile) + { + $reflection = new \ReflectionClass($this->factory); + $createUploadedFile = $reflection->getMethod('createUploadedFile'); + $createUploadedFile->setAccessible(true); + + return $createUploadedFile->invokeArgs($this->factory, array($uploadedFile)); + } + + public function testCreateResponse() + { + $response = new Response( + '1.0', + array( + 'X-Symfony' => array('2.8'), + 'Set-Cookie' => array( + 'theme=light', + 'test', + 'ABC=AeD; Domain=dunglas.fr; Path=/kevin; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly', + + ), + ), + new Stream('The response body'), + 200 + ); + + $symfonyResponse = $this->factory->createResponse($response); + + $this->assertEquals('1.0', $symfonyResponse->getProtocolVersion()); + $this->assertEquals('2.8', $symfonyResponse->headers->get('X-Symfony')); + + $cookies = $symfonyResponse->headers->getCookies(); + $this->assertEquals('theme', $cookies[0]->getName()); + $this->assertEquals('light', $cookies[0]->getValue()); + $this->assertEquals(0, $cookies[0]->getExpiresTime()); + $this->assertNull($cookies[0]->getDomain()); + $this->assertEquals('/', $cookies[0]->getPath()); + $this->assertFalse($cookies[0]->isSecure()); + $this->assertFalse($cookies[0]->isHttpOnly()); + + $this->assertEquals('test', $cookies[1]->getName()); + $this->assertNull($cookies[1]->getValue()); + + $this->assertEquals('ABC', $cookies[2]->getName()); + $this->assertEquals('AeD', $cookies[2]->getValue()); + $this->assertEquals(strtotime('Wed, 13 Jan 2021 22:23:01 GMT'), $cookies[2]->getExpiresTime()); + $this->assertEquals('dunglas.fr', $cookies[2]->getDomain()); + $this->assertEquals('/kevin', $cookies[2]->getPath()); + $this->assertTrue($cookies[2]->isSecure()); + $this->assertTrue($cookies[2]->isHttpOnly()); + + $this->assertEquals('The response body', $symfonyResponse->getContent()); + $this->assertEquals(200, $symfonyResponse->getStatusCode()); + } +} diff --git a/Tests/Fixtures/Message.php b/Tests/Fixtures/Message.php new file mode 100644 index 0000000..5cd0999 --- /dev/null +++ b/Tests/Fixtures/Message.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\StreamInterface; + +/** + * Message. + * + * @author Kévin Dunglas + */ +class Message implements MessageInterface +{ + private $version = '1.1'; + private $headers = array(); + private $body; + + public function __construct($version = '1.1', array $headers = array(), StreamInterface $body = null) + { + $this->version = $version; + $this->headers = $headers; + $this->body = null === $body ? new Stream() : $body; + } + + public function getProtocolVersion() + { + return $this->version; + } + + public function withProtocolVersion($version) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getHeaders() + { + return $this->headers; + } + + public function hasHeader($name) + { + return isset($this->headers[$name]); + } + + public function getHeader($name) + { + return $this->hasHeader($name) ? $this->headers[$name] : array(); + } + + public function getHeaderLine($name) + { + return $this->hasHeader($name) ? implode(',', $this->headers[$name]) : ''; + } + + public function withHeader($name, $value) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withAddedHeader($name, $value) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withoutHeader($name) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getBody() + { + return $this->body; + } + + public function withBody(StreamInterface $body) + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/Tests/Fixtures/Response.php b/Tests/Fixtures/Response.php new file mode 100644 index 0000000..0fd85c2 --- /dev/null +++ b/Tests/Fixtures/Response.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; + +/** + * @author Kévin Dunglas + */ +class Response extends Message implements ResponseInterface +{ + private $statusCode; + + public function __construct($version = '1.1', array $headers = array(), StreamInterface $body = null, $statusCode = 200) + { + parent::__construct($version, $headers, $body); + + $this->statusCode = $statusCode; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function withStatus($code, $reasonPhrase = '') + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getReasonPhrase() + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/Tests/Fixtures/ServerRequest.php b/Tests/Fixtures/ServerRequest.php new file mode 100644 index 0000000..63b8c06 --- /dev/null +++ b/Tests/Fixtures/ServerRequest.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; + +/** + * @author Kévin Dunglas + */ +class ServerRequest extends Message implements ServerRequestInterface +{ + private $requestTarget; + private $method; + private $uri; + private $server; + private $cookies; + private $query; + private $uploadedFiles; + private $data; + private $attributes; + + public function __construct($version = '1.1', array $headers = array(), StreamInterface $body = null, $requestTarget = '/', $method = 'GET', $uri = null, array $server = array(), array $cookies = array(), array $query = array(), array $uploadedFiles = array(), $data = null, array $attributes = array()) + { + parent::__construct($version, $headers, $body); + + $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() + { + return $this->requestTarget; + } + + public function withRequestTarget($requestTarget) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getServerParams() + { + return $this->server; + } + + public function getCookieParams() + { + return $this->cookies; + } + + public function withCookieParams(array $cookies) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getQueryParams() + { + return $this->query; + } + + public function withQueryParams(array $query) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getUploadedFiles() + { + return $this->uploadedFiles; + } + + public function withUploadedFiles(array $uploadedFiles) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getParsedBody() + { + return $this->data; + } + + public function withParsedBody($data) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function getAttributes() + { + return $this->attributes; + } + + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + public function withAttribute($name, $value) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withoutAttribute($name) + { + throw new \BadMethodCallException('Not implemented.'); + } +} diff --git a/Tests/Fixtures/Stream.php b/Tests/Fixtures/Stream.php new file mode 100644 index 0000000..aeca3d8 --- /dev/null +++ b/Tests/Fixtures/Stream.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\StreamInterface; + +/** + * @author Kévin Dunglas + */ +class Stream implements StreamInterface +{ + private $stringContent; + + public function __construct($stringContent = '') + { + $this->stringContent = $stringContent; + } + + public function __toString() + { + return $this->stringContent; + } + + public function close() + { + } + + public function detach() + { + } + + public function getSize() + { + } + + public function tell() + { + return 0; + } + + public function eof() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function seek($offset, $whence = SEEK_SET) + { + } + + public function rewind() + { + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + return $this->stringContent; + } + + public function getContents() + { + return $this->stringContent; + } + + public function getMetadata($key = null) + { + } +} diff --git a/Tests/Fixtures/UploadedFile.php b/Tests/Fixtures/UploadedFile.php new file mode 100644 index 0000000..4cfa98b --- /dev/null +++ b/Tests/Fixtures/UploadedFile.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\UploadedFileInterface; + +/** + * @author Kévin Dunglas + */ +class UploadedFile implements UploadedFileInterface +{ + private $filePath; + private $size; + private $error; + private $clientFileName; + private $clientMediaType; + + public function __construct($filePath, $size = null, $error = UPLOAD_ERR_OK, $clientFileName = null, $clientMediaType = null) + { + $this->filePath = $filePath; + $this->size = $size; + $this->error = $error; + $this->clientFileName = $clientFileName; + $this->clientMediaType = $clientMediaType; + } + + public function getStream() + { + throw new \RuntimeException('No stream is available.'); + } + + public function moveTo($targetPath) + { + rename($this->filePath, $targetPath); + } + + public function getSize() + { + return $this->size; + } + + public function getError() + { + return $this->error; + } + + public function getClientFilename() + { + return $this->clientFileName; + } + + public function getClientMediaType() + { + return $this->clientMediaType; + } +} diff --git a/composer.json b/composer.json index 700becc..2086398 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "symfony/psr-http-message-bridge", "type": "symfony-bridge", "description": "PSR HTTP message bridge", - "keywords": [], + "keywords": ["http", "psr-7", "http-message"], "homepage": "http://symfony.com", "license": "MIT", "authors": [ @@ -16,16 +16,19 @@ } ], "require": { - "php": ">=5.3.3" + "php": ">=5.3.3", + "psr/http-message": "~1.0", + "symfony/http-foundation": "~2.3|~3.0" }, "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0" }, "suggest": { + "zendframework/zend-diactoros": "To use the Zend Diactoros factory" }, "autoload": { - "psr-0": { "Symfony\\Bridge\\PsrHttpMessage\\": "" } + "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" } }, - "target-dir": "Symfony/Bridge/PsrHttpMessage", "minimum-stability": "dev", "extra": { "branch-alias": { From dc7e308e1dc2898a46776e2221a643cb08315453 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 29 May 2015 19:57:12 +0200 Subject: [PATCH 03/91] removed the branch alias for now as we are pre 1.0 --- composer.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 2086398..9dc9ce7 100644 --- a/composer.json +++ b/composer.json @@ -29,10 +29,5 @@ "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" } }, - "minimum-stability": "dev", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - } + "minimum-stability": "dev" } From d7660b8e47b08853f652a4b196031d318b487671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 9 Jul 2015 09:55:09 +0200 Subject: [PATCH 04/91] Suggest psr/http-message-implementation --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 9dc9ce7..8e5885b 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "symfony/phpunit-bridge": "~2.7|~3.0" }, "suggest": { + "psr/http-message-implementation": "To use the HttpFoundation factory", "zendframework/zend-diactoros": "To use the Zend Diactoros factory" }, "autoload": { From bab1530616203ebfada3722f99a122834716cff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 9 Jul 2015 10:03:14 +0200 Subject: [PATCH 05/91] Test Diactoros Factory with PHP 5.4 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 39e7eb6..801dfb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ before_install: - if [ "$TRAVIS_BRANCH" = "master" ]; then export COMPOSER_ROOT_VERSION=dev-master; else export COMPOSER_ROOT_VERSION="$TRAVIS_BRANCH".x-dev; fi; install: - - if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]] && [[ "$TRAVIS_PHP_VERSION" != "5.4" ]]; then composer require --no-update zendframework/zend-diactoros; fi; + - if [ "$TRAVIS_PHP_VERSION" != "5.3" ]; then composer require --no-update zendframework/zend-diactoros; fi; - if [ "$deps" = "no" ]; then export SYMFONY_DEPRECATIONS_HELPER=strict; fi; - if [ "$deps" = "no" ]; then composer --prefer-source install; fi; - if [ "$deps" = "high" ]; then composer --prefer-source update; fi; From 305c0fe45fb053004bfe72b883c2fb1bfa773d49 Mon Sep 17 00:00:00 2001 From: Korvin Szanto Date: Thu, 29 Oct 2015 23:37:51 -0700 Subject: [PATCH 06/91] Remove use of deprecated 'deep' parameter --- Tests/Factory/HttpFoundationFactoryTest.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index 412e287..1af7aec 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -23,7 +23,10 @@ */ class HttpFoundationFactoryTest extends \PHPUnit_Framework_TestCase { + /** @var HttpFoundationFactory */ private $factory; + + /** @var string */ private $tmpDir; public function setup() @@ -62,11 +65,12 @@ public function testCreateRequest() ); $symfonyRequest = $this->factory->createRequest($serverRequest); + $files = $symfonyRequest->files->all(); $this->assertEquals('http://les-tilleuls.coop', $symfonyRequest->query->get('url')); - $this->assertEquals('doc1.txt', $symfonyRequest->files->get('doc1')->getClientOriginalName()); - $this->assertEquals('doc2.txt', $symfonyRequest->files->get('nested[docs][0]', null, true)->getClientOriginalName()); - $this->assertEquals('doc3.txt', $symfonyRequest->files->get('nested[docs][1]', null, true)->getClientOriginalName()); + $this->assertEquals('doc1.txt', $files['doc1']->getClientOriginalName()); + $this->assertEquals('doc2.txt', $files['nested']['docs'][0]->getClientOriginalName()); + $this->assertEquals('doc3.txt', $files['nested']['docs'][1]->getClientOriginalName()); $this->assertEquals('http://dunglas.fr', $symfonyRequest->request->get('url')); $this->assertEquals($stdClass, $symfonyRequest->attributes->get('custom')); $this->assertEquals('Lille', $symfonyRequest->cookies->get('city')); From a388c43734225e9fea831112e25a3e208f1b801c Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 15 Feb 2016 14:11:58 +0100 Subject: [PATCH 07/91] update Travis CI configuration --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39e7eb6..81d03e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,10 @@ language: php sudo: false +cache: + directories: + - $HOME/.composer/cache/files + matrix: include: - php: 5.3 @@ -12,10 +16,9 @@ matrix: env: deps=low - php: 5.6 env: deps=high - - php: nightly + - php: 7.0 - php: hhvm allow_failures: - - php: nightly - php: hhvm fast_finish: true @@ -25,8 +28,8 @@ env: - SYMFONY_DEPRECATIONS_HELPER=weak before_install: - - composer self-update - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi; + - composer self-update - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi; - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php -i; fi; # Set the COMPOSER_ROOT_VERSION to the right version according to the branch being built From 9624b8bac68b6e50eb536ec0ea68510239c53f16 Mon Sep 17 00:00:00 2001 From: Aimeos Date: Sun, 14 Feb 2016 18:14:02 +0100 Subject: [PATCH 08/91] Allow multiple calls to Request::getContent() --- Factory/DiactorosFactory.php | 6 +++--- Tests/Factory/DiactorosFactoryTest.php | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index 31726f1..39202be 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -45,11 +45,11 @@ public function createRequest(Request $symfonyRequest) $server = DiactorosRequestFactory::normalizeServer($symfonyRequest->server->all()); $headers = $symfonyRequest->headers->all(); - try { - $body = new DiactorosStream($symfonyRequest->getContent(true)); - } catch (\LogicException $e) { + if (PHP_VERSION_ID < 50600) { $body = new DiactorosStream('php://temp', 'wb+'); $body->write($symfonyRequest->getContent()); + } else { + $body = new DiactorosStream($symfonyRequest->getContent(true)); } $request = new ServerRequest( diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index a4c32bd..f2d421d 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -111,6 +111,17 @@ public function testCreateRequest() $this->assertEquals(array('2.8'), $psrRequest->getHeader('X-Symfony')); } + public function testGetContentCanBeCalledAfterRequestCreation() + { + $header = array('HTTP_HOST' => 'dunglas.fr'); + $request = new Request(array(), array(), array(), array(), array(), $header, 'Content'); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertEquals('Content', $psrRequest->getBody()->__toString()); + $this->assertEquals('Content', $request->getContent()); + } + private function createUploadedFile($content, $originalName, $mimeType, $error) { $path = tempnam($this->tmpDir, uniqid()); From 101b6080ffef23b9a3c17f4aca172f2d14871dd5 Mon Sep 17 00:00:00 2001 From: Artx Hundiak Date: Wed, 19 Aug 2015 10:01:54 -0500 Subject: [PATCH 09/91] Handles null file in createrequest bridge. Fixed PHP 5.3.3 array syntax --- Factory/DiactorosFactory.php | 6 ++++- Tests/Factory/DiactorosFactoryTest.php | 34 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index 31726f1..f0fc0d3 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -86,6 +86,10 @@ private function getFiles(array $uploadedFiles) $files = array(); foreach ($uploadedFiles as $key => $value) { + if ($value === null) { + $files[$key] = new DiactorosUploadedFile(null, 0, UPLOAD_ERR_NO_FILE, null, null); + continue; + } if ($value instanceof UploadedFile) { $files[$key] = $this->createUploadedFile($value); } else { @@ -107,7 +111,7 @@ private function createUploadedFile(UploadedFile $symfonyUploadedFile) { return new DiactorosUploadedFile( $symfonyUploadedFile->getRealPath(), - $symfonyUploadedFile->getSize(), + $symfonyUploadedFile->getClientSize(), $symfonyUploadedFile->getError(), $symfonyUploadedFile->getClientOriginalName(), $symfonyUploadedFile->getClientMimeType() diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index a4c32bd..c6ab6dc 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -161,4 +161,38 @@ public function testCreateResponseFromBinaryFile() $this->assertEquals('Binary', $psrResponse->getBody()->__toString()); } + + public function testUploadErrNoFile() + { + $file = new UploadedFile(null, null, null, 0, UPLOAD_ERR_NO_FILE, true); + $this->assertEquals(0,$file->getSize()); + $this->assertEquals(UPLOAD_ERR_NO_FILE,$file->getError()); + + // SplFile returns false on error + $this->assertEquals('boolean',gettype(($file->getSize()))); + $this->assertFalse($file->getSize()); + + // This is an integer, oddly enough internally size is declared as a string + $this->assertTrue(is_int($file->getClientSize())); + + $request = new Request(array(),array(),array(),array(), + array( + 'f1' => $file, + 'f2' => array('name' => null, 'type' => null, 'tmp_name' => null, 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0), + ), + array( + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + ), + 'Content' + ); + + $psrRequest = $this->factory->createRequest($request); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + + $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); + $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); + } } From e5d62e62720e935d548999aaf367875467ea2e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Str=C3=B8m?= Date: Thu, 2 Jun 2016 10:54:14 +0200 Subject: [PATCH 10/91] Fixes based on code-review --- Factory/DiactorosFactory.php | 2 +- Factory/HttpFoundationFactory.php | 10 +++++++--- Tests/Factory/DiactorosFactoryTest.php | 15 +++++---------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index f0fc0d3..4777e7a 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -86,7 +86,7 @@ private function getFiles(array $uploadedFiles) $files = array(); foreach ($uploadedFiles as $key => $value) { - if ($value === null) { + if (null === $value) { $files[$key] = new DiactorosUploadedFile(null, 0, UPLOAD_ERR_NO_FILE, null, null); continue; } diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 2c356fd..1f52545 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -80,10 +80,14 @@ private function getFiles(array $uploadedFiles) */ private function createUploadedFile(UploadedFileInterface $psrUploadedFile) { - $temporaryPath = $this->getTemporaryPath(); - $psrUploadedFile->moveTo($temporaryPath); + $temporaryPath = ''; + $clientFileName = ''; + if (UPLOAD_ERR_NO_FILE !== $psrUploadedFile->getError()) { + $temporaryPath = $this->getTemporaryPath(); + $psrUploadedFile->moveTo($temporaryPath); - $clientFileName = $psrUploadedFile->getClientFilename(); + $clientFileName = $psrUploadedFile->getClientFilename(); + } return new UploadedFile( $temporaryPath, diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index c6ab6dc..d36ad1d 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -164,18 +164,13 @@ public function testCreateResponseFromBinaryFile() public function testUploadErrNoFile() { - $file = new UploadedFile(null, null, null, 0, UPLOAD_ERR_NO_FILE, true); - $this->assertEquals(0,$file->getSize()); - $this->assertEquals(UPLOAD_ERR_NO_FILE,$file->getError()); - - // SplFile returns false on error - $this->assertEquals('boolean',gettype(($file->getSize()))); + $file = new UploadedFile('', '', null, 0, UPLOAD_ERR_NO_FILE, true); + $this->assertEquals(0, $file->getSize()); + $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError()); $this->assertFalse($file->getSize()); + $this->assertInternalType('integer', $file->getClientSize()); - // This is an integer, oddly enough internally size is declared as a string - $this->assertTrue(is_int($file->getClientSize())); - - $request = new Request(array(),array(),array(),array(), + $request = new Request(array(), array(), array(), array(), array( 'f1' => $file, 'f2' => array('name' => null, 'type' => null, 'tmp_name' => null, 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0), From a1a631a92686388f3076a90844d0a8cefe3e8ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Str=C3=B8m?= Date: Thu, 18 Aug 2016 12:54:08 +0200 Subject: [PATCH 11/91] Update assert error message --- Tests/Factory/DiactorosFactoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index d36ad1d..94ea5e4 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -167,7 +167,7 @@ public function testUploadErrNoFile() $file = new UploadedFile('', '', null, 0, UPLOAD_ERR_NO_FILE, true); $this->assertEquals(0, $file->getSize()); $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError()); - $this->assertFalse($file->getSize()); + $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); $this->assertInternalType('integer', $file->getClientSize()); $request = new Request(array(), array(), array(), array(), From a59c57258cf950b3a623f4cf506685bc04e67c90 Mon Sep 17 00:00:00 2001 From: Rougin Royce Gutib Date: Sun, 22 May 2016 03:06:45 +0800 Subject: [PATCH 12/91] Fixes #16 Symfony Request created without any URI --- Factory/HttpFoundationFactory.php | 15 ++- Tests/Factory/HttpFoundationFactoryTest.php | 21 +++ Tests/Fixtures/Uri.php | 135 ++++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 Tests/Fixtures/Uri.php diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 2c356fd..016264f 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -14,6 +14,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UploadedFileInterface; +use Psr\Http\Message\UriInterface; use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -32,6 +33,18 @@ class HttpFoundationFactory implements HttpFoundationFactoryInterface */ public function createRequest(ServerRequestInterface $psrRequest) { + $server = array(); + $uri = $psrRequest->getUri(); + + if ($uri instanceof UriInterface) { + $server['SERVER_NAME'] = $uri->getHost(); + $server['SERVER_PORT'] = $uri->getPort(); + $server['REQUEST_URI'] = $uri->getPath(); + $server['QUERY_STRING'] = $uri->getQuery(); + } + + $server = array_replace($server, $psrRequest->getServerParams()); + $parsedBody = $psrRequest->getParsedBody(); $parsedBody = is_array($parsedBody) ? $parsedBody : array(); @@ -41,7 +54,7 @@ public function createRequest(ServerRequestInterface $psrRequest) $psrRequest->getAttributes(), $psrRequest->getCookieParams(), $this->getFiles($psrRequest->getUploadedFiles()), - $psrRequest->getServerParams(), + $server, $psrRequest->getBody()->__toString() ); $request->headers->replace($psrRequest->getHeaders()); diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index 1af7aec..47ef4e3 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -17,6 +17,7 @@ use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\ServerRequest; use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Stream; use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\UploadedFile; +use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Uri; /** * @author Kévin Dunglas @@ -120,6 +121,26 @@ public function testCreateRequestWithObjectParsedBody() $this->assertCount(0, $this->factory->createRequest($serverRequest)->request); } + public function testCreateRequestWithUri() + { + $serverRequest = new ServerRequest( + '1.1', + array(), + new Stream(), + '/', + 'GET', + new Uri('http://les-tilleuls.coop/about/kevin'), + array(), + array(), + array(), + array(), + null, + array() + ); + + $this->assertEquals('/about/kevin', $this->factory->createRequest($serverRequest)->getPathInfo()); + } + public function testCreateUploadedFile() { $uploadedFile = $this->createUploadedFile('An uploaded file.', UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); diff --git a/Tests/Fixtures/Uri.php b/Tests/Fixtures/Uri.php new file mode 100644 index 0000000..f11c7e5 --- /dev/null +++ b/Tests/Fixtures/Uri.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; + +use Psr\Http\Message\UriInterface; + +/** + * @author Rougin Royce Gutib + */ +class Uri implements UriInterface +{ + private $scheme = ''; + private $userInfo = ''; + private $host = ''; + private $port; + private $path = ''; + private $query = ''; + private $fragment = ''; + private $uriString; + + public function __construct($uri = '') + { + $parts = parse_url($uri); + + $this->scheme = isset($parts['scheme']) ? $parts['scheme'] : ''; + $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; + $this->host = isset($parts['host']) ? $parts['host'] : ''; + $this->port = isset($parts['port']) ? $parts['port'] : null; + $this->path = isset($parts['path']) ? $parts['path'] : ''; + $this->query = isset($parts['query']) ? $parts['query'] : ''; + $this->fragment = isset($parts['fragment']) ? $parts['fragment'] : ''; + $this->uriString = $uri; + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + if (empty($this->host)) { + return ''; + } + + $authority = $this->host; + + if (!empty($this->userInfo)) { + $authority = $this->userInfo.'@'.$authority; + } + + $authority .= ':'.$this->port; + + return $authority; + } + + public function getUserInfo() + { + return $this->userInfo; + } + + public function getHost() + { + return $this->host; + } + + public function getPort() + { + return $this->port; + } + + public function getPath() + { + return $this->path; + } + + public function getQuery() + { + return $this->query; + } + + public function getFragment() + { + return $this->fragment; + } + + public function withScheme($scheme) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withUserInfo($user, $password = null) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withHost($host) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withPort($port) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withPath($path) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withQuery($query) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function withFragment($fragment) + { + throw new \BadMethodCallException('Not implemented.'); + } + + public function __toString() + { + return $this->uriString; + } +} From d2db47c7ec0669f1fcc83d84dbbaf5458b19978e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 13 Sep 2016 16:42:22 -0700 Subject: [PATCH 13/91] removed obsolete CHANGELOG file --- CHANGELOG.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 569522f..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -CHANGELOG -========= - -2.8.0 ------ - - * added the component From 29be4f84646ef3e6f03fb44ae09e87e5069b92cd Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 13 Sep 2016 16:42:32 -0700 Subject: [PATCH 14/91] updated LICENCE year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 43028bc..12a7453 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2015 Fabien Potencier +Copyright (c) 2004-2016 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 From 98ab85a154f2b111c381eb1f59f9ea6688d2fd03 Mon Sep 17 00:00:00 2001 From: csunolgomez Date: Wed, 14 Sep 2016 11:57:16 +0200 Subject: [PATCH 15/91] Fix REQUEST_METHOD on symfony request --- Factory/HttpFoundationFactory.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 5e3dc91..76d196b 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -43,6 +43,8 @@ public function createRequest(ServerRequestInterface $psrRequest) $server['QUERY_STRING'] = $uri->getQuery(); } + $server['REQUEST_METHOD'] = $psrRequest->getMethod(); + $server = array_replace($server, $psrRequest->getServerParams()); $parsedBody = $psrRequest->getParsedBody(); From 533d3e4097721a33f55ddda1cb61a1911df2406c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 14 Sep 2016 11:36:44 -0700 Subject: [PATCH 16/91] added a CHANGELOG for 1.0 --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..cc3e983 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,3 @@ +* 1.0.0 (2016-09-14) + + * Initial release From 66085f246d3893cbdbcec5f5ad15ac60546cf0de Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 14 Sep 2016 11:37:20 -0700 Subject: [PATCH 17/91] preparing 1.0 release --- composer.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8e5885b..ad25080 100644 --- a/composer.json +++ b/composer.json @@ -30,5 +30,10 @@ "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" } }, - "minimum-stability": "dev" + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + } } From f5c46f0ff42ac4cd2557a48a14bdb85c6f48d65f Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 4 May 2017 21:07:18 +0200 Subject: [PATCH 18/91] test suite compatibility with PHPUnit 6 --- .travis.yml | 17 +++++++++-------- Tests/Factory/DiactorosFactoryTest.php | 9 +++++++-- Tests/Factory/HttpFoundationFactoryTest.php | 4 ++-- composer.json | 2 +- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3008471..da2f48c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ sudo: false cache: directories: - $HOME/.composer/cache/files + - $HOME/symfony-bridge/.phpunit matrix: include: @@ -13,9 +14,9 @@ matrix: - php: 5.5 - php: 5.6 - php: 5.3 - env: deps=low + env: COMPOSER_OPTIONS="--prefer-lowest --prefer-stable" SYMFONY_DEPRECATIONS_HELPER=weak - php: 5.6 - env: deps=high + env: COMPOSER_OPTIONS="" SYMFONY_DEPRECATIONS_HELPER=weak - php: 7.0 - php: hhvm allow_failures: @@ -25,7 +26,9 @@ matrix: env: global: - deps=no - - SYMFONY_DEPRECATIONS_HELPER=weak + - SYMFONY_DEPRECATIONS_HELPER=strict + - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" + - COMPOSER_OPTIONS="--prefer-stable" before_install: - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi; @@ -37,10 +40,8 @@ before_install: install: - if [ "$TRAVIS_PHP_VERSION" != "5.3" ]; then composer require --no-update zendframework/zend-diactoros; fi; - - if [ "$deps" = "no" ]; then export SYMFONY_DEPRECATIONS_HELPER=strict; fi; - - if [ "$deps" = "no" ]; then composer --prefer-source install; fi; - - if [ "$deps" = "high" ]; then composer --prefer-source update; fi; - - if [ "$deps" = "low" ]; then composer --prefer-source --prefer-lowest --prefer-stable update; fi; + - composer update --prefer-source $COMPOSER_OPTIONS + - vendor/bin/simple-phpunit install script: - - phpunit + - vendor/bin/simple-phpunit diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index ddf0806..fbc1ce8 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Cookie; @@ -22,7 +23,7 @@ /** * @author Kévin Dunglas */ -class DiactorosFactoryTest extends \PHPUnit_Framework_TestCase +class DiactorosFactoryTest extends TestCase { private $factory; private $tmpDir; @@ -143,7 +144,11 @@ public function testCreateResponse() $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); $this->assertEquals(202, $psrResponse->getStatusCode()); $this->assertEquals(array('2.8'), $psrResponse->getHeader('X-Symfony')); - $this->assertEquals(array('city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT; path=/; httponly'), $psrResponse->getHeader('Set-Cookie')); + + $cookieHeader = $psrResponse->getHeader('Set-Cookie'); + $this->assertInternalType('array', $cookieHeader); + $this->assertCount(1, $cookieHeader); + $this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}', $cookieHeader[0]); } public function testCreateResponseFromStreamed() diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index 47ef4e3..8790a20 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; +use PHPUnit\Framework\TestCase; use Psr\Http\Message\UploadedFileInterface; use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Response; @@ -22,7 +23,7 @@ /** * @author Kévin Dunglas */ -class HttpFoundationFactoryTest extends \PHPUnit_Framework_TestCase +class HttpFoundationFactoryTest extends TestCase { /** @var HttpFoundationFactory */ private $factory; @@ -198,7 +199,6 @@ public function testCreateResponse() 'theme=light', 'test', 'ABC=AeD; Domain=dunglas.fr; Path=/kevin; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly', - ), ), new Stream('The response body'), diff --git a/composer.json b/composer.json index ad25080..90412ac 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "symfony/http-foundation": "~2.3|~3.0" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7|~3.0" + "symfony/phpunit-bridge": "~3.2" }, "suggest": { "psr/http-message-implementation": "To use the HttpFoundation factory", From 97635f1ccf88e336c1f24bcfc56c162731e277e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 18 May 2017 08:49:55 +0200 Subject: [PATCH 19/91] Allow Symfony 4 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 90412ac..76513b1 100644 --- a/composer.json +++ b/composer.json @@ -18,10 +18,10 @@ "require": { "php": ">=5.3.3", "psr/http-message": "~1.0", - "symfony/http-foundation": "~2.3|~3.0" + "symfony/http-foundation": "~2.3|~3.0|~4.0" }, "require-dev": { - "symfony/phpunit-bridge": "~3.2" + "symfony/phpunit-bridge": "~3.2|4.0" }, "suggest": { "psr/http-message-implementation": "To use the HttpFoundation factory", From 64c0cb0cb0301934ba4a6fa8d5225cfb59003bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Parmentier?= Date: Tue, 12 Dec 2017 10:26:06 +0100 Subject: [PATCH 20/91] Run PHP 5.3 tests on Precise --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index da2f48c..d9c04e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,10 +10,12 @@ cache: matrix: include: - php: 5.3 + dist: 'precise' - php: 5.4 - php: 5.5 - php: 5.6 - php: 5.3 + dist: 'precise' env: COMPOSER_OPTIONS="--prefer-lowest --prefer-stable" SYMFONY_DEPRECATIONS_HELPER=weak - php: 5.6 env: COMPOSER_OPTIONS="" SYMFONY_DEPRECATIONS_HELPER=weak From 94fcfa54cfd196ced46c46a515d0d52243628683 Mon Sep 17 00:00:00 2001 From: Michael Tibben Date: Wed, 3 May 2017 21:55:41 +1000 Subject: [PATCH 21/91] Fix the request target in PSR7 Request --- Factory/DiactorosFactory.php | 3 ++- Tests/Factory/DiactorosFactoryTest.php | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index 9174f05..0600325 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -55,7 +55,7 @@ public function createRequest(Request $symfonyRequest) $request = new ServerRequest( $server, DiactorosRequestFactory::normalizeFiles($this->getFiles($symfonyRequest->files->all())), - $symfonyRequest->getUri(), + $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getRequestUri(), $symfonyRequest->getMethod(), $body, $headers @@ -65,6 +65,7 @@ public function createRequest(Request $symfonyRequest) ->withCookieParams($symfonyRequest->cookies->all()) ->withQueryParams($symfonyRequest->query->all()) ->withParsedBody($symfonyRequest->request->all()) + ->withRequestTarget($symfonyRequest->getRequestUri()) ; foreach ($symfonyRequest->attributes->all() as $key => $value) { diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index fbc1ce8..869fd32 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -69,6 +69,8 @@ public function testCreateRequest() 'REQUEST_METHOD' => 'POST', 'HTTP_HOST' => 'dunglas.fr', 'HTTP_X_SYMFONY' => '2.8', + 'REQUEST_URI' => '/testCreateRequest?foo=1&bar[baz]=42', + 'QUERY_STRING' => 'foo=1&bar[baz]=42', ), 'Content' ); @@ -81,6 +83,9 @@ public function testCreateRequest() $this->assertEquals('1', $queryParams['foo']); $this->assertEquals('42', $queryParams['bar']['baz']); + $requestTarget = $psrRequest->getRequestTarget(); + $this->assertEquals('/testCreateRequest?foo=1&bar[baz]=42', $requestTarget); + $parsedBody = $psrRequest->getParsedBody(); $this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']); $this->assertEquals('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']); From 8780dd3b6e368ede5cafc4aab708db5849a2695f Mon Sep 17 00:00:00 2001 From: Nyholm Date: Sat, 4 Aug 2018 09:19:43 +0200 Subject: [PATCH 22/91] Fixed broken build --- .travis.yml | 71 +++++++++++++-------- Factory/DiactorosFactory.php | 17 ++--- Factory/HttpFoundationFactory.php | 11 ++++ Tests/Factory/DiactorosFactoryTest.php | 18 ++++-- Tests/Factory/HttpFoundationFactoryTest.php | 3 +- composer.json | 9 ++- 6 files changed, 83 insertions(+), 46 deletions(-) diff --git a/.travis.yml b/.travis.yml index d9c04e4..28906da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,49 +1,66 @@ language: php - sudo: false - cache: directories: - $HOME/.composer/cache/files - $HOME/symfony-bridge/.phpunit +env: + global: + - PHPUNIT_FLAGS="-v" + - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" + - DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" + matrix: + fast_finish: true include: + # Minimum supported dependencies with the latest and oldest PHP version + - php: 7.2 + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" - php: 5.3 dist: 'precise' - - php: 5.4 - - php: 5.5 - - php: 5.6 + env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" DEPENDENCIES="" + + # Test the latest stable release - php: 5.3 dist: 'precise' - env: COMPOSER_OPTIONS="--prefer-lowest --prefer-stable" SYMFONY_DEPRECATIONS_HELPER=weak + env: DEPENDENCIES="" + - php: 5.4 + - php: 5.5 - php: 5.6 - env: COMPOSER_OPTIONS="" SYMFONY_DEPRECATIONS_HELPER=weak - php: 7.0 - - php: hhvm - allow_failures: - - php: hhvm - fast_finish: true + - php: 7.1 + - php: 7.2 + env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" -env: - global: - - deps=no - - SYMFONY_DEPRECATIONS_HELPER=strict - - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" - - COMPOSER_OPTIONS="--prefer-stable" + # Test LTS versions. This makes sure we do not use Symfony packages with version greater + # than 2 or 3 respectively. + - php: 7.2 + env: DEPENDENCIES="symfony/lts:^2 symfony/force-lowest:~2.8.0 zendframework/zend-diactoros:^1.4.1" + - php: 7.2 + env: DEPENDENCIES="symfony/lts:^3 symfony/force-lowest:~3.4.0 zendframework/zend-diactoros:^1.4.1" + + # Latest commit to master + - php: 7.2 + env: STABILITY="dev" + + allow_failures: + # Dev-master is allowed to fail. + - env: STABILITY="dev" before_install: - - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi; - - composer self-update - - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi; - - if [[ "$TRAVIS_PHP_VERSION" != "nightly" ]] && [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php -i; fi; - # Set the COMPOSER_ROOT_VERSION to the right version according to the branch being built - - if [ "$TRAVIS_BRANCH" = "master" ]; then export COMPOSER_ROOT_VERSION=dev-master; else export COMPOSER_ROOT_VERSION="$TRAVIS_BRANCH".x-dev; fi; + - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi + - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi; + - if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi; install: - - if [ "$TRAVIS_PHP_VERSION" != "5.3" ]; then composer require --no-update zendframework/zend-diactoros; fi; - - composer update --prefer-source $COMPOSER_OPTIONS - - vendor/bin/simple-phpunit install + # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355 + - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi + - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction + - ./vendor/bin/simple-phpunit install script: - - vendor/bin/simple-phpunit + - composer validate --strict --no-check-lock + # simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and + # it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge) + - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS \ No newline at end of file diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index 0600325..18c5396 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\PsrHttpMessage\Factory; +use Psr\Http\Message\UploadedFileInterface; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -112,7 +113,7 @@ private function createUploadedFile(UploadedFile $symfonyUploadedFile) { return new DiactorosUploadedFile( $symfonyUploadedFile->getRealPath(), - $symfonyUploadedFile->getClientSize(), + (int) $symfonyUploadedFile->getSize(), $symfonyUploadedFile->getError(), $symfonyUploadedFile->getClientOriginalName(), $symfonyUploadedFile->getClientMimeType() @@ -143,13 +144,13 @@ public function createResponse(Response $symfonyResponse) } $headers = $symfonyResponse->headers->all(); - - $cookies = $symfonyResponse->headers->getCookies(); - if (!empty($cookies)) { - $headers['Set-Cookie'] = array(); - - foreach ($cookies as $cookie) { - $headers['Set-Cookie'][] = $cookie->__toString(); + if (!isset($headers['Set-Cookie']) && !isset($headers['set-sookie'])) { + $cookies = $symfonyResponse->headers->getCookies(); + if (!empty($cookies)) { + $headers['Set-Cookie'] = array(); + foreach ($cookies as $cookie) { + $headers['Set-Cookie'][] = $cookie->__toString(); + } } } diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 76d196b..65289c6 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -104,6 +104,17 @@ private function createUploadedFile(UploadedFileInterface $psrUploadedFile) $clientFileName = $psrUploadedFile->getClientFilename(); } + if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { + // Symfony 4.1+ + return new UploadedFile( + $temporaryPath, + null === $clientFileName ? '' : $clientFileName, + $psrUploadedFile->getClientMediaType(), + $psrUploadedFile->getError(), + true + ); + } + return new UploadedFile( $temporaryPath, null === $clientFileName ? '' : $clientFileName, diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index 869fd32..00e0b66 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -133,6 +133,10 @@ private function createUploadedFile($content, $originalName, $mimeType, $error) $path = tempnam($this->tmpDir, uniqid()); file_put_contents($path, $content); + if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { + // Symfony 4.1+ + return new UploadedFile($path, $originalName, $mimeType, $error, true); + } return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true); } @@ -141,19 +145,19 @@ public function testCreateResponse() $response = new Response( 'Response content.', 202, - array('X-Symfony' => array('2.8')) + array('X-Symfony' => array('3.4')) ); $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'))); $psrResponse = $this->factory->createResponse($response); $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); $this->assertEquals(202, $psrResponse->getStatusCode()); - $this->assertEquals(array('2.8'), $psrResponse->getHeader('X-Symfony')); + $this->assertEquals(array('3.4'), $psrResponse->getHeader('X-Symfony')); $cookieHeader = $psrResponse->getHeader('Set-Cookie'); $this->assertInternalType('array', $cookieHeader); $this->assertCount(1, $cookieHeader); - $this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}', $cookieHeader[0]); + $this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); } public function testCreateResponseFromStreamed() @@ -185,11 +189,15 @@ public function testCreateResponseFromBinaryFile() public function testUploadErrNoFile() { - $file = new UploadedFile('', '', null, 0, UPLOAD_ERR_NO_FILE, true); + if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { + // Symfony 4.1+ + $file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true); + } else { + $file = new UploadedFile('', '', null, 0, UPLOAD_ERR_NO_FILE, true); + } $this->assertEquals(0, $file->getSize()); $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError()); $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); - $this->assertInternalType('integer', $file->getClientSize()); $request = new Request(array(), array(), array(), array(), array( diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index 8790a20..5492bec 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -146,11 +146,12 @@ public function testCreateUploadedFile() { $uploadedFile = $this->createUploadedFile('An uploaded file.', UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); + $size = $symfonyUploadedFile->getSize(); $uniqid = uniqid(); $symfonyUploadedFile->move($this->tmpDir, $uniqid); - $this->assertEquals($uploadedFile->getSize(), $symfonyUploadedFile->getClientSize()); + $this->assertEquals($uploadedFile->getSize(), $size); $this->assertEquals(UPLOAD_ERR_OK, $symfonyUploadedFile->getError()); $this->assertEquals('myfile.txt', $symfonyUploadedFile->getClientOriginalName()); $this->assertEquals('txt', $symfonyUploadedFile->getClientOriginalExtension()); diff --git a/composer.json b/composer.json index 76513b1..74ccad6 100644 --- a/composer.json +++ b/composer.json @@ -16,12 +16,12 @@ } ], "require": { - "php": ">=5.3.3", - "psr/http-message": "~1.0", - "symfony/http-foundation": "~2.3|~3.0|~4.0" + "php": "^5.3.3 || ^7.0", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^2.3.42 || ^3.4 || ^4.0" }, "require-dev": { - "symfony/phpunit-bridge": "~3.2|4.0" + "symfony/phpunit-bridge": "^3.4 || 4.0" }, "suggest": { "psr/http-message-implementation": "To use the HttpFoundation factory", @@ -30,7 +30,6 @@ "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" } }, - "minimum-stability": "dev", "extra": { "branch-alias": { "dev-master": "1.0-dev" From dd81b4bb9feb59f0c65429034b3855eda9795404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20J=2E=20Garc=C3=ADa=20Lagar?= Date: Wed, 8 Aug 2018 17:35:46 +0200 Subject: [PATCH 23/91] Create PSR-7 messages using PSR-17 factories --- .travis.yml | 9 +- Factory/DiactorosFactory.php | 2 +- Factory/PsrHttpFactory.php | 177 ++++++++++++++ .../AbstractHttpMessageFactoryTest.php | 224 ++++++++++++++++++ Tests/Factory/DiactorosFactoryTest.php | 200 +--------------- Tests/Factory/PsrHttpFactoryTest.php | 39 +++ composer.json | 3 +- 7 files changed, 453 insertions(+), 201 deletions(-) create mode 100644 Factory/PsrHttpFactory.php create mode 100644 Tests/Factory/AbstractHttpMessageFactoryTest.php create mode 100644 Tests/Factory/PsrHttpFactoryTest.php diff --git a/.travis.yml b/.travis.yml index 28906da..6c9646a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ env: global: - PHPUNIT_FLAGS="-v" - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" - - DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" + - DEPENDENCIES="zendframework/zend-diactoros:^1.4.1 http-interop/http-factory-diactoros:^1.0" matrix: fast_finish: true @@ -26,8 +26,11 @@ matrix: dist: 'precise' env: DEPENDENCIES="" - php: 5.4 + env: DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" - php: 5.5 + env: DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" - php: 5.6 + env: DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" - php: 7.0 - php: 7.1 - php: 7.2 @@ -36,9 +39,9 @@ matrix: # Test LTS versions. This makes sure we do not use Symfony packages with version greater # than 2 or 3 respectively. - php: 7.2 - env: DEPENDENCIES="symfony/lts:^2 symfony/force-lowest:~2.8.0 zendframework/zend-diactoros:^1.4.1" + env: DEPENDENCIES="$DEPENDENCIES symfony/lts:^2 symfony/force-lowest:~2.8.0" - php: 7.2 - env: DEPENDENCIES="symfony/lts:^3 symfony/force-lowest:~3.4.0 zendframework/zend-diactoros:^1.4.1" + env: DEPENDENCIES="$DEPENDENCIES symfony/lts:^3 symfony/force-lowest:~3.4.0" # Latest commit to master - php: 7.2 diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index 18c5396..a5dcd86 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -133,7 +133,7 @@ public function createResponse(Response $symfonyResponse) ob_start(function ($buffer) use ($stream) { $stream->write($buffer); - return false; + return ''; }); $symfonyResponse->sendContent(); diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php new file mode 100644 index 0000000..c4c48a1 --- /dev/null +++ b/Factory/PsrHttpFactory.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ServerRequestFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\UploadedFileFactoryInterface; +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Builds Psr\HttpMessage instances using a PSR-17 implementation. + * + * @author Antonio J. García Lagar + */ +class PsrHttpFactory implements HttpMessageFactoryInterface +{ + 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; + } + + /** + * {@inheritdoc} + */ + public function createRequest(Request $symfonyRequest) + { + $request = $this->serverRequestFactory->createServerRequest( + $symfonyRequest->getMethod(), + $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getRequestUri(), + $symfonyRequest->server->all() + ); + + foreach ($symfonyRequest->headers->all() as $name => $value) { + $request = $request->withHeader($name, $value); + } + + if (PHP_VERSION_ID < 50600) { + $body = $this->streamFactory->createStreamFromFile('php://temp', 'wb+'); + $body->write($symfonyRequest->getContent()); + } else { + $body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true)); + } + + $request = $request + ->withBody($body) + ->withUploadedFiles($this->getFiles($symfonyRequest->files->all())) + ->withCookieParams($symfonyRequest->cookies->all()) + ->withQueryParams($symfonyRequest->query->all()) + ->withParsedBody($symfonyRequest->request->all()) + ; + + foreach ($symfonyRequest->attributes->all() as $key => $value) { + $request = $request->withAttribute($key, $value); + } + + return $request; + } + + /** + * Converts Symfony uploaded files array to the PSR one. + * + * @param array $uploadedFiles + * + * @return array + */ + private function getFiles(array $uploadedFiles) + { + $files = array(); + + foreach ($uploadedFiles as $key => $value) { + if (null === $value) { + $files[$key] = $this->uploadedFileFactory->createUploadedFile($this->streamFactory->createStream(), 0, UPLOAD_ERR_NO_FILE); + continue; + } + if ($value instanceof UploadedFile) { + $files[$key] = $this->createUploadedFile($value); + } else { + $files[$key] = $this->getFiles($value); + } + } + + return $files; + } + + /** + * Creates a PSR-7 UploadedFile instance from a Symfony one. + * + * @param UploadedFile $symfonyUploadedFile + * + * @return UploadedFileInterface + */ + private function createUploadedFile(UploadedFile $symfonyUploadedFile) + { + return $this->uploadedFileFactory->createUploadedFile( + $this->streamFactory->createStreamFromFile( + $symfonyUploadedFile->getRealPath() + ), + (int) $symfonyUploadedFile->getSize(), + $symfonyUploadedFile->getError(), + $symfonyUploadedFile->getClientOriginalName(), + $symfonyUploadedFile->getClientMimeType() + ); + } + + /** + * {@inheritdoc} + */ + public function createResponse(Response $symfonyResponse) + { + $response = $this->responseFactory->createResponse($symfonyResponse->getStatusCode()); + + if ($symfonyResponse instanceof BinaryFileResponse) { + $stream = $this->streamFactory->createStreamFromFile( + $symfonyResponse->getFile()->getPathname() + ); + } else { + $stream = $this->streamFactory->createStreamFromFile('php://temp', 'wb+'); + if ($symfonyResponse instanceof StreamedResponse) { + ob_start(function ($buffer) use ($stream) { + $stream->write($buffer); + + return ''; + }); + + $symfonyResponse->sendContent(); + ob_end_clean(); + } else { + $stream->write($symfonyResponse->getContent()); + } + } + + $response = $response->withBody($stream); + + $headers = $symfonyResponse->headers->all(); + $cookies = $symfonyResponse->headers->getCookies(); + if (!empty($cookies)) { + $headers['Set-Cookie'] = array(); + + foreach ($cookies as $cookie) { + $headers['Set-Cookie'][] = $cookie->__toString(); + } + } + + foreach ($headers as $name => $value) { + $response = $response->withHeader($name, $value); + } + + $protocolVersion = $symfonyResponse->getProtocolVersion(); + $response = $response->withProtocolVersion($protocolVersion); + + return $response; + } +} diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php new file mode 100644 index 0000000..7216a80 --- /dev/null +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * @author Kévin Dunglas + * @author Antonio J. García Lagar + */ +abstract class AbstractHttpMessageFactoryTest extends TestCase +{ + private $factory; + private $tmpDir; + + /** + * @return HttpMessageFactoryInterface + */ + abstract protected function buildHttpMessageFactory(); + + public function setup() + { + $this->factory = $this->buildHttpMessageFactory(); + $this->tmpDir = sys_get_temp_dir(); + } + + public function testCreateRequest() + { + $stdClass = new \stdClass(); + $request = new Request( + array( + 'foo' => '1', + 'bar' => array('baz' => '42'), + ), + array( + 'twitter' => array( + '@dunglas' => 'Kévin Dunglas', + '@coopTilleuls' => 'Les-Tilleuls.coop', + ), + 'baz' => '2', + ), + array( + 'a1' => $stdClass, + 'a2' => array('foo' => 'bar'), + ), + array( + 'c1' => 'foo', + 'c2' => array('c3' => 'bar'), + ), + array( + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK), + 'foo' => array('f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)), + ), + array( + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + 'REQUEST_URI' => '/testCreateRequest?foo=1&bar[baz]=42', + 'QUERY_STRING' => 'foo=1&bar[baz]=42', + ), + 'Content' + ); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertEquals('Content', $psrRequest->getBody()->__toString()); + + $queryParams = $psrRequest->getQueryParams(); + $this->assertEquals('1', $queryParams['foo']); + $this->assertEquals('42', $queryParams['bar']['baz']); + + $requestTarget = $psrRequest->getRequestTarget(); + $this->assertEquals('/testCreateRequest?foo=1&bar[baz]=42', urldecode($requestTarget)); + + $parsedBody = $psrRequest->getParsedBody(); + $this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']); + $this->assertEquals('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']); + $this->assertEquals('2', $parsedBody['baz']); + + $attributes = $psrRequest->getAttributes(); + $this->assertEquals($stdClass, $attributes['a1']); + $this->assertEquals('bar', $attributes['a2']['foo']); + + $cookies = $psrRequest->getCookieParams(); + $this->assertEquals('foo', $cookies['c1']); + $this->assertEquals('bar', $cookies['c2']['c3']); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + $this->assertEquals('F1', $uploadedFiles['f1']->getStream()->__toString()); + $this->assertEquals('f1.txt', $uploadedFiles['f1']->getClientFilename()); + $this->assertEquals('text/plain', $uploadedFiles['f1']->getClientMediaType()); + $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['f1']->getError()); + + $this->assertEquals('F2', $uploadedFiles['foo']['f2']->getStream()->__toString()); + $this->assertEquals('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename()); + $this->assertEquals('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType()); + $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError()); + + $serverParams = $psrRequest->getServerParams(); + $this->assertEquals('POST', $serverParams['REQUEST_METHOD']); + $this->assertEquals('2.8', $serverParams['HTTP_X_SYMFONY']); + $this->assertEquals('POST', $psrRequest->getMethod()); + $this->assertEquals(array('2.8'), $psrRequest->getHeader('X-Symfony')); + } + + public function testGetContentCanBeCalledAfterRequestCreation() + { + $header = array('HTTP_HOST' => 'dunglas.fr'); + $request = new Request(array(), array(), array(), array(), array(), $header, 'Content'); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertEquals('Content', $psrRequest->getBody()->__toString()); + $this->assertEquals('Content', $request->getContent()); + } + + private function createUploadedFile($content, $originalName, $mimeType, $error) + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, $content); + + if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { + // Symfony 4.1+ + return new UploadedFile($path, $originalName, $mimeType, $error, true); + } + return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true); + } + + public function testCreateResponse() + { + $response = new Response( + 'Response content.', + 202, + array('X-Symfony' => array('3.4')) + ); + $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'))); + + $psrResponse = $this->factory->createResponse($response); + $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); + $this->assertEquals(202, $psrResponse->getStatusCode()); + $this->assertEquals(array('3.4'), $psrResponse->getHeader('X-Symfony')); + + $cookieHeader = $psrResponse->getHeader('Set-Cookie'); + $this->assertInternalType('array', $cookieHeader); + $this->assertCount(1, $cookieHeader); + $this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); + } + + public function testCreateResponseFromStreamed() + { + $response = new StreamedResponse(function () { + echo "Line 1\n"; + flush(); + + echo "Line 2\n"; + flush(); + }); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals("Line 1\nLine 2\n", $psrResponse->getBody()->__toString()); + } + + public function testCreateResponseFromBinaryFile() + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, 'Binary'); + + $response = new BinaryFileResponse($path); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals('Binary', $psrResponse->getBody()->__toString()); + } + + public function testUploadErrNoFile() + { + if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { + // Symfony 4.1+ + $file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true); + } else { + $file = new UploadedFile('', '', null, 0, UPLOAD_ERR_NO_FILE, true); + } + $this->assertEquals(0, $file->getSize()); + $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError()); + $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); + + $request = new Request(array(), array(), array(), array(), + array( + 'f1' => $file, + 'f2' => array('name' => null, 'type' => null, 'tmp_name' => null, 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0), + ), + array( + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + ), + 'Content' + ); + + $psrRequest = $this->factory->createRequest($request); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + + $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); + $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); + } +} diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index 00e0b66..08d37bd 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -11,212 +11,20 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; -use PHPUnit\Framework\TestCase; use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; -use Symfony\Component\HttpFoundation\BinaryFileResponse; -use Symfony\Component\HttpFoundation\Cookie; -use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\StreamedResponse; /** * @author Kévin Dunglas + * @author Antonio J. García Lagar */ -class DiactorosFactoryTest extends TestCase +class DiactorosFactoryTest extends AbstractHttpMessageFactoryTest { - private $factory; - private $tmpDir; - - public function setup() + protected function buildHttpMessageFactory() { if (!class_exists('Zend\Diactoros\ServerRequestFactory')) { $this->markTestSkipped('Zend Diactoros is not installed.'); } - $this->factory = new DiactorosFactory(); - $this->tmpDir = sys_get_temp_dir(); - } - - public function testCreateRequest() - { - $stdClass = new \stdClass(); - $request = new Request( - array( - 'foo' => '1', - 'bar' => array('baz' => '42'), - ), - array( - 'twitter' => array( - '@dunglas' => 'Kévin Dunglas', - '@coopTilleuls' => 'Les-Tilleuls.coop', - ), - 'baz' => '2', - ), - array( - 'a1' => $stdClass, - 'a2' => array('foo' => 'bar'), - ), - array( - 'c1' => 'foo', - 'c2' => array('c3' => 'bar'), - ), - array( - 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK), - 'foo' => array('f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)), - ), - array( - 'REQUEST_METHOD' => 'POST', - 'HTTP_HOST' => 'dunglas.fr', - 'HTTP_X_SYMFONY' => '2.8', - 'REQUEST_URI' => '/testCreateRequest?foo=1&bar[baz]=42', - 'QUERY_STRING' => 'foo=1&bar[baz]=42', - ), - 'Content' - ); - - $psrRequest = $this->factory->createRequest($request); - - $this->assertEquals('Content', $psrRequest->getBody()->__toString()); - - $queryParams = $psrRequest->getQueryParams(); - $this->assertEquals('1', $queryParams['foo']); - $this->assertEquals('42', $queryParams['bar']['baz']); - - $requestTarget = $psrRequest->getRequestTarget(); - $this->assertEquals('/testCreateRequest?foo=1&bar[baz]=42', $requestTarget); - - $parsedBody = $psrRequest->getParsedBody(); - $this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']); - $this->assertEquals('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']); - $this->assertEquals('2', $parsedBody['baz']); - - $attributes = $psrRequest->getAttributes(); - $this->assertEquals($stdClass, $attributes['a1']); - $this->assertEquals('bar', $attributes['a2']['foo']); - - $cookies = $psrRequest->getCookieParams(); - $this->assertEquals('foo', $cookies['c1']); - $this->assertEquals('bar', $cookies['c2']['c3']); - - $uploadedFiles = $psrRequest->getUploadedFiles(); - $this->assertEquals('F1', $uploadedFiles['f1']->getStream()->__toString()); - $this->assertEquals('f1.txt', $uploadedFiles['f1']->getClientFilename()); - $this->assertEquals('text/plain', $uploadedFiles['f1']->getClientMediaType()); - $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['f1']->getError()); - - $this->assertEquals('F2', $uploadedFiles['foo']['f2']->getStream()->__toString()); - $this->assertEquals('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename()); - $this->assertEquals('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType()); - $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError()); - - $serverParams = $psrRequest->getServerParams(); - $this->assertEquals('POST', $serverParams['REQUEST_METHOD']); - $this->assertEquals('2.8', $serverParams['HTTP_X_SYMFONY']); - $this->assertEquals('POST', $psrRequest->getMethod()); - $this->assertEquals(array('2.8'), $psrRequest->getHeader('X-Symfony')); - } - - public function testGetContentCanBeCalledAfterRequestCreation() - { - $header = array('HTTP_HOST' => 'dunglas.fr'); - $request = new Request(array(), array(), array(), array(), array(), $header, 'Content'); - - $psrRequest = $this->factory->createRequest($request); - - $this->assertEquals('Content', $psrRequest->getBody()->__toString()); - $this->assertEquals('Content', $request->getContent()); - } - - private function createUploadedFile($content, $originalName, $mimeType, $error) - { - $path = tempnam($this->tmpDir, uniqid()); - file_put_contents($path, $content); - - if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { - // Symfony 4.1+ - return new UploadedFile($path, $originalName, $mimeType, $error, true); - } - return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true); - } - - public function testCreateResponse() - { - $response = new Response( - 'Response content.', - 202, - array('X-Symfony' => array('3.4')) - ); - $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'))); - - $psrResponse = $this->factory->createResponse($response); - $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); - $this->assertEquals(202, $psrResponse->getStatusCode()); - $this->assertEquals(array('3.4'), $psrResponse->getHeader('X-Symfony')); - - $cookieHeader = $psrResponse->getHeader('Set-Cookie'); - $this->assertInternalType('array', $cookieHeader); - $this->assertCount(1, $cookieHeader); - $this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); - } - - public function testCreateResponseFromStreamed() - { - $response = new StreamedResponse(function () { - echo "Line 1\n"; - flush(); - - echo "Line 2\n"; - flush(); - }); - - $psrResponse = $this->factory->createResponse($response); - - $this->assertEquals("Line 1\nLine 2\n", $psrResponse->getBody()->__toString()); - } - - public function testCreateResponseFromBinaryFile() - { - $path = tempnam($this->tmpDir, uniqid()); - file_put_contents($path, 'Binary'); - - $response = new BinaryFileResponse($path); - - $psrResponse = $this->factory->createResponse($response); - - $this->assertEquals('Binary', $psrResponse->getBody()->__toString()); - } - - public function testUploadErrNoFile() - { - if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { - // Symfony 4.1+ - $file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true); - } else { - $file = new UploadedFile('', '', null, 0, UPLOAD_ERR_NO_FILE, true); - } - $this->assertEquals(0, $file->getSize()); - $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError()); - $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); - - $request = new Request(array(), array(), array(), array(), - array( - 'f1' => $file, - 'f2' => array('name' => null, 'type' => null, 'tmp_name' => null, 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0), - ), - array( - 'REQUEST_METHOD' => 'POST', - 'HTTP_HOST' => 'dunglas.fr', - 'HTTP_X_SYMFONY' => '2.8', - ), - 'Content' - ); - - $psrRequest = $this->factory->createRequest($request); - - $uploadedFiles = $psrRequest->getUploadedFiles(); - - $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); - $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); + return new DiactorosFactory(); } } diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php new file mode 100644 index 0000000..045c11d --- /dev/null +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; + +use Http\Factory\Diactoros\ResponseFactory; +use Http\Factory\Diactoros\ServerRequestFactory; +use Http\Factory\Diactoros\StreamFactory; +use Http\Factory\Diactoros\UploadedFileFactory; +use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; + +/** + * @author Kévin Dunglas + * @author Antonio J. García Lagar + */ +class PsrHttpFactoryTest extends AbstractHttpMessageFactoryTest +{ + protected function buildHttpMessageFactory() + { + if (!class_exists('Http\Factory\Diactoros\ServerRequestFactory')) { + $this->markTestSkipped('HTTP Factory for Diactoros is not installed.'); + } + + return new PsrHttpFactory( + new ServerRequestFactory(), + new StreamFactory(), + new UploadedFileFactory(), + new ResponseFactory() + ); + } +} diff --git a/composer.json b/composer.json index 74ccad6..af16997 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,8 @@ }, "suggest": { "psr/http-message-implementation": "To use the HttpFoundation factory", - "zendframework/zend-diactoros": "To use the Zend Diactoros factory" + "zendframework/zend-diactoros": "To use the Zend Diactoros factory", + "psr/http-factory-implementation": "To use the PSR-17 factory" }, "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" } From c2282e319666b2a2dc274ab8b0eb9ad5c0d36773 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Wed, 8 Aug 2018 16:54:00 +0200 Subject: [PATCH 24/91] Updated changelog --- CHANGELOG | 3 --- CHANGELOG.md | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) delete mode 100644 CHANGELOG create mode 100644 CHANGELOG.md diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index cc3e983..0000000 --- a/CHANGELOG +++ /dev/null @@ -1,3 +0,0 @@ -* 1.0.0 (2016-09-14) - - * Initial release diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2a833ea --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +CHANGELOG +========= + +* 1.0.2 (2017-12-19) + + * Fixed request target in PSR7 Request (mtibben) + +* 1.0.1 (2017-12-04) + + * Added support for Symfony 4 (dunglas) + +* 1.0.0 (2016-09-14) + + * Initial release From c821241c4d3d785ca0d3465fccc799cdcb68139a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 30 Aug 2018 18:27:58 +0200 Subject: [PATCH 25/91] bumped version to 1.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index af16997..80f4785 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } } } From 53c15a6a7918e6c2ab16ae370ea607fb40cab196 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 30 Aug 2018 18:28:28 +0200 Subject: [PATCH 26/91] updated CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a833ea..70f3b82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG ========= +* 1.1.0 (2018-08-30) + + * Added support for creating PSR-7 messages using PSR-17 factories + * 1.0.2 (2017-12-19) * Fixed request target in PSR7 Request (mtibben) From 8ff61e5744c6e8c438f2c9f3b04cb3754d514439 Mon Sep 17 00:00:00 2001 From: Harry Lewis Date: Thu, 4 Oct 2018 18:07:18 +0100 Subject: [PATCH 27/91] Fix compatability issue with "zendframework/zend-diactoros": "^2.0." (#51) --- Factory/DiactorosFactory.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index a5dcd86..5d37fe2 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -43,7 +43,9 @@ public function __construct() */ public function createRequest(Request $symfonyRequest) { - $server = DiactorosRequestFactory::normalizeServer($symfonyRequest->server->all()); + $server = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeServer') + ? DiactorosRequestFactory::normalizeServer($symfonyRequest->server->all()) + : \Zend\Diactoros\normalizeServer($symfonyRequest->server->all()); $headers = $symfonyRequest->headers->all(); if (PHP_VERSION_ID < 50600) { @@ -53,9 +55,13 @@ public function createRequest(Request $symfonyRequest) $body = new DiactorosStream($symfonyRequest->getContent(true)); } + $files = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeFiles') + ? DiactorosRequestFactory::normalizeFiles($this->getFiles($symfonyRequest->files->all())) + : \Zend\Diactoros\normalizeUploadedFiles($this->getFiles($symfonyRequest->files->all())); + $request = new ServerRequest( $server, - DiactorosRequestFactory::normalizeFiles($this->getFiles($symfonyRequest->files->all())), + $files, $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getRequestUri(), $symfonyRequest->getMethod(), $body, From 25f9c3afd9329ddde57b49f6e9ee805c70b5fbe1 Mon Sep 17 00:00:00 2001 From: Samuel NELA Date: Sun, 18 Nov 2018 09:41:12 +0100 Subject: [PATCH 28/91] Excluded tests from classmap --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 80f4785..ebbfb6d 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,10 @@ "psr/http-factory-implementation": "To use the PSR-17 factory" }, "autoload": { - "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" } + "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "extra": { "branch-alias": { From 757ea8189852c2bc04d64fb57d172c84e0b1df1d Mon Sep 17 00:00:00 2001 From: Konstantin Grachev Date: Sat, 22 Dec 2018 02:29:15 +0300 Subject: [PATCH 29/91] [Bugfix] Typo header set-sookie --- Factory/DiactorosFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index a5dcd86..c17eda9 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -144,7 +144,7 @@ public function createResponse(Response $symfonyResponse) } $headers = $symfonyResponse->headers->all(); - if (!isset($headers['Set-Cookie']) && !isset($headers['set-sookie'])) { + if (!isset($headers['Set-Cookie']) && !isset($headers['set-cookie'])) { $cookies = $symfonyResponse->headers->getCookies(); if (!empty($cookies)) { $headers['Set-Cookie'] = array(); From 36a806532bb81ba05aa044473d94e7683266a4ee Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Mar 2019 15:21:51 +0100 Subject: [PATCH 30/91] Deprecate DiactorosFactory, use nyholm/psr7 for tests --- Factory/DiactorosFactory.php | 4 ++++ Tests/Factory/DiactorosFactoryTest.php | 2 ++ Tests/Factory/PsrHttpFactoryTest.php | 16 +++------------- composer.json | 9 ++++----- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index 5d37fe2..1d03184 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -11,6 +11,8 @@ namespace Symfony\Bridge\PsrHttpMessage\Factory; +@trigger_error(sprintf('The "%s" class is deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instea.', DiactorosFactory::class), E_USER_DEPRECATED); + use Psr\Http\Message\UploadedFileInterface; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\BinaryFileResponse; @@ -28,6 +30,8 @@ * Builds Psr\HttpMessage instances using the Zend Diactoros implementation. * * @author Kévin Dunglas + * + * @deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead */ class DiactorosFactory implements HttpMessageFactoryInterface { diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index 08d37bd..5ebe7a9 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -16,6 +16,8 @@ /** * @author Kévin Dunglas * @author Antonio J. García Lagar + * + * @group legacy */ class DiactorosFactoryTest extends AbstractHttpMessageFactoryTest { diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php index 045c11d..da12ea7 100644 --- a/Tests/Factory/PsrHttpFactoryTest.php +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -11,10 +11,7 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; -use Http\Factory\Diactoros\ResponseFactory; -use Http\Factory\Diactoros\ServerRequestFactory; -use Http\Factory\Diactoros\StreamFactory; -use Http\Factory\Diactoros\UploadedFileFactory; +use Nyholm\Psr7\Factory\Psr17Factory; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; /** @@ -25,15 +22,8 @@ class PsrHttpFactoryTest extends AbstractHttpMessageFactoryTest { protected function buildHttpMessageFactory() { - if (!class_exists('Http\Factory\Diactoros\ServerRequestFactory')) { - $this->markTestSkipped('HTTP Factory for Diactoros is not installed.'); - } + $factory = new Psr17Factory(); - return new PsrHttpFactory( - new ServerRequestFactory(), - new StreamFactory(), - new UploadedFileFactory(), - new ResponseFactory() - ); + return new PsrHttpFactory($factory, $factory, $factory, $factory); } } diff --git a/composer.json b/composer.json index 80f4785..a5a7fdd 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "symfony/psr-http-message-bridge", "type": "symfony-bridge", "description": "PSR HTTP message bridge", - "keywords": ["http", "psr-7", "http-message"], + "keywords": ["http", "psr-7", "psr-17", "http-message"], "homepage": "http://symfony.com", "license": "MIT", "authors": [ @@ -21,12 +21,11 @@ "symfony/http-foundation": "^2.3.42 || ^3.4 || ^4.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3.4 || 4.0" + "symfony/phpunit-bridge": "^3.4 || ^4.0", + "nyholm/psr7": "^1.1" }, "suggest": { - "psr/http-message-implementation": "To use the HttpFoundation factory", - "zendframework/zend-diactoros": "To use the Zend Diactoros factory", - "psr/http-factory-implementation": "To use the PSR-17 factory" + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" }, "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" } From 5ee1f8f2530a08f759211ed84f572b5307c6a54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20J=2E=20Garc=C3=ADa=20Lagar?= Date: Tue, 29 Jan 2019 08:31:33 +0100 Subject: [PATCH 31/91] Fix SameSite attribute conversion from PSR7 to HttpFoundation --- Factory/HttpFoundationFactory.php | 10 +++++++++- Tests/Factory/HttpFoundationFactoryTest.php | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 65289c6..eadf215 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -210,6 +210,12 @@ private function createCookie($cookie) continue; } + + if ('samesite' === strtolower($name) && null !== $value) { + $samesite = $value; + + continue; + } } if (!isset($cookieName)) { @@ -223,7 +229,9 @@ private function createCookie($cookie) isset($cookiePath) ? $cookiePath : '/', isset($cookieDomain) ? $cookieDomain : null, isset($cookieSecure), - isset($cookieHttpOnly) + isset($cookieHttpOnly), + false, + isset($samesite) ? $samesite : null ); } } diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index 5492bec..8daa256 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -19,6 +19,7 @@ use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Stream; use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\UploadedFile; use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Uri; +use Symfony\Component\HttpFoundation\Cookie; /** * @author Kévin Dunglas @@ -199,7 +200,7 @@ public function testCreateResponse() 'Set-Cookie' => array( 'theme=light', 'test', - 'ABC=AeD; Domain=dunglas.fr; Path=/kevin; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly', + 'ABC=AeD; Domain=dunglas.fr; Path=/kevin; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; SameSite=Strict', ), ), new Stream('The response body'), @@ -230,6 +231,9 @@ public function testCreateResponse() $this->assertEquals('/kevin', $cookies[2]->getPath()); $this->assertTrue($cookies[2]->isSecure()); $this->assertTrue($cookies[2]->isHttpOnly()); + if (defined('Symfony\Component\HttpFoundation\Cookie::SAMESITE_STRICT')) { + $this->assertEquals(Cookie::SAMESITE_STRICT, $cookies[2]->getSameSite()); + } $this->assertEquals('The response body', $symfonyResponse->getContent()); $this->assertEquals(200, $symfonyResponse->getStatusCode()); From f2c48c5703242009366dd636b581bdf449ac535b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Mar 2019 15:46:50 +0100 Subject: [PATCH 32/91] fix tests --- Tests/Factory/AbstractHttpMessageFactoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php index 7216a80..5855c7a 100644 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -149,7 +149,7 @@ public function testCreateResponse() 202, array('X-Symfony' => array('3.4')) ); - $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'))); + $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, '')); $psrResponse = $this->factory->createResponse($response); $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); From 5f9a032dbe9695344775932c3e5852fbfdadbe5a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Mar 2019 15:48:11 +0100 Subject: [PATCH 33/91] typo --- Factory/DiactorosFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index e6cae14..005b988 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\PsrHttpMessage\Factory; -@trigger_error(sprintf('The "%s" class is deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instea.', DiactorosFactory::class), E_USER_DEPRECATED); +@trigger_error(sprintf('The "%s" class is deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead.', DiactorosFactory::class), E_USER_DEPRECATED); use Psr\Http\Message\UploadedFileInterface; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; From ba672d83a63252a125e8fc4dd993fccc1e8ffe1a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Mar 2019 15:48:54 +0100 Subject: [PATCH 34/91] bump branch-alias --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 692f858..39bfc68 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "1.2-dev" } } } From dd1111ed1603065141a17320f3a8d4268ac15d22 Mon Sep 17 00:00:00 2001 From: Royston Tong Date: Sun, 17 Jul 2016 18:34:41 +0000 Subject: [PATCH 35/91] removed 'Set-Cookie' from header when it is already converted to a Symfony header cookie --- Factory/HttpFoundationFactory.php | 5 ++++- Tests/Fixtures/Message.php | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index eadf215..8bf209d 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -140,6 +140,9 @@ protected function getTemporaryPath() */ public function createResponse(ResponseInterface $psrResponse) { + $cookies = $psrResponse->getHeader('Set-Cookie'); + $psrResponse = $psrResponse->withHeader('Set-Cookie', array()); + $response = new Response( $psrResponse->getBody()->__toString(), $psrResponse->getStatusCode(), @@ -147,7 +150,7 @@ public function createResponse(ResponseInterface $psrResponse) ); $response->setProtocolVersion($psrResponse->getProtocolVersion()); - foreach ($psrResponse->getHeader('Set-Cookie') as $cookie) { + foreach ($cookies as $cookie) { $response->headers->setCookie($this->createCookie($cookie)); } diff --git a/Tests/Fixtures/Message.php b/Tests/Fixtures/Message.php index 5cd0999..43ac394 100644 --- a/Tests/Fixtures/Message.php +++ b/Tests/Fixtures/Message.php @@ -64,7 +64,9 @@ public function getHeaderLine($name) public function withHeader($name, $value) { - throw new \BadMethodCallException('Not implemented.'); + $this->headers[$name] = (array) $value; + + return $this; } public function withAddedHeader($name, $value) From 921f8669c36ea0148d2520c0bb7838cda14879e0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Mar 2019 16:23:00 +0100 Subject: [PATCH 36/91] Undeprecate DiactorosFactory for 1.1 --- Factory/DiactorosFactory.php | 4 ---- Tests/Factory/DiactorosFactoryTest.php | 2 -- composer.json | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index 005b988..0948849 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -11,8 +11,6 @@ namespace Symfony\Bridge\PsrHttpMessage\Factory; -@trigger_error(sprintf('The "%s" class is deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead.', DiactorosFactory::class), E_USER_DEPRECATED); - use Psr\Http\Message\UploadedFileInterface; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\BinaryFileResponse; @@ -30,8 +28,6 @@ * Builds Psr\HttpMessage instances using the Zend Diactoros implementation. * * @author Kévin Dunglas - * - * @deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead */ class DiactorosFactory implements HttpMessageFactoryInterface { diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index 5ebe7a9..08d37bd 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -16,8 +16,6 @@ /** * @author Kévin Dunglas * @author Antonio J. García Lagar - * - * @group legacy */ class DiactorosFactoryTest extends AbstractHttpMessageFactoryTest { diff --git a/composer.json b/composer.json index 39bfc68..692f858 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.1-dev" } } } From 5e5e0c3fa41b4989d950de002e7a63af3a06b4a1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Mar 2019 16:24:51 +0100 Subject: [PATCH 37/91] Revert "Undeprecate DiactorosFactory for 1.1" This reverts commit 921f8669c36ea0148d2520c0bb7838cda14879e0. --- Factory/DiactorosFactory.php | 4 ++++ Tests/Factory/DiactorosFactoryTest.php | 2 ++ composer.json | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index 0948849..005b988 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -11,6 +11,8 @@ namespace Symfony\Bridge\PsrHttpMessage\Factory; +@trigger_error(sprintf('The "%s" class is deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead.', DiactorosFactory::class), E_USER_DEPRECATED); + use Psr\Http\Message\UploadedFileInterface; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\BinaryFileResponse; @@ -28,6 +30,8 @@ * Builds Psr\HttpMessage instances using the Zend Diactoros implementation. * * @author Kévin Dunglas + * + * @deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead */ class DiactorosFactory implements HttpMessageFactoryInterface { diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index 08d37bd..5ebe7a9 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -16,6 +16,8 @@ /** * @author Kévin Dunglas * @author Antonio J. García Lagar + * + * @group legacy */ class DiactorosFactoryTest extends AbstractHttpMessageFactoryTest { diff --git a/composer.json b/composer.json index 692f858..39bfc68 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "1.2-dev" } } } From 8e109238a24e0a7c86c4ce70b993571f3e0337f1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Mar 2019 16:16:00 +0100 Subject: [PATCH 38/91] bump to PHP 7.1 --- .gitignore | 1 + .php_cs.dist | 24 ++++++ .travis.yml | 24 +----- Factory/DiactorosFactory.php | 11 +-- Factory/HttpFoundationFactory.php | 10 +-- Factory/PsrHttpFactory.php | 11 +-- HttpFoundationFactoryInterface.php | 2 +- HttpMessageFactoryInterface.php | 2 +- .../AbstractHttpMessageFactoryTest.php | 67 +++++++++------- Tests/Factory/HttpFoundationFactoryTest.php | 80 +++++++++---------- Tests/Fixtures/Message.php | 6 +- Tests/Fixtures/Response.php | 2 +- Tests/Fixtures/ServerRequest.php | 2 +- composer.json | 9 ++- 14 files changed, 125 insertions(+), 126 deletions(-) create mode 100644 .php_cs.dist diff --git a/.gitignore b/.gitignore index c49a5d8..027924f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ vendor/ composer.lock phpunit.xml +.php_cs.cache diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..d741d39 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,24 @@ +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( + PhpCsFixer\Finder::create() + ->in(__DIR__) + ->name('*.php') + ) +; diff --git a/.travis.yml b/.travis.yml index 6c9646a..a247c61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ env: global: - PHPUNIT_FLAGS="-v" - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" - - DEPENDENCIES="zendframework/zend-diactoros:^1.4.1 http-interop/http-factory-diactoros:^1.0" matrix: fast_finish: true @@ -17,32 +16,11 @@ matrix: # Minimum supported dependencies with the latest and oldest PHP version - php: 7.2 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" - - php: 5.3 - dist: 'precise' - env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" DEPENDENCIES="" - # Test the latest stable release - - php: 5.3 - dist: 'precise' - env: DEPENDENCIES="" - - php: 5.4 - env: DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" - - php: 5.5 - env: DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" - - php: 5.6 - env: DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" - - php: 7.0 - php: 7.1 - php: 7.2 env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" - # Test LTS versions. This makes sure we do not use Symfony packages with version greater - # than 2 or 3 respectively. - - php: 7.2 - env: DEPENDENCIES="$DEPENDENCIES symfony/lts:^2 symfony/force-lowest:~2.8.0" - - php: 7.2 - env: DEPENDENCIES="$DEPENDENCIES symfony/lts:^3 symfony/force-lowest:~3.4.0" - # Latest commit to master - php: 7.2 env: STABILITY="dev" @@ -66,4 +44,4 @@ script: - composer validate --strict --no-check-lock # simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and # it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge) - - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS \ No newline at end of file + - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index 005b988..d3e70b5 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -52,12 +52,7 @@ public function createRequest(Request $symfonyRequest) : \Zend\Diactoros\normalizeServer($symfonyRequest->server->all()); $headers = $symfonyRequest->headers->all(); - if (PHP_VERSION_ID < 50600) { - $body = new DiactorosStream('php://temp', 'wb+'); - $body->write($symfonyRequest->getContent()); - } else { - $body = new DiactorosStream($symfonyRequest->getContent(true)); - } + $body = new DiactorosStream($symfonyRequest->getContent(true)); $files = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeFiles') ? DiactorosRequestFactory::normalizeFiles($this->getFiles($symfonyRequest->files->all())) @@ -95,7 +90,7 @@ public function createRequest(Request $symfonyRequest) */ private function getFiles(array $uploadedFiles) { - $files = array(); + $files = []; foreach ($uploadedFiles as $key => $value) { if (null === $value) { @@ -157,7 +152,7 @@ public function createResponse(Response $symfonyResponse) if (!isset($headers['Set-Cookie']) && !isset($headers['set-cookie'])) { $cookies = $symfonyResponse->headers->getCookies(); if (!empty($cookies)) { - $headers['Set-Cookie'] = array(); + $headers['Set-Cookie'] = []; foreach ($cookies as $cookie) { $headers['Set-Cookie'][] = $cookie->__toString(); } diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 8bf209d..f048564 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -11,8 +11,8 @@ namespace Symfony\Bridge\PsrHttpMessage\Factory; -use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UriInterface; use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; @@ -33,7 +33,7 @@ class HttpFoundationFactory implements HttpFoundationFactoryInterface */ public function createRequest(ServerRequestInterface $psrRequest) { - $server = array(); + $server = []; $uri = $psrRequest->getUri(); if ($uri instanceof UriInterface) { @@ -48,7 +48,7 @@ public function createRequest(ServerRequestInterface $psrRequest) $server = array_replace($server, $psrRequest->getServerParams()); $parsedBody = $psrRequest->getParsedBody(); - $parsedBody = is_array($parsedBody) ? $parsedBody : array(); + $parsedBody = \is_array($parsedBody) ? $parsedBody : []; $request = new Request( $psrRequest->getQueryParams(), @@ -73,7 +73,7 @@ public function createRequest(ServerRequestInterface $psrRequest) */ private function getFiles(array $uploadedFiles) { - $files = array(); + $files = []; foreach ($uploadedFiles as $key => $value) { if ($value instanceof UploadedFileInterface) { @@ -141,7 +141,7 @@ protected function getTemporaryPath() public function createResponse(ResponseInterface $psrResponse) { $cookies = $psrResponse->getHeader('Set-Cookie'); - $psrResponse = $psrResponse->withHeader('Set-Cookie', array()); + $psrResponse = $psrResponse->withHeader('Set-Cookie', []); $response = new Response( $psrResponse->getBody()->__toString(), diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index c4c48a1..09405ba 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -58,12 +58,7 @@ public function createRequest(Request $symfonyRequest) $request = $request->withHeader($name, $value); } - if (PHP_VERSION_ID < 50600) { - $body = $this->streamFactory->createStreamFromFile('php://temp', 'wb+'); - $body->write($symfonyRequest->getContent()); - } else { - $body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true)); - } + $body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true)); $request = $request ->withBody($body) @@ -89,7 +84,7 @@ public function createRequest(Request $symfonyRequest) */ private function getFiles(array $uploadedFiles) { - $files = array(); + $files = []; foreach ($uploadedFiles as $key => $value) { if (null === $value) { @@ -158,7 +153,7 @@ public function createResponse(Response $symfonyResponse) $headers = $symfonyResponse->headers->all(); $cookies = $symfonyResponse->headers->getCookies(); if (!empty($cookies)) { - $headers['Set-Cookie'] = array(); + $headers['Set-Cookie'] = []; foreach ($cookies as $cookie) { $headers['Set-Cookie'][] = $cookie->__toString(); diff --git a/HttpFoundationFactoryInterface.php b/HttpFoundationFactoryInterface.php index 32ec456..79ec40f 100644 --- a/HttpFoundationFactoryInterface.php +++ b/HttpFoundationFactoryInterface.php @@ -11,8 +11,8 @@ namespace Symfony\Bridge\PsrHttpMessage; -use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/HttpMessageFactoryInterface.php b/HttpMessageFactoryInterface.php index 1367c8c..b7960e8 100644 --- a/HttpMessageFactoryInterface.php +++ b/HttpMessageFactoryInterface.php @@ -11,8 +11,8 @@ namespace Symfony\Bridge\PsrHttpMessage; -use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php index 5855c7a..83df2c0 100644 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -44,36 +44,36 @@ public function testCreateRequest() { $stdClass = new \stdClass(); $request = new Request( - array( + [ 'foo' => '1', - 'bar' => array('baz' => '42'), - ), - array( - 'twitter' => array( + 'bar' => ['baz' => '42'], + ], + [ + 'twitter' => [ '@dunglas' => 'Kévin Dunglas', '@coopTilleuls' => 'Les-Tilleuls.coop', - ), + ], 'baz' => '2', - ), - array( + ], + [ 'a1' => $stdClass, - 'a2' => array('foo' => 'bar'), - ), - array( + 'a2' => ['foo' => 'bar'], + ], + [ 'c1' => 'foo', - 'c2' => array('c3' => 'bar'), - ), - array( + 'c2' => ['c3' => 'bar'], + ], + [ 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK), - 'foo' => array('f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)), - ), - array( + 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)], + ], + [ 'REQUEST_METHOD' => 'POST', 'HTTP_HOST' => 'dunglas.fr', 'HTTP_X_SYMFONY' => '2.8', 'REQUEST_URI' => '/testCreateRequest?foo=1&bar[baz]=42', 'QUERY_STRING' => 'foo=1&bar[baz]=42', - ), + ], 'Content' ); @@ -116,13 +116,13 @@ public function testCreateRequest() $this->assertEquals('POST', $serverParams['REQUEST_METHOD']); $this->assertEquals('2.8', $serverParams['HTTP_X_SYMFONY']); $this->assertEquals('POST', $psrRequest->getMethod()); - $this->assertEquals(array('2.8'), $psrRequest->getHeader('X-Symfony')); + $this->assertEquals(['2.8'], $psrRequest->getHeader('X-Symfony')); } public function testGetContentCanBeCalledAfterRequestCreation() { - $header = array('HTTP_HOST' => 'dunglas.fr'); - $request = new Request(array(), array(), array(), array(), array(), $header, 'Content'); + $header = ['HTTP_HOST' => 'dunglas.fr']; + $request = new Request([], [], [], [], [], $header, 'Content'); $psrRequest = $this->factory->createRequest($request); @@ -139,6 +139,7 @@ private function createUploadedFile($content, $originalName, $mimeType, $error) // Symfony 4.1+ return new UploadedFile($path, $originalName, $mimeType, $error, true); } + return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true); } @@ -147,14 +148,14 @@ public function testCreateResponse() $response = new Response( 'Response content.', 202, - array('X-Symfony' => array('3.4')) + ['X-Symfony' => ['3.4']] ); - $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, '')); + $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, null)); $psrResponse = $this->factory->createResponse($response); $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); $this->assertEquals(202, $psrResponse->getStatusCode()); - $this->assertEquals(array('3.4'), $psrResponse->getHeader('X-Symfony')); + $this->assertEquals(['3.4'], $psrResponse->getHeader('X-Symfony')); $cookieHeader = $psrResponse->getHeader('Set-Cookie'); $this->assertInternalType('array', $cookieHeader); @@ -201,17 +202,21 @@ public function testUploadErrNoFile() $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError()); $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); - $request = new Request(array(), array(), array(), array(), - array( + $request = new Request( + [], + [], + [], + [], + [ 'f1' => $file, - 'f2' => array('name' => null, 'type' => null, 'tmp_name' => null, 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0), - ), - array( + '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', - ), - 'Content' + ], + 'Content' ); $psrRequest = $this->factory->createRequest($request); diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index 8daa256..ca18e24 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -43,28 +43,28 @@ public function testCreateRequest() $stdClass = new \stdClass(); $serverRequest = new ServerRequest( '1.1', - array( + [ 'X-Dunglas-API-Platform' => '1.0', - 'X-data' => array('a', 'b'), - ), + 'X-data' => ['a', 'b'], + ], new Stream('The body'), '/about/kevin', 'GET', 'http://les-tilleuls.coop/about/kevin', - array('country' => 'France'), - array('city' => 'Lille'), - array('url' => 'http://les-tilleuls.coop'), - array( + ['country' => 'France'], + ['city' => 'Lille'], + ['url' => 'http://les-tilleuls.coop'], + [ 'doc1' => $this->createUploadedFile('Doc 1', UPLOAD_ERR_OK, 'doc1.txt', 'text/plain'), - 'nested' => array( - 'docs' => array( + 'nested' => [ + 'docs' => [ $this->createUploadedFile('Doc 2', UPLOAD_ERR_OK, 'doc2.txt', 'text/plain'), $this->createUploadedFile('Doc 3', UPLOAD_ERR_OK, 'doc3.txt', 'text/plain'), - ), - ), - ), - array('url' => 'http://dunglas.fr'), - array('custom' => $stdClass) + ], + ], + ], + ['url' => 'http://dunglas.fr'], + ['custom' => $stdClass] ); $symfonyRequest = $this->factory->createRequest($serverRequest); @@ -80,24 +80,24 @@ public function testCreateRequest() $this->assertEquals('France', $symfonyRequest->server->get('country')); $this->assertEquals('The body', $symfonyRequest->getContent()); $this->assertEquals('1.0', $symfonyRequest->headers->get('X-Dunglas-API-Platform')); - $this->assertEquals(array('a', 'b'), $symfonyRequest->headers->get('X-data', null, false)); + $this->assertEquals(['a', 'b'], $symfonyRequest->headers->get('X-data', null, false)); } public function testCreateRequestWithNullParsedBody() { $serverRequest = new ServerRequest( '1.1', - array(), + [], new Stream(), '/', 'GET', null, - array(), - array(), - array(), - array(), + [], + [], + [], + [], null, - array() + [] ); $this->assertCount(0, $this->factory->createRequest($serverRequest)->request); @@ -107,17 +107,17 @@ public function testCreateRequestWithObjectParsedBody() { $serverRequest = new ServerRequest( '1.1', - array(), + [], new Stream(), '/', 'GET', null, - array(), - array(), - array(), - array(), + [], + [], + [], + [], new \stdClass(), - array() + [] ); $this->assertCount(0, $this->factory->createRequest($serverRequest)->request); @@ -127,17 +127,17 @@ public function testCreateRequestWithUri() { $serverRequest = new ServerRequest( '1.1', - array(), + [], new Stream(), '/', 'GET', new Uri('http://les-tilleuls.coop/about/kevin'), - array(), - array(), - array(), - array(), + [], + [], + [], + [], null, - array() + [] ); $this->assertEquals('/about/kevin', $this->factory->createRequest($serverRequest)->getPathInfo()); @@ -188,21 +188,21 @@ private function callCreateUploadedFile(UploadedFileInterface $uploadedFile) $createUploadedFile = $reflection->getMethod('createUploadedFile'); $createUploadedFile->setAccessible(true); - return $createUploadedFile->invokeArgs($this->factory, array($uploadedFile)); + return $createUploadedFile->invokeArgs($this->factory, [$uploadedFile]); } public function testCreateResponse() { $response = new Response( '1.0', - array( - 'X-Symfony' => array('2.8'), - 'Set-Cookie' => array( + [ + 'X-Symfony' => ['2.8'], + 'Set-Cookie' => [ 'theme=light', 'test', 'ABC=AeD; Domain=dunglas.fr; Path=/kevin; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; SameSite=Strict', - ), - ), + ], + ], new Stream('The response body'), 200 ); @@ -231,7 +231,7 @@ public function testCreateResponse() $this->assertEquals('/kevin', $cookies[2]->getPath()); $this->assertTrue($cookies[2]->isSecure()); $this->assertTrue($cookies[2]->isHttpOnly()); - if (defined('Symfony\Component\HttpFoundation\Cookie::SAMESITE_STRICT')) { + if (\defined('Symfony\Component\HttpFoundation\Cookie::SAMESITE_STRICT')) { $this->assertEquals(Cookie::SAMESITE_STRICT, $cookies[2]->getSameSite()); } diff --git a/Tests/Fixtures/Message.php b/Tests/Fixtures/Message.php index 43ac394..a9b0c66 100644 --- a/Tests/Fixtures/Message.php +++ b/Tests/Fixtures/Message.php @@ -22,10 +22,10 @@ class Message implements MessageInterface { private $version = '1.1'; - private $headers = array(); + private $headers = []; private $body; - public function __construct($version = '1.1', array $headers = array(), StreamInterface $body = null) + public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null) { $this->version = $version; $this->headers = $headers; @@ -54,7 +54,7 @@ public function hasHeader($name) public function getHeader($name) { - return $this->hasHeader($name) ? $this->headers[$name] : array(); + return $this->hasHeader($name) ? $this->headers[$name] : []; } public function getHeaderLine($name) diff --git a/Tests/Fixtures/Response.php b/Tests/Fixtures/Response.php index 0fd85c2..a890792 100644 --- a/Tests/Fixtures/Response.php +++ b/Tests/Fixtures/Response.php @@ -21,7 +21,7 @@ class Response extends Message implements ResponseInterface { private $statusCode; - public function __construct($version = '1.1', array $headers = array(), StreamInterface $body = null, $statusCode = 200) + public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $statusCode = 200) { parent::__construct($version, $headers, $body); diff --git a/Tests/Fixtures/ServerRequest.php b/Tests/Fixtures/ServerRequest.php index 63b8c06..88ec984 100644 --- a/Tests/Fixtures/ServerRequest.php +++ b/Tests/Fixtures/ServerRequest.php @@ -30,7 +30,7 @@ class ServerRequest extends Message implements ServerRequestInterface private $data; private $attributes; - public function __construct($version = '1.1', array $headers = array(), StreamInterface $body = null, $requestTarget = '/', $method = 'GET', $uri = null, array $server = array(), array $cookies = array(), array $query = array(), array $uploadedFiles = array(), $data = null, array $attributes = array()) + public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $requestTarget = '/', $method = 'GET', $uri = null, array $server = [], array $cookies = [], array $query = [], array $uploadedFiles = [], $data = null, array $attributes = []) { parent::__construct($version, $headers, $body); diff --git a/composer.json b/composer.json index 39bfc68..51bb655 100644 --- a/composer.json +++ b/composer.json @@ -16,13 +16,14 @@ } ], "require": { - "php": "^5.3.3 || ^7.0", + "php": "^7.1", "psr/http-message": "^1.0", - "symfony/http-foundation": "^2.3.42 || ^3.4 || ^4.0" + "symfony/http-foundation": "^3.4 || ^4.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3.4 || ^4.0", - "nyholm/psr7": "^1.1" + "symfony/phpunit-bridge": "^3.4.20 || ^4.0", + "nyholm/psr7": "^1.1", + "zendframework/zend-diactoros": "^1.4.1 || ^2.0" }, "suggest": { "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" From 7cc16058ff9c5fc2d568ca512771bd9fa40cc28b Mon Sep 17 00:00:00 2001 From: Daniel Gimenes Date: Wed, 26 Sep 2018 15:39:07 -0300 Subject: [PATCH 39/91] Add support for streamed response --- Factory/HttpFoundationFactory.php | 52 ++++++++++++++++++--- Tests/Factory/HttpFoundationFactoryTest.php | 9 ++++ Tests/Fixtures/Stream.php | 8 +++- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index f048564..023a480 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -13,6 +13,7 @@ use Psr\Http\Message\ResponseInterface; 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; @@ -20,6 +21,7 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; /** * {@inheritdoc} @@ -28,6 +30,16 @@ */ class HttpFoundationFactory implements HttpFoundationFactoryInterface { + /** + * @var int The maximum output buffering size for each iteration when sending the response + */ + private $responseBufferMaxLength; + + public function __construct(int $responseBufferMaxLength = 16372) + { + $this->responseBufferMaxLength = $responseBufferMaxLength; + } + /** * {@inheritdoc} */ @@ -138,16 +150,25 @@ protected function getTemporaryPath() /** * {@inheritdoc} */ - public function createResponse(ResponseInterface $psrResponse) + public function createResponse(ResponseInterface $psrResponse, bool $streamed = false) { $cookies = $psrResponse->getHeader('Set-Cookie'); $psrResponse = $psrResponse->withHeader('Set-Cookie', []); - $response = new Response( - $psrResponse->getBody()->__toString(), - $psrResponse->getStatusCode(), - $psrResponse->getHeaders() - ); + if ($streamed) { + $response = new StreamedResponse( + $this->createStreamedResponseCallback($psrResponse->getBody()), + $psrResponse->getStatusCode(), + $psrResponse->getHeaders() + ); + } else { + $response = new Response( + $psrResponse->getBody()->__toString(), + $psrResponse->getStatusCode(), + $psrResponse->getHeaders() + ); + } + $response->setProtocolVersion($psrResponse->getProtocolVersion()); foreach ($cookies as $cookie) { @@ -237,4 +258,23 @@ private function createCookie($cookie) isset($samesite) ? $samesite : null ); } + + private function createStreamedResponseCallback(StreamInterface $body): callable + { + return function () use ($body) { + if ($body->isSeekable()) { + $body->rewind(); + } + + if (!$body->isReadable()) { + echo $body; + + return; + } + + while (!$body->eof()) { + echo $body->read($this->responseBufferMaxLength); + } + }; + } } diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index ca18e24..27b84c0 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -237,5 +237,14 @@ public function testCreateResponse() $this->assertEquals('The response body', $symfonyResponse->getContent()); $this->assertEquals(200, $symfonyResponse->getStatusCode()); + + $symfonyResponse = $this->factory->createResponse($response, true); + + ob_start(); + $symfonyResponse->sendContent(); + $sentContent = ob_get_clean(); + + $this->assertEquals('The response body', $sentContent); + $this->assertEquals(200, $symfonyResponse->getStatusCode()); } } diff --git a/Tests/Fixtures/Stream.php b/Tests/Fixtures/Stream.php index aeca3d8..aa0ba24 100644 --- a/Tests/Fixtures/Stream.php +++ b/Tests/Fixtures/Stream.php @@ -19,6 +19,7 @@ class Stream implements StreamInterface { private $stringContent; + private $eof = true; public function __construct($stringContent = '') { @@ -49,12 +50,12 @@ public function tell() public function eof() { - return true; + return $this->eof; } public function isSeekable() { - return false; + return true; } public function seek($offset, $whence = SEEK_SET) @@ -63,6 +64,7 @@ public function seek($offset, $whence = SEEK_SET) public function rewind() { + $this->eof = false; } public function isWritable() @@ -81,6 +83,8 @@ public function isReadable() public function read($length) { + $this->eof = true; + return $this->stringContent; } From 8564bf76630423ced21bbbee189947b90677dcde Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Wed, 8 Aug 2018 17:40:53 +0200 Subject: [PATCH 40/91] Convert Request/Response multiple times --- Factory/HttpFoundationFactory.php | 2 +- Factory/PsrHttpFactory.php | 4 +- .../AbstractHttpMessageFactoryTest.php | 10 +- Tests/Fixtures/Message.php | 4 +- Tests/Functional/CovertTest.php | 239 ++++++++++++++++++ 5 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 Tests/Functional/CovertTest.php diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index f048564..9575853 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -141,7 +141,7 @@ protected function getTemporaryPath() public function createResponse(ResponseInterface $psrResponse) { $cookies = $psrResponse->getHeader('Set-Cookie'); - $psrResponse = $psrResponse->withHeader('Set-Cookie', []); + $psrResponse = $psrResponse->withoutHeader('Set-Cookie'); $response = new Response( $psrResponse->getBody()->__toString(), diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index 09405ba..a37e1e9 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -50,7 +50,7 @@ public function createRequest(Request $symfonyRequest) { $request = $this->serverRequestFactory->createServerRequest( $symfonyRequest->getMethod(), - $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getRequestUri(), + $symfonyRequest->getUri(), $symfonyRequest->server->all() ); @@ -126,7 +126,7 @@ private function createUploadedFile(UploadedFile $symfonyUploadedFile) */ public function createResponse(Response $symfonyResponse) { - $response = $this->responseFactory->createResponse($symfonyResponse->getStatusCode()); + $response = $this->responseFactory->createResponse($symfonyResponse->getStatusCode(), Response::$statusTexts[$symfonyResponse->getStatusCode()] ?? ''); if ($symfonyResponse instanceof BinaryFileResponse) { $stream = $this->streamFactory->createStreamFromFile( diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php index 83df2c0..4b8bb97 100644 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -45,8 +45,8 @@ public function testCreateRequest() $stdClass = new \stdClass(); $request = new Request( [ - 'foo' => '1', 'bar' => ['baz' => '42'], + 'foo' => '1', ], [ 'twitter' => [ @@ -71,8 +71,8 @@ public function testCreateRequest() 'REQUEST_METHOD' => 'POST', 'HTTP_HOST' => 'dunglas.fr', 'HTTP_X_SYMFONY' => '2.8', - 'REQUEST_URI' => '/testCreateRequest?foo=1&bar[baz]=42', - 'QUERY_STRING' => 'foo=1&bar[baz]=42', + 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1', + 'QUERY_STRING' => 'bar[baz]=42&foo=1', ], 'Content' ); @@ -86,7 +86,7 @@ public function testCreateRequest() $this->assertEquals('42', $queryParams['bar']['baz']); $requestTarget = $psrRequest->getRequestTarget(); - $this->assertEquals('/testCreateRequest?foo=1&bar[baz]=42', urldecode($requestTarget)); + $this->assertEquals('/testCreateRequest?bar[baz]=42&foo=1', urldecode($requestTarget)); $parsedBody = $psrRequest->getParsedBody(); $this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']); @@ -150,7 +150,7 @@ public function testCreateResponse() 202, ['X-Symfony' => ['3.4']] ); - $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, null)); + $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); $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); diff --git a/Tests/Fixtures/Message.php b/Tests/Fixtures/Message.php index a9b0c66..0cda6fc 100644 --- a/Tests/Fixtures/Message.php +++ b/Tests/Fixtures/Message.php @@ -76,7 +76,9 @@ public function withAddedHeader($name, $value) public function withoutHeader($name) { - throw new \BadMethodCallException('Not implemented.'); + unset($this->headers[$name]); + + return $this; } public function getBody() diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php new file mode 100644 index 0000000..33ec34f --- /dev/null +++ b/Tests/Functional/CovertTest.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Tests\Functional; + +use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Response as Psr7Response; +use Nyholm\Psr7\ServerRequest as Psr7Request; +use Nyholm\Psr7\Stream as Psr7Stream; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; +use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; +use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Test to convert a request/response back and forth to make sure we do not loose data. + * + * @author Tobias Nyholm + */ +class CovertTest extends TestCase +{ + private $tmpDir; + + public function setup() + { + if (!class_exists('Nyholm\Psr7\ServerRequest')) { + $this->markTestSkipped('nyholm/psr7 is not installed.'); + } + + $this->tmpDir = sys_get_temp_dir(); + } + + /** + * @dataProvider requestProvider + * + * @param Request|ServerRequestInterface $request + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory + */ + public function testConvertRequestMultipleTimes($request, $firstFactory, $secondFactory) + { + $temporaryRequest = $firstFactory->createRequest($request); + $finalRequest = $secondFactory->createRequest($temporaryRequest); + + if ($finalRequest instanceof Request) { + $this->assertEquals($request->getBasePath(), $finalRequest->getBasePath()); + $this->assertEquals($request->getBaseUrl(), $finalRequest->getBaseUrl()); + $this->assertEquals($request->getContent(), $finalRequest->getContent()); + $this->assertEquals($request->getEncodings(), $finalRequest->getEncodings()); + $this->assertEquals($request->getETags(), $finalRequest->getETags()); + $this->assertEquals($request->getHost(), $finalRequest->getHost()); + $this->assertEquals($request->getHttpHost(), $finalRequest->getHttpHost()); + $this->assertEquals($request->getMethod(), $finalRequest->getMethod()); + $this->assertEquals($request->getPassword(), $finalRequest->getPassword()); + $this->assertEquals($request->getPathInfo(), $finalRequest->getPathInfo()); + $this->assertEquals($request->getPort(), $finalRequest->getPort()); + $this->assertEquals($request->getProtocolVersion(), $finalRequest->getProtocolVersion()); + $this->assertEquals($request->getQueryString(), $finalRequest->getQueryString()); + $this->assertEquals($request->getRequestUri(), $finalRequest->getRequestUri()); + $this->assertEquals($request->getScheme(), $finalRequest->getScheme()); + $this->assertEquals($request->getSchemeAndHttpHost(), $finalRequest->getSchemeAndHttpHost()); + $this->assertEquals($request->getScriptName(), $finalRequest->getScriptName()); + $this->assertEquals($request->getUri(), $finalRequest->getUri()); + $this->assertEquals($request->getUser(), $finalRequest->getUser()); + $this->assertEquals($request->getUserInfo(), $finalRequest->getUserInfo()); + } elseif ($finalRequest instanceof ServerRequestInterface) { + $strToLower = function ($arr) { + foreach ($arr as $key => $value) { + yield strtolower($key) => $value; + } + }; + $this->assertEquals($request->getAttributes(), $finalRequest->getAttributes()); + $this->assertEquals($request->getCookieParams(), $finalRequest->getCookieParams()); + $this->assertEquals((array) $request->getParsedBody(), (array) $finalRequest->getParsedBody()); + $this->assertEquals($request->getQueryParams(), $finalRequest->getQueryParams()); + // PSR7 does not define a "withServerParams" so this is impossible to implement without knowing the PSR7 implementation. + //$this->assertEquals($request->getServerParams(), $finalRequest->getServerParams()); + $this->assertEquals($request->getUploadedFiles(), $finalRequest->getUploadedFiles()); + $this->assertEquals($request->getMethod(), $finalRequest->getMethod()); + $this->assertEquals($request->getRequestTarget(), $finalRequest->getRequestTarget()); + $this->assertEquals((string) $request->getUri(), (string) $finalRequest->getUri()); + $this->assertEquals((string) $request->getBody(), (string) $finalRequest->getBody()); + $this->assertEquals($strToLower($request->getHeaders()), $strToLower($finalRequest->getHeaders())); + $this->assertEquals($request->getProtocolVersion(), $finalRequest->getProtocolVersion()); + } else { + $this->fail('$finalRequest must be an instance of PSR7 or a HTTPFoundation request'); + } + } + + public function requestProvider() + { + $sfRequest = new Request( + [ + 'foo' => '1', + 'bar' => ['baz' => '42'], + ], + [ + 'twitter' => [ + '@dunglas' => 'Kévin Dunglas', + '@coopTilleuls' => 'Les-Tilleuls.coop', + ], + 'baz' => '2', + ], + [ + 'a2' => ['foo' => 'bar'], + ], + [ + 'c1' => 'foo', + 'c2' => ['c3' => 'bar'], + ], + [ + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK), + 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)], + ], + [ + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'SERVER_NAME' => 'dunglas.fr', + 'SERVER_PORT' => null, + 'HTTP_X_SYMFONY' => '2.8', + 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1', + 'QUERY_STRING' => 'foo=1&bar[baz]=42', + ], + 'Content' + ); + + $psr7Request = (new Psr7Request('POST', 'http://tnyholm.se/foo/?bar=biz')) + ->withQueryParams(['bar' => 'biz']); + + $nyholmFactory = new Psr17Factory(); + $psr17Factory = new PsrHttpFactory($nyholmFactory, $nyholmFactory, $nyholmFactory, $nyholmFactory); + $symfonyFactory = new HttpFoundationFactory(); + + return [ + [$sfRequest, $psr17Factory, $symfonyFactory], + [$psr7Request, $symfonyFactory, $psr17Factory], + ]; + } + + /** + * @dataProvider responseProvider + * + * @param Response|ResponseInterface $response + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $firstFactory + * @param HttpFoundationFactoryInterface|HttpMessageFactoryInterface $secondFactory + */ + public function testConvertResponseMultipleTimes($response, $firstFactory, $secondFactory) + { + $temporaryResponse = $firstFactory->createResponse($response); + $finalResponse = $secondFactory->createResponse($temporaryResponse); + + if ($finalResponse instanceof Response) { + $this->assertEquals($response->getAge(), $finalResponse->getAge()); + $this->assertEquals($response->getCharset(), $finalResponse->getCharset()); + $this->assertEquals($response->getContent(), $finalResponse->getContent()); + $this->assertEquals($response->getDate(), $finalResponse->getDate()); + $this->assertEquals($response->getEtag(), $finalResponse->getEtag()); + $this->assertEquals($response->getExpires(), $finalResponse->getExpires()); + $this->assertEquals($response->getLastModified(), $finalResponse->getLastModified()); + $this->assertEquals($response->getMaxAge(), $finalResponse->getMaxAge()); + $this->assertEquals($response->getProtocolVersion(), $finalResponse->getProtocolVersion()); + $this->assertEquals($response->getStatusCode(), $finalResponse->getStatusCode()); + $this->assertEquals($response->getTtl(), $finalResponse->getTtl()); + } elseif ($finalResponse instanceof ResponseInterface) { + $strToLower = function ($arr) { + foreach ($arr as $key => $value) { + yield strtolower($key) => $value; + } + }; + $this->assertEquals($response->getStatusCode(), $finalResponse->getStatusCode()); + $this->assertEquals($response->getReasonPhrase(), $finalResponse->getReasonPhrase()); + $this->assertEquals((string) $response->getBody(), (string) $finalResponse->getBody()); + $this->assertEquals($strToLower($response->getHeaders()), $strToLower($finalResponse->getHeaders())); + $this->assertEquals($response->getProtocolVersion(), $finalResponse->getProtocolVersion()); + } else { + $this->fail('$finalResponse must be an instance of PSR7 or a HTTPFoundation response'); + } + } + + public function responseProvider() + { + $sfResponse = new Response( + 'Response content.', + 202, + ['x-symfony' => ['3.4']] + ); + + 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(); + $status = 302; + $headers = [ + 'location' => ['http://example.com/'], + ]; + $zendResponse = new Psr7Response($status, $headers, $body); + + $nyholmFactory = new Psr17Factory(); + $psr17Factory = new PsrHttpFactory($nyholmFactory, $nyholmFactory, $nyholmFactory, $nyholmFactory); + $symfonyFactory = new HttpFoundationFactory(); + + return [ + [$sfResponse, $psr17Factory, $symfonyFactory], + [$zendResponse, $symfonyFactory, $psr17Factory], + ]; + } + + private function createUploadedFile($content, $originalName, $mimeType, $error) + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, $content); + + if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { + // Symfony 4.1+ + return new UploadedFile($path, $originalName, $mimeType, $error, true); + } + + return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true); + } +} From 59b94061d383aa26f4e97eb09331a8b12da10d9b Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Mon, 11 Mar 2019 18:58:55 +0100 Subject: [PATCH 41/91] Added links to documentation --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index d2b2d37..87fbd43 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ Provides integration for PSR7. Resources --------- + * [Documentation](https://symfony.com/doc/current/components/psr7.html) + * [SensioFrameworkExtraBundle](https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html#psr-7-support) + +Running the tests +----------------- + If you want to run the unit tests, install dev dependencies before running PHPUnit: From 580de38a50c5556f45c170b47612ea022053c95b Mon Sep 17 00:00:00 2001 From: Daniel Degasperi Date: Wed, 27 Mar 2019 10:29:29 +0100 Subject: [PATCH 42/91] Fixed createResponse --- Factory/HttpFoundationFactory.php | 2 +- Tests/Fixtures/Message.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 8bf209d..8842ad0 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -141,7 +141,7 @@ protected function getTemporaryPath() public function createResponse(ResponseInterface $psrResponse) { $cookies = $psrResponse->getHeader('Set-Cookie'); - $psrResponse = $psrResponse->withHeader('Set-Cookie', array()); + $psrResponse = $psrResponse->withoutHeader('Set-Cookie'); $response = new Response( $psrResponse->getBody()->__toString(), diff --git a/Tests/Fixtures/Message.php b/Tests/Fixtures/Message.php index 43ac394..30f2b79 100644 --- a/Tests/Fixtures/Message.php +++ b/Tests/Fixtures/Message.php @@ -76,7 +76,9 @@ public function withAddedHeader($name, $value) public function withoutHeader($name) { - throw new \BadMethodCallException('Not implemented.'); + unset($this->headers[$name]); + + return $this; } public function getBody() From 19905b08fcfe3746e25d9bf344b0ef6a75e70431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20J=2E=20Garc=C3=ADa=20Lagar?= Date: Wed, 27 Mar 2019 12:25:23 +0100 Subject: [PATCH 43/91] Fix tests --- .travis.yml | 14 +++++++------ .../AbstractHttpMessageFactoryTest.php | 2 +- Tests/Factory/PsrHttpFactoryTest.php | 20 +++++++++++++++++-- composer.json | 3 +-- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6c9646a..8ac385b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,13 @@ env: global: - PHPUNIT_FLAGS="-v" - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" - - DEPENDENCIES="zendframework/zend-diactoros:^1.4.1 http-interop/http-factory-diactoros:^1.0" + - DEPENDENCIES="nyholm/psr7:^1.1" matrix: fast_finish: true include: # Minimum supported dependencies with the latest and oldest PHP version - - php: 7.2 + - php: 7.3 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" - php: 5.3 dist: 'precise' @@ -24,7 +24,7 @@ matrix: # Test the latest stable release - php: 5.3 dist: 'precise' - env: DEPENDENCIES="" + env: DEPENDENCIES="symfony/phpunit-bridge:>=3.4,<3.4.22||>=4.0,<4.1.11||>=4.2,<4.2.3" - php: 5.4 env: DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" - php: 5.5 @@ -32,19 +32,21 @@ matrix: - php: 5.6 env: DEPENDENCIES="zendframework/zend-diactoros:^1.4.1" - php: 7.0 + env: DEPENDENCIES="zendframework/zend-diactoros:^1.4.1 http-interop/http-factory-diactoros:^1.0" - php: 7.1 - php: 7.2 + - php: 7.3 env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" # Test LTS versions. This makes sure we do not use Symfony packages with version greater # than 2 or 3 respectively. - - php: 7.2 + - php: 7.3 env: DEPENDENCIES="$DEPENDENCIES symfony/lts:^2 symfony/force-lowest:~2.8.0" - - php: 7.2 + - php: 7.3 env: DEPENDENCIES="$DEPENDENCIES symfony/lts:^3 symfony/force-lowest:~3.4.0" # Latest commit to master - - php: 7.2 + - php: 7.3 env: STABILITY="dev" allow_failures: diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php index 5855c7a..d3e3f7e 100644 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -149,7 +149,7 @@ public function testCreateResponse() 202, array('X-Symfony' => array('3.4')) ); - $response->headers->setCookie(new Cookie('city', 'Lille', new \DateTime('Wed, 13 Jan 2021 22:23:01 GMT'), '/', null, false, true, false, '')); + $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); $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php index da12ea7..1aeff07 100644 --- a/Tests/Factory/PsrHttpFactoryTest.php +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; +use Http\Factory\Diactoros\ResponseFactory; +use Http\Factory\Diactoros\ServerRequestFactory; +use Http\Factory\Diactoros\StreamFactory; +use Http\Factory\Diactoros\UploadedFileFactory; use Nyholm\Psr7\Factory\Psr17Factory; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; @@ -22,8 +26,20 @@ class PsrHttpFactoryTest extends AbstractHttpMessageFactoryTest { protected function buildHttpMessageFactory() { - $factory = new Psr17Factory(); + if (class_exists('Nyholm\Psr7\Factory\Psr17Factory')) { + $factory = new Psr17Factory(); + return new PsrHttpFactory($factory, $factory, $factory, $factory); + } - return new PsrHttpFactory($factory, $factory, $factory, $factory); + if (class_exists('Http\Factory\Diactoros\ServerRequestFactory')) { + return new PsrHttpFactory( + new ServerRequestFactory(), + new StreamFactory(), + new UploadedFileFactory(), + new ResponseFactory() + ); + } + + $this->markTestSkipped('No PSR-17 HTTP Factory installed.'); } } diff --git a/composer.json b/composer.json index 692f858..c1eb5fb 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,7 @@ "symfony/http-foundation": "^2.3.42 || ^3.4 || ^4.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3.4 || ^4.0", - "nyholm/psr7": "^1.1" + "symfony/phpunit-bridge": "^3.4 || ^4.0" }, "suggest": { "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" From 1158149d9254d1d4cb123f3a166b64c8f36a6616 Mon Sep 17 00:00:00 2001 From: Rein Baarsma Date: Sat, 23 Nov 2019 13:40:32 +0100 Subject: [PATCH 44/91] Allow Symfony 5.0 --- .../Factory/AbstractHttpMessageFactoryTest.php | 9 +++------ Tests/Factory/DiactorosFactoryTest.php | 3 ++- Tests/Factory/HttpFoundationFactoryTest.php | 17 +++++++++-------- Tests/Factory/PsrHttpFactoryTest.php | 3 ++- Tests/Functional/CovertTest.php | 2 +- composer.json | 4 ++-- phpunit.xml.dist | 1 - 7 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php index 4b8bb97..acb7f9e 100644 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -29,12 +29,9 @@ abstract class AbstractHttpMessageFactoryTest extends TestCase private $factory; private $tmpDir; - /** - * @return HttpMessageFactoryInterface - */ - abstract protected function buildHttpMessageFactory(); + abstract protected function buildHttpMessageFactory(): HttpMessageFactoryInterface; - public function setup() + public function setUp(): void { $this->factory = $this->buildHttpMessageFactory(); $this->tmpDir = sys_get_temp_dir(); @@ -158,7 +155,7 @@ public function testCreateResponse() $this->assertEquals(['3.4'], $psrResponse->getHeader('X-Symfony')); $cookieHeader = $psrResponse->getHeader('Set-Cookie'); - $this->assertInternalType('array', $cookieHeader); + $this->assertIsArray($cookieHeader); $this->assertCount(1, $cookieHeader); $this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); } diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php index 5ebe7a9..9d0fc05 100644 --- a/Tests/Factory/DiactorosFactoryTest.php +++ b/Tests/Factory/DiactorosFactoryTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; /** * @author Kévin Dunglas @@ -21,7 +22,7 @@ */ class DiactorosFactoryTest extends AbstractHttpMessageFactoryTest { - protected function buildHttpMessageFactory() + protected function buildHttpMessageFactory(): HttpMessageFactoryInterface { if (!class_exists('Zend\Diactoros\ServerRequestFactory')) { $this->markTestSkipped('Zend Diactoros is not installed.'); diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index 27b84c0..a79b352 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -20,6 +20,8 @@ use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\UploadedFile; use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Uri; use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\UploadedFile as HttpFoundationUploadedFile; /** * @author Kévin Dunglas @@ -32,7 +34,7 @@ class HttpFoundationFactoryTest extends TestCase /** @var string */ private $tmpDir; - public function setup() + public function setUp(): void { $this->factory = new HttpFoundationFactory(); $this->tmpDir = sys_get_temp_dir(); @@ -80,7 +82,7 @@ public function testCreateRequest() $this->assertEquals('France', $symfonyRequest->server->get('country')); $this->assertEquals('The body', $symfonyRequest->getContent()); $this->assertEquals('1.0', $symfonyRequest->headers->get('X-Dunglas-API-Platform')); - $this->assertEquals(['a', 'b'], $symfonyRequest->headers->get('X-data', null, false)); + $this->assertEquals(['a', 'b'], $symfonyRequest->headers->all('X-data')); } public function testCreateRequestWithNullParsedBody() @@ -160,12 +162,11 @@ public function testCreateUploadedFile() $this->assertEquals('An uploaded file.', file_get_contents($this->tmpDir.'/'.$uniqid)); } - /** - * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException - * @expectedExceptionMessage The file "e" could not be written on disk. - */ 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); @@ -174,7 +175,7 @@ public function testCreateUploadedFileWithError() $symfonyUploadedFile->move($this->tmpDir, 'shouldFail.txt'); } - private function createUploadedFile($content, $error, $clientFileName, $clientMediaType) + private function createUploadedFile($content, $error, $clientFileName, $clientMediaType): UploadedFile { $filePath = tempnam($this->tmpDir, uniqid()); file_put_contents($filePath, $content); @@ -182,7 +183,7 @@ private function createUploadedFile($content, $error, $clientFileName, $clientMe return new UploadedFile($filePath, filesize($filePath), $error, $clientFileName, $clientMediaType); } - private function callCreateUploadedFile(UploadedFileInterface $uploadedFile) + private function callCreateUploadedFile(UploadedFileInterface $uploadedFile): HttpFoundationUploadedFile { $reflection = new \ReflectionClass($this->factory); $createUploadedFile = $reflection->getMethod('createUploadedFile'); diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php index da12ea7..b47cefc 100644 --- a/Tests/Factory/PsrHttpFactoryTest.php +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -13,6 +13,7 @@ use Nyholm\Psr7\Factory\Psr17Factory; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; +use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; /** * @author Kévin Dunglas @@ -20,7 +21,7 @@ */ class PsrHttpFactoryTest extends AbstractHttpMessageFactoryTest { - protected function buildHttpMessageFactory() + protected function buildHttpMessageFactory(): HttpMessageFactoryInterface { $factory = new Psr17Factory(); diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php index 33ec34f..66b92dc 100644 --- a/Tests/Functional/CovertTest.php +++ b/Tests/Functional/CovertTest.php @@ -36,7 +36,7 @@ class CovertTest extends TestCase { private $tmpDir; - public function setup() + public function setUp(): void { if (!class_exists('Nyholm\Psr7\ServerRequest')) { $this->markTestSkipped('nyholm/psr7 is not installed.'); diff --git a/composer.json b/composer.json index 51bb655..ef4c8f3 100644 --- a/composer.json +++ b/composer.json @@ -18,10 +18,10 @@ "require": { "php": "^7.1", "psr/http-message": "^1.0", - "symfony/http-foundation": "^3.4 || ^4.0" + "symfony/http-foundation": "^4.4 || ^5.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3.4.20 || ^4.0", + "symfony/phpunit-bridge": "^4.4 || ^5.0", "nyholm/psr7": "^1.1", "zendframework/zend-diactoros": "^1.4.1 || ^2.0" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9a6e477..43aeaa3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,7 +8,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" bootstrap="vendor/autoload.php" > From df26630d79114b3a34da29ab9ea388bd32b159ec Mon Sep 17 00:00:00 2001 From: Niklas Ekman Date: Tue, 24 Sep 2019 21:09:55 +0200 Subject: [PATCH 45/91] Add support for streamed Symfony request --- Factory/DiactorosFactory.php | 4 ---- Factory/HttpFoundationFactory.php | 12 +++--------- Factory/PsrHttpFactory.php | 4 ---- HttpFoundationFactoryInterface.php | 8 ++------ HttpMessageFactoryInterface.php | 4 ---- Tests/Factory/HttpFoundationFactoryTest.php | 21 +++++++++++++++++++++ Tests/Fixtures/Stream.php | 1 + 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php index d3e70b5..cc87dd8 100644 --- a/Factory/DiactorosFactory.php +++ b/Factory/DiactorosFactory.php @@ -84,8 +84,6 @@ public function createRequest(Request $symfonyRequest) /** * Converts Symfony uploaded files array to the PSR one. * - * @param array $uploadedFiles - * * @return array */ private function getFiles(array $uploadedFiles) @@ -110,8 +108,6 @@ private function getFiles(array $uploadedFiles) /** * Creates a PSR-7 UploadedFile instance from a Symfony one. * - * @param UploadedFile $symfonyUploadedFile - * * @return UploadedFileInterface */ private function createUploadedFile(UploadedFile $symfonyUploadedFile) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 4be0aff..d8bfe62 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -43,7 +43,7 @@ public function __construct(int $responseBufferMaxLength = 16372) /** * {@inheritdoc} */ - public function createRequest(ServerRequestInterface $psrRequest) + public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false) { $server = []; $uri = $psrRequest->getUri(); @@ -69,7 +69,7 @@ public function createRequest(ServerRequestInterface $psrRequest) $psrRequest->getCookieParams(), $this->getFiles($psrRequest->getUploadedFiles()), $server, - $psrRequest->getBody()->__toString() + $streamed ? $psrRequest->getBody()->detach() : $psrRequest->getBody()->__toString() ); $request->headers->replace($psrRequest->getHeaders()); @@ -79,8 +79,6 @@ public function createRequest(ServerRequestInterface $psrRequest) /** * Converts to the input array to $_FILES structure. * - * @param array $uploadedFiles - * * @return array */ private function getFiles(array $uploadedFiles) @@ -101,8 +99,6 @@ private function getFiles(array $uploadedFiles) /** * Creates Symfony UploadedFile instance from PSR-7 ones. * - * @param UploadedFileInterface $psrUploadedFile - * * @return UploadedFile */ private function createUploadedFile(UploadedFileInterface $psrUploadedFile) @@ -183,13 +179,11 @@ public function createResponse(ResponseInterface $psrResponse, bool $streamed = * * Some snippets have been taken from the Guzzle project: https://github.com/guzzle/guzzle/blob/5.3/src/Cookie/SetCookie.php#L34 * - * @param string $cookie - * * @return Cookie * * @throws \InvalidArgumentException */ - private function createCookie($cookie) + private function createCookie(string $cookie) { foreach (explode(';', $cookie) as $part) { $part = trim($part); diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index a37e1e9..dfcfe73 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -78,8 +78,6 @@ public function createRequest(Request $symfonyRequest) /** * Converts Symfony uploaded files array to the PSR one. * - * @param array $uploadedFiles - * * @return array */ private function getFiles(array $uploadedFiles) @@ -104,8 +102,6 @@ private function getFiles(array $uploadedFiles) /** * Creates a PSR-7 UploadedFile instance from a Symfony one. * - * @param UploadedFile $symfonyUploadedFile - * * @return UploadedFileInterface */ private function createUploadedFile(UploadedFile $symfonyUploadedFile) diff --git a/HttpFoundationFactoryInterface.php b/HttpFoundationFactoryInterface.php index 79ec40f..a3f9043 100644 --- a/HttpFoundationFactoryInterface.php +++ b/HttpFoundationFactoryInterface.php @@ -26,18 +26,14 @@ interface HttpFoundationFactoryInterface /** * Creates a Symfony Request instance from a PSR-7 one. * - * @param ServerRequestInterface $psrRequest - * * @return Request */ - public function createRequest(ServerRequestInterface $psrRequest); + public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false); /** * Creates a Symfony Response instance from a PSR-7 one. * - * @param ResponseInterface $psrResponse - * * @return Response */ - public function createResponse(ResponseInterface $psrResponse); + public function createResponse(ResponseInterface $psrResponse, bool $streamed = false); } diff --git a/HttpMessageFactoryInterface.php b/HttpMessageFactoryInterface.php index b7960e8..f7b964e 100644 --- a/HttpMessageFactoryInterface.php +++ b/HttpMessageFactoryInterface.php @@ -26,8 +26,6 @@ interface HttpMessageFactoryInterface /** * Creates a PSR-7 Request instance from a Symfony one. * - * @param Request $symfonyRequest - * * @return ServerRequestInterface */ public function createRequest(Request $symfonyRequest); @@ -35,8 +33,6 @@ public function createRequest(Request $symfonyRequest); /** * Creates a PSR-7 Response instance from a Symfony one. * - * @param Response $symfonyResponse - * * @return ResponseInterface */ public function createResponse(Response $symfonyResponse); diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index 27b84c0..0741ebe 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -83,6 +83,27 @@ public function testCreateRequest() $this->assertEquals(['a', 'b'], $symfonyRequest->headers->get('X-data', null, false)); } + public function testCreateRequestWithStreamedBody() + { + $serverRequest = new ServerRequest( + '1.1', + [], + new Stream('The body'), + '/', + 'GET', + null, + [], + [], + [], + [], + null, + [] + ); + + $symfonyRequest = $this->factory->createRequest($serverRequest, true); + $this->assertEquals('The body', $symfonyRequest->getContent()); + } + public function testCreateRequestWithNullParsedBody() { $serverRequest = new ServerRequest( diff --git a/Tests/Fixtures/Stream.php b/Tests/Fixtures/Stream.php index aa0ba24..06fff28 100644 --- a/Tests/Fixtures/Stream.php +++ b/Tests/Fixtures/Stream.php @@ -37,6 +37,7 @@ public function close() public function detach() { + return fopen('data://text/plain,'.$this->stringContent, 'r'); } public function getSize() From e9a9557fa6d1c1c20de3dfe48ee51f48f986b2d1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 25 Nov 2019 19:14:23 +0100 Subject: [PATCH 46/91] Cleanup after bump to Symfony v4.4 --- .gitignore | 1 + Factory/HttpFoundationFactory.php | 12 ------------ Tests/Factory/AbstractHttpMessageFactoryTest.php | 15 +++------------ Tests/Functional/CovertTest.php | 7 +------ 4 files changed, 5 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 027924f..082fd22 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor/ composer.lock phpunit.xml .php_cs.cache +.phpunit.result.cache diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index d8bfe62..5327f52 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -112,22 +112,10 @@ private function createUploadedFile(UploadedFileInterface $psrUploadedFile) $clientFileName = $psrUploadedFile->getClientFilename(); } - if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { - // Symfony 4.1+ - return new UploadedFile( - $temporaryPath, - null === $clientFileName ? '' : $clientFileName, - $psrUploadedFile->getClientMediaType(), - $psrUploadedFile->getError(), - true - ); - } - return new UploadedFile( $temporaryPath, null === $clientFileName ? '' : $clientFileName, $psrUploadedFile->getClientMediaType(), - $psrUploadedFile->getSize(), $psrUploadedFile->getError(), true ); diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php index acb7f9e..998edcc 100644 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -132,12 +132,7 @@ private function createUploadedFile($content, $originalName, $mimeType, $error) $path = tempnam($this->tmpDir, uniqid()); file_put_contents($path, $content); - if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { - // Symfony 4.1+ - return new UploadedFile($path, $originalName, $mimeType, $error, true); - } - - return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true); + return new UploadedFile($path, $originalName, $mimeType, $error, true); } public function testCreateResponse() @@ -189,12 +184,8 @@ public function testCreateResponseFromBinaryFile() public function testUploadErrNoFile() { - if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { - // Symfony 4.1+ - $file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true); - } else { - $file = new UploadedFile('', '', null, 0, UPLOAD_ERR_NO_FILE, true); - } + $file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true); + $this->assertEquals(0, $file->getSize()); $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError()); $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php index 66b92dc..137a154 100644 --- a/Tests/Functional/CovertTest.php +++ b/Tests/Functional/CovertTest.php @@ -229,11 +229,6 @@ private function createUploadedFile($content, $originalName, $mimeType, $error) $path = tempnam($this->tmpDir, uniqid()); file_put_contents($path, $content); - if (class_exists('Symfony\Component\HttpFoundation\HeaderUtils')) { - // Symfony 4.1+ - return new UploadedFile($path, $originalName, $mimeType, $error, true); - } - - return new UploadedFile($path, $originalName, $mimeType, filesize($path), $error, true); + return new UploadedFile($path, $originalName, $mimeType, $error, true); } } From 9ad4bccda4f8ad1cada6ce54588fd4a7282d369c Mon Sep 17 00:00:00 2001 From: Rein Baarsma Date: Sat, 23 Nov 2019 14:38:10 +0100 Subject: [PATCH 47/91] Updated CHANGELOG --- CHANGELOG.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70f3b82..5df9a55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,32 @@ CHANGELOG ========= +* 1.3.0 (2019-11-26) + + * Added support for streamed requests + * Added support for Symfony 5.0+ + * Fixed bridging UploadedFile objects + * Bumped minimum version of Symfony to 4.4 + +* 1.2.0 (2019-03-11) + + * Added new documentation links + * Bumped minimum version of PHP to 7.1 + * Added support for streamed responses + +* 1.1.2 (2019-04-03) + + * Fixed createResponse + +* 1.1.1 (2019-03-11) + + * Removed triggering of deprecation + * 1.1.0 (2018-08-30) - * Added support for creating PSR-7 messages using PSR-17 factories + * Deprecated DiactorosFactory, use PsrHttpFactory instead + * Added option to stream the response on the HttpFoundationFactory createResponse + * Added more tests and improved code style * 1.0.2 (2017-12-19) From ec7892ba4b66446322c2d2d961d80f26b715e6fa Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 25 Nov 2019 20:31:08 +0100 Subject: [PATCH 48/91] Fix CHANGELOG, bump branch-alias --- CHANGELOG.md | 5 ++--- composer.json | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5df9a55..538473c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,13 +20,12 @@ CHANGELOG * 1.1.1 (2019-03-11) + * Deprecated DiactorosFactory, use PsrHttpFactory instead * Removed triggering of deprecation * 1.1.0 (2018-08-30) - * Deprecated DiactorosFactory, use PsrHttpFactory instead - * Added option to stream the response on the HttpFoundationFactory createResponse - * Added more tests and improved code style + * Added support for creating PSR-7 messages using PSR-17 factories * 1.0.2 (2017-12-19) diff --git a/composer.json b/composer.json index ef4c8f3..07ed9cf 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } } } From a4f9f6d318a4dd02807fb9d49756e77b18d2a2d5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 25 Nov 2019 20:00:05 +0100 Subject: [PATCH 49/91] Use adapter for UploadedFile objects --- CHANGELOG.md | 2 +- Factory/HttpFoundationFactory.php | 30 ++----------- Factory/UploadedFile.php | 73 +++++++++++++++++++++++++++++++ Tests/Fixtures/UploadedFile.php | 2 +- 4 files changed, 79 insertions(+), 28 deletions(-) create mode 100644 Factory/UploadedFile.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 538473c..1559be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ CHANGELOG ========= -* 1.3.0 (2019-11-26) +* 1.3.0 (2019-11-25) * Added support for streamed requests * Added support for Symfony 5.0+ diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 5327f52..d9bbaf2 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -18,7 +18,6 @@ use Psr\Http\Message\UriInterface; use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; use Symfony\Component\HttpFoundation\Cookie; -use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -78,10 +77,8 @@ public function createRequest(ServerRequestInterface $psrRequest, bool $streamed /** * Converts to the input array to $_FILES structure. - * - * @return array */ - private function getFiles(array $uploadedFiles) + private function getFiles(array $uploadedFiles): array { $files = []; @@ -98,27 +95,10 @@ private function getFiles(array $uploadedFiles) /** * Creates Symfony UploadedFile instance from PSR-7 ones. - * - * @return UploadedFile */ - private function createUploadedFile(UploadedFileInterface $psrUploadedFile) + private function createUploadedFile(UploadedFileInterface $psrUploadedFile): UploadedFile { - $temporaryPath = ''; - $clientFileName = ''; - if (UPLOAD_ERR_NO_FILE !== $psrUploadedFile->getError()) { - $temporaryPath = $this->getTemporaryPath(); - $psrUploadedFile->moveTo($temporaryPath); - - $clientFileName = $psrUploadedFile->getClientFilename(); - } - - return new UploadedFile( - $temporaryPath, - null === $clientFileName ? '' : $clientFileName, - $psrUploadedFile->getClientMediaType(), - $psrUploadedFile->getError(), - true - ); + return new UploadedFile($psrUploadedFile, function () { return $this->getTemporaryPath(); }); } /** @@ -167,11 +147,9 @@ public function createResponse(ResponseInterface $psrResponse, bool $streamed = * * Some snippets have been taken from the Guzzle project: https://github.com/guzzle/guzzle/blob/5.3/src/Cookie/SetCookie.php#L34 * - * @return Cookie - * * @throws \InvalidArgumentException */ - private function createCookie(string $cookie) + private function createCookie(string $cookie): Cookie { foreach (explode(';', $cookie) as $part) { $part = trim($part); diff --git a/Factory/UploadedFile.php b/Factory/UploadedFile.php new file mode 100644 index 0000000..804f747 --- /dev/null +++ b/Factory/UploadedFile.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PsrHttpMessage\Factory; + +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\UploadedFile as BaseUploadedFile; + +/** + * @author Nicolas Grekas + */ +class UploadedFile extends BaseUploadedFile +{ + private $psrUploadedFile; + private $test = false; + + public function __construct(UploadedFileInterface $psrUploadedFile, callable $getTemporaryPath) + { + $error = $psrUploadedFile->getError(); + $path = ''; + + if (UPLOAD_ERR_NO_FILE !== $error) { + $path = $psrUploadedFile->getStream()->getMetadata('uri') ?? ''; + + if ($this->test = !\is_string($path) || !is_uploaded_file($path)) { + $path = $getTemporaryPath(); + $psrUploadedFile->moveTo($path); + } + } + + parent::__construct( + $path, + (string) $psrUploadedFile->getClientFilename(), + $psrUploadedFile->getClientMediaType(), + $psrUploadedFile->getError(), + $this->test + ); + + $this->psrUploadedFile = $psrUploadedFile; + } + + /** + * {@inheritdoc} + */ + public function move($directory, $name = null): File + { + if (!$this->isValid() || $this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + try { + $this->psrUploadedFile->moveTo($target); + } catch (\RuntimeException $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()); + + return $target; + } +} diff --git a/Tests/Fixtures/UploadedFile.php b/Tests/Fixtures/UploadedFile.php index 4cfa98b..f58a4bd 100644 --- a/Tests/Fixtures/UploadedFile.php +++ b/Tests/Fixtures/UploadedFile.php @@ -35,7 +35,7 @@ public function __construct($filePath, $size = null, $error = UPLOAD_ERR_OK, $cl public function getStream() { - throw new \RuntimeException('No stream is available.'); + return new Stream(file_get_contents($this->filePath)); } public function moveTo($targetPath) From dfc523855a995ba3e32c9210c22f88e05fa26e61 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 2 Jan 2020 08:47:53 +0100 Subject: [PATCH 50/91] Remove deprecated code --- CHANGELOG.md | 4 + Factory/DiactorosFactory.php | 171 ------------------------- Tests/Factory/DiactorosFactoryTest.php | 33 ----- composer.json | 5 +- 4 files changed, 6 insertions(+), 207 deletions(-) delete mode 100644 Factory/DiactorosFactory.php delete mode 100644 Tests/Factory/DiactorosFactoryTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1559be2..4883fc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG ========= +* 2.0.0 (2020-01-02) + + * Remove DiactorosFactory + * 1.3.0 (2019-11-25) * Added support for streamed requests diff --git a/Factory/DiactorosFactory.php b/Factory/DiactorosFactory.php deleted file mode 100644 index cc87dd8..0000000 --- a/Factory/DiactorosFactory.php +++ /dev/null @@ -1,171 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PsrHttpMessage\Factory; - -@trigger_error(sprintf('The "%s" class is deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead.', DiactorosFactory::class), E_USER_DEPRECATED); - -use Psr\Http\Message\UploadedFileInterface; -use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; -use Symfony\Component\HttpFoundation\BinaryFileResponse; -use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\StreamedResponse; -use Zend\Diactoros\Response as DiactorosResponse; -use Zend\Diactoros\ServerRequest; -use Zend\Diactoros\ServerRequestFactory as DiactorosRequestFactory; -use Zend\Diactoros\Stream as DiactorosStream; -use Zend\Diactoros\UploadedFile as DiactorosUploadedFile; - -/** - * Builds Psr\HttpMessage instances using the Zend Diactoros implementation. - * - * @author Kévin Dunglas - * - * @deprecated since symfony/psr-http-message-bridge 1.2, use PsrHttpFactory instead - */ -class DiactorosFactory implements HttpMessageFactoryInterface -{ - public function __construct() - { - if (!class_exists('Zend\Diactoros\ServerRequestFactory')) { - throw new \RuntimeException('Zend Diactoros must be installed to use the DiactorosFactory.'); - } - } - - /** - * {@inheritdoc} - */ - public function createRequest(Request $symfonyRequest) - { - $server = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeServer') - ? DiactorosRequestFactory::normalizeServer($symfonyRequest->server->all()) - : \Zend\Diactoros\normalizeServer($symfonyRequest->server->all()); - $headers = $symfonyRequest->headers->all(); - - $body = new DiactorosStream($symfonyRequest->getContent(true)); - - $files = method_exists('Zend\Diactoros\ServerRequestFactory', 'normalizeFiles') - ? DiactorosRequestFactory::normalizeFiles($this->getFiles($symfonyRequest->files->all())) - : \Zend\Diactoros\normalizeUploadedFiles($this->getFiles($symfonyRequest->files->all())); - - $request = new ServerRequest( - $server, - $files, - $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getRequestUri(), - $symfonyRequest->getMethod(), - $body, - $headers - ); - - $request = $request - ->withCookieParams($symfonyRequest->cookies->all()) - ->withQueryParams($symfonyRequest->query->all()) - ->withParsedBody($symfonyRequest->request->all()) - ->withRequestTarget($symfonyRequest->getRequestUri()) - ; - - foreach ($symfonyRequest->attributes->all() as $key => $value) { - $request = $request->withAttribute($key, $value); - } - - return $request; - } - - /** - * Converts Symfony uploaded files array to the PSR one. - * - * @return array - */ - private function getFiles(array $uploadedFiles) - { - $files = []; - - foreach ($uploadedFiles as $key => $value) { - if (null === $value) { - $files[$key] = new DiactorosUploadedFile(null, 0, UPLOAD_ERR_NO_FILE, null, null); - continue; - } - if ($value instanceof UploadedFile) { - $files[$key] = $this->createUploadedFile($value); - } else { - $files[$key] = $this->getFiles($value); - } - } - - return $files; - } - - /** - * Creates a PSR-7 UploadedFile instance from a Symfony one. - * - * @return UploadedFileInterface - */ - private function createUploadedFile(UploadedFile $symfonyUploadedFile) - { - return new DiactorosUploadedFile( - $symfonyUploadedFile->getRealPath(), - (int) $symfonyUploadedFile->getSize(), - $symfonyUploadedFile->getError(), - $symfonyUploadedFile->getClientOriginalName(), - $symfonyUploadedFile->getClientMimeType() - ); - } - - /** - * {@inheritdoc} - */ - public function createResponse(Response $symfonyResponse) - { - if ($symfonyResponse instanceof BinaryFileResponse) { - $stream = new DiactorosStream($symfonyResponse->getFile()->getPathname(), 'r'); - } else { - $stream = new DiactorosStream('php://temp', 'wb+'); - if ($symfonyResponse instanceof StreamedResponse) { - ob_start(function ($buffer) use ($stream) { - $stream->write($buffer); - - return ''; - }); - - $symfonyResponse->sendContent(); - ob_end_clean(); - } else { - $stream->write($symfonyResponse->getContent()); - } - } - - $headers = $symfonyResponse->headers->all(); - if (!isset($headers['Set-Cookie']) && !isset($headers['set-cookie'])) { - $cookies = $symfonyResponse->headers->getCookies(); - if (!empty($cookies)) { - $headers['Set-Cookie'] = []; - foreach ($cookies as $cookie) { - $headers['Set-Cookie'][] = $cookie->__toString(); - } - } - } - - $response = new DiactorosResponse( - $stream, - $symfonyResponse->getStatusCode(), - $headers - ); - - $protocolVersion = $symfonyResponse->getProtocolVersion(); - if ('1.1' !== $protocolVersion) { - $response = $response->withProtocolVersion($protocolVersion); - } - - return $response; - } -} diff --git a/Tests/Factory/DiactorosFactoryTest.php b/Tests/Factory/DiactorosFactoryTest.php deleted file mode 100644 index 9d0fc05..0000000 --- a/Tests/Factory/DiactorosFactoryTest.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; - -use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; -use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; - -/** - * @author Kévin Dunglas - * @author Antonio J. García Lagar - * - * @group legacy - */ -class DiactorosFactoryTest extends AbstractHttpMessageFactoryTest -{ - protected function buildHttpMessageFactory(): HttpMessageFactoryInterface - { - if (!class_exists('Zend\Diactoros\ServerRequestFactory')) { - $this->markTestSkipped('Zend Diactoros is not installed.'); - } - - return new DiactorosFactory(); - } -} diff --git a/composer.json b/composer.json index 07ed9cf..dc04d08 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,7 @@ }, "require-dev": { "symfony/phpunit-bridge": "^4.4 || ^5.0", - "nyholm/psr7": "^1.1", - "zendframework/zend-diactoros": "^1.4.1 || ^2.0" + "nyholm/psr7": "^1.1" }, "suggest": { "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" @@ -36,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "2.0-dev" } } } From 126903c2b23c62be23781c178a16d17bb7cc49db Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 13 Feb 2020 17:58:21 +0100 Subject: [PATCH 51/91] Fix format of CHANGELOG.md --- CHANGELOG.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4883fc1..77d5ed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,44 +1,44 @@ CHANGELOG ========= -* 2.0.0 (2020-01-02) +# 2.0.0 (2020-01-02) * Remove DiactorosFactory -* 1.3.0 (2019-11-25) +# 1.3.0 (2019-11-25) * Added support for streamed requests * Added support for Symfony 5.0+ * Fixed bridging UploadedFile objects * Bumped minimum version of Symfony to 4.4 -* 1.2.0 (2019-03-11) +# 1.2.0 (2019-03-11) * Added new documentation links * Bumped minimum version of PHP to 7.1 * Added support for streamed responses -* 1.1.2 (2019-04-03) +# 1.1.2 (2019-04-03) * Fixed createResponse -* 1.1.1 (2019-03-11) +# 1.1.1 (2019-03-11) * Deprecated DiactorosFactory, use PsrHttpFactory instead * Removed triggering of deprecation -* 1.1.0 (2018-08-30) +# 1.1.0 (2018-08-30) * Added support for creating PSR-7 messages using PSR-17 factories -* 1.0.2 (2017-12-19) +# 1.0.2 (2017-12-19) * Fixed request target in PSR7 Request (mtibben) -* 1.0.1 (2017-12-04) +# 1.0.1 (2017-12-04) * Added support for Symfony 4 (dunglas) -* 1.0.0 (2016-09-14) +# 1.0.0 (2016-09-14) * Initial release From d336c735f5d06063fd287fe50824f8859607fb92 Mon Sep 17 00:00:00 2001 From: Arthur de Moulins Date: Thu, 2 Apr 2020 10:41:57 +0200 Subject: [PATCH 52/91] fix conversion for https requests --- Factory/HttpFoundationFactory.php | 4 ++++ Tests/Functional/CovertTest.php | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index d9bbaf2..68ba7dd 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -52,6 +52,10 @@ public function createRequest(ServerRequestInterface $psrRequest, bool $streamed $server['SERVER_PORT'] = $uri->getPort(); $server['REQUEST_URI'] = $uri->getPath(); $server['QUERY_STRING'] = $uri->getQuery(); + + if ('https' === $uri->getScheme()) { + $server['HTTPS'] = 'on'; + } } $server['REQUEST_METHOD'] = $psrRequest->getMethod(); diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php index 137a154..11f86c8 100644 --- a/Tests/Functional/CovertTest.php +++ b/Tests/Functional/CovertTest.php @@ -139,17 +139,24 @@ public function requestProvider() 'Content' ); - $psr7Request = (new Psr7Request('POST', 'http://tnyholm.se/foo/?bar=biz')) - ->withQueryParams(['bar' => 'biz']); + $psr7Requests = [ + (new Psr7Request('POST', 'http://tnyholm.se/foo/?bar=biz')) + ->withQueryParams(['bar' => 'biz']), + new Psr7Request('GET', 'https://hey-octave.com/'), + new Psr7Request('GET', 'https://hey-octave.com:443/'), + new Psr7Request('GET', 'https://hey-octave.com:4242/'), + new Psr7Request('GET', 'http://hey-octave.com:80/'), + ]; $nyholmFactory = new Psr17Factory(); $psr17Factory = new PsrHttpFactory($nyholmFactory, $nyholmFactory, $nyholmFactory, $nyholmFactory); $symfonyFactory = new HttpFoundationFactory(); - return [ + return array_merge([ [$sfRequest, $psr17Factory, $symfonyFactory], - [$psr7Request, $symfonyFactory, $psr17Factory], - ]; + ], array_map(function ($psr7Request) use ($symfonyFactory, $psr17Factory) { + return [$psr7Request, $symfonyFactory, $psr17Factory]; + }, $psr7Requests)); } /** From 9243f9307ebe993fb2ed2d9ab89cb607cd656332 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 31 May 2020 10:52:42 +0200 Subject: [PATCH 53/91] Allow installation on php 8. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index dc04d08..eaf2b94 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": "^7.1", + "php": ">=7.1", "psr/http-message": "^1.0", "symfony/http-foundation": "^4.4 || ^5.0" }, From 4f304013f95f022fb3af139fee07064cf82168ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Mleczko?= Date: Mon, 20 May 2019 12:17:57 +0200 Subject: [PATCH 54/91] Fix populating default port and headers in HttpFoundationFactory --- Factory/HttpFoundationFactory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 68ba7dd..105dd99 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -49,7 +49,7 @@ public function createRequest(ServerRequestInterface $psrRequest, bool $streamed if ($uri instanceof UriInterface) { $server['SERVER_NAME'] = $uri->getHost(); - $server['SERVER_PORT'] = $uri->getPort(); + $server['SERVER_PORT'] = $uri->getPort() ?: ('https' === $uri->getScheme() ? 443 : 80); $server['REQUEST_URI'] = $uri->getPath(); $server['QUERY_STRING'] = $uri->getQuery(); @@ -74,7 +74,7 @@ public function createRequest(ServerRequestInterface $psrRequest, bool $streamed $server, $streamed ? $psrRequest->getBody()->detach() : $psrRequest->getBody()->__toString() ); - $request->headers->replace($psrRequest->getHeaders()); + $request->headers->add($psrRequest->getHeaders()); return $request; } From bc258291257b2ec37bd0abb5e16f83debd967f43 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 25 Jun 2020 09:40:06 +0200 Subject: [PATCH 55/91] Don't normalize query string in PsrHttpFactory --- CHANGELOG.md | 6 ++++++ Factory/PsrHttpFactory.php | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d5ed8..53663dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +# 2.0.1 (2020-06-25) + + * Don't normalize query string in PsrHttpFactory + * Fix conversion for HTTPS requests + * Fix populating default port and headers in HttpFoundationFactory + # 2.0.0 (2020-01-02) * Remove DiactorosFactory diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index dfcfe73..80ec527 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -48,9 +48,12 @@ public function __construct(ServerRequestFactoryInterface $serverRequestFactory, */ public function createRequest(Request $symfonyRequest) { + $uri = $symfonyRequest->server->get('QUERY_STRING', ''); + $uri = $symfonyRequest->getSchemeAndHttpHost().$symfonyRequest->getBaseUrl().$symfonyRequest->getPathInfo().('' !== $uri ? '?'.$uri : ''); + $request = $this->serverRequestFactory->createServerRequest( $symfonyRequest->getMethod(), - $symfonyRequest->getUri(), + $uri, $symfonyRequest->server->all() ); From 5d5932d1f1573c9f3e1493d5b0f9ee758d1cf603 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Fri, 25 Sep 2020 13:34:25 +0200 Subject: [PATCH 56/91] Fix BinaryFileResponse with range to psr response conversion Closes #84 --- Factory/PsrHttpFactory.php | 4 ++-- .../Factory/AbstractHttpMessageFactoryTest.php | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index 80ec527..fcb80d6 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -127,13 +127,13 @@ public function createResponse(Response $symfonyResponse) { $response = $this->responseFactory->createResponse($symfonyResponse->getStatusCode(), Response::$statusTexts[$symfonyResponse->getStatusCode()] ?? ''); - if ($symfonyResponse instanceof BinaryFileResponse) { + if ($symfonyResponse instanceof BinaryFileResponse && !$symfonyResponse->headers->has('Content-Range')) { $stream = $this->streamFactory->createStreamFromFile( $symfonyResponse->getFile()->getPathname() ); } else { $stream = $this->streamFactory->createStreamFromFile('php://temp', 'wb+'); - if ($symfonyResponse instanceof StreamedResponse) { + if ($symfonyResponse instanceof StreamedResponse || $symfonyResponse instanceof BinaryFileResponse) { ob_start(function ($buffer) use ($stream) { $stream->write($buffer); diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php index 998edcc..d43f7fb 100644 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -182,6 +182,23 @@ public function testCreateResponseFromBinaryFile() $this->assertEquals('Binary', $psrResponse->getBody()->__toString()); } + public function testCreateResponseFromBinaryFileWithRange() + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, 'Binary'); + + $request = new Request(); + $request->headers->set('Range', 'bytes=1-4'); + + $response = new BinaryFileResponse($path, 200, ['Content-Type' => 'plain/text']); + $response->prepare($request); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertEquals('inar', $psrResponse->getBody()->__toString()); + $this->assertSame('bytes 1-4/6', $psrResponse->getHeaderLine('Content-Range')); + } + public function testUploadErrNoFile() { $file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true); From 42cca49571b6454064e60108182a2069fcd07132 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 29 Sep 2020 10:00:56 +0200 Subject: [PATCH 57/91] Create cookies as raw in HttpFoundationFactory --- Factory/HttpFoundationFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 105dd99..6e8f581 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -218,7 +218,7 @@ private function createCookie(string $cookie): Cookie isset($cookieDomain) ? $cookieDomain : null, isset($cookieSecure), isset($cookieHttpOnly), - false, + true, isset($samesite) ? $samesite : null ); } From 66095a5cfec5076e9f46c5dc8ae335c5a1a22d1e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 29 Sep 2020 10:12:41 +0200 Subject: [PATCH 58/91] Fix populating server params from URI in HttpFoundationFactory --- Factory/HttpFoundationFactory.php | 6 +++++- Tests/Functional/CovertTest.php | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 105dd99..6427a99 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -53,6 +53,10 @@ public function createRequest(ServerRequestInterface $psrRequest, bool $streamed $server['REQUEST_URI'] = $uri->getPath(); $server['QUERY_STRING'] = $uri->getQuery(); + if ('' !== $server['QUERY_STRING']) { + $server['REQUEST_URI'] .= '?'.$server['QUERY_STRING']; + } + if ('https' === $uri->getScheme()) { $server['HTTPS'] = 'on'; } @@ -60,7 +64,7 @@ public function createRequest(ServerRequestInterface $psrRequest, bool $streamed $server['REQUEST_METHOD'] = $psrRequest->getMethod(); - $server = array_replace($server, $psrRequest->getServerParams()); + $server = array_replace($psrRequest->getServerParams(), $server); $parsedBody = $psrRequest->getParsedBody(); $parsedBody = \is_array($parsedBody) ? $parsedBody : []; diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php index 11f86c8..4fc6890 100644 --- a/Tests/Functional/CovertTest.php +++ b/Tests/Functional/CovertTest.php @@ -133,8 +133,8 @@ public function requestProvider() 'SERVER_NAME' => 'dunglas.fr', 'SERVER_PORT' => null, 'HTTP_X_SYMFONY' => '2.8', - 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1', - 'QUERY_STRING' => 'foo=1&bar[baz]=42', + 'REQUEST_URI' => '/testCreateRequest?foo=1&bar%5Bbaz%5D=42', + 'QUERY_STRING' => 'foo=1&bar%5Bbaz%5D=42', ], 'Content' ); From 51a21cb3ba3927d4b4bf8f25cc55763351af5f2e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 29 Sep 2020 10:17:46 +0200 Subject: [PATCH 59/91] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53663dd..47f3e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +# 2.0.2 (2020-09-29) + + * Fix populating server params from URI in HttpFoundationFactory + * Create cookies as raw in HttpFoundationFactory + * Fix BinaryFileResponse with Content-Range PsrHttpFactory + # 2.0.1 (2020-06-25) * Don't normalize query string in PsrHttpFactory From c62f7d0fd6cde97c1ce86f20ed30998325a6a62d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 19 Nov 2020 13:34:11 +0100 Subject: [PATCH 60/91] Update branch-alias --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index eaf2b94..bf201cd 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "2.0-dev" } } } From a6697fddd719348b111c7572c1392d163c4ed1e9 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 16 Feb 2021 15:25:39 +0100 Subject: [PATCH 61/91] Fix CI failures with Xdebug 3 and test on PHP 7.4/8.0 as well --- .travis.yml | 11 ++++++++--- Tests/Factory/AbstractHttpMessageFactoryTest.php | 2 +- composer.json | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index d171c00..5409284 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,22 +8,27 @@ cache: env: global: - PHPUNIT_FLAGS="-v" + - SYMFONY_PHPUNIT_VERSION=9.5 - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" matrix: fast_finish: true include: # Minimum supported dependencies with the latest and oldest PHP version - - php: 7.3 + - php: 7.4 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" - php: 7.1 + env: SYMFONY_PHPUNIT_VERSION=7.5 - php: 7.2 + env: SYMFONY_PHPUNIT_VERSION=8.5 - php: 7.3 - env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" + - php: 7.4 + env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" XDEBUG_MODE=coverage + - php: 8.0 # Latest commit to master - - php: 7.3 + - php: 7.4 env: STABILITY="dev" allow_failures: diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php index d43f7fb..2176ae8 100644 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -152,7 +152,7 @@ public function testCreateResponse() $cookieHeader = $psrResponse->getHeader('Set-Cookie'); $this->assertIsArray($cookieHeader); $this->assertCount(1, $cookieHeader); - $this->assertRegExp('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); + $this->assertMatchesRegularExpression('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); } public function testCreateResponseFromStreamed() diff --git a/composer.json b/composer.json index bf201cd..6fa97f0 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "symfony/http-foundation": "^4.4 || ^5.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.0", + "symfony/phpunit-bridge": "^4.4.19 || ^5.2", "nyholm/psr7": "^1.1" }, "suggest": { From 2bead2266bbff6b138f2c8967e609d0c4e5307b8 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 16 Feb 2021 16:09:14 +0100 Subject: [PATCH 62/91] Fix CS --- Factory/PsrHttpFactory.php | 2 +- Factory/UploadedFile.php | 2 +- .../AbstractHttpMessageFactoryTest.php | 20 +++++++++---------- Tests/Factory/HttpFoundationFactoryTest.php | 16 +++++++-------- Tests/Fixtures/Stream.php | 2 +- Tests/Fixtures/UploadedFile.php | 2 +- Tests/Functional/CovertTest.php | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index fcb80d6..d19baa1 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -89,7 +89,7 @@ private function getFiles(array $uploadedFiles) foreach ($uploadedFiles as $key => $value) { if (null === $value) { - $files[$key] = $this->uploadedFileFactory->createUploadedFile($this->streamFactory->createStream(), 0, UPLOAD_ERR_NO_FILE); + $files[$key] = $this->uploadedFileFactory->createUploadedFile($this->streamFactory->createStream(), 0, \UPLOAD_ERR_NO_FILE); continue; } if ($value instanceof UploadedFile) { diff --git a/Factory/UploadedFile.php b/Factory/UploadedFile.php index 804f747..53aa37a 100644 --- a/Factory/UploadedFile.php +++ b/Factory/UploadedFile.php @@ -29,7 +29,7 @@ public function __construct(UploadedFileInterface $psrUploadedFile, callable $ge $error = $psrUploadedFile->getError(); $path = ''; - if (UPLOAD_ERR_NO_FILE !== $error) { + if (\UPLOAD_ERR_NO_FILE !== $error) { $path = $psrUploadedFile->getStream()->getMetadata('uri') ?? ''; if ($this->test = !\is_string($path) || !is_uploaded_file($path)) { diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php index 2176ae8..82d3fc7 100644 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ b/Tests/Factory/AbstractHttpMessageFactoryTest.php @@ -31,7 +31,7 @@ abstract class AbstractHttpMessageFactoryTest extends TestCase abstract protected function buildHttpMessageFactory(): HttpMessageFactoryInterface; - public function setUp(): void + protected function setUp(): void { $this->factory = $this->buildHttpMessageFactory(); $this->tmpDir = sys_get_temp_dir(); @@ -61,8 +61,8 @@ public function testCreateRequest() 'c2' => ['c3' => 'bar'], ], [ - 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK), - 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)], + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', \UPLOAD_ERR_OK), + 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', \UPLOAD_ERR_OK)], ], [ 'REQUEST_METHOD' => 'POST', @@ -102,12 +102,12 @@ public function testCreateRequest() $this->assertEquals('F1', $uploadedFiles['f1']->getStream()->__toString()); $this->assertEquals('f1.txt', $uploadedFiles['f1']->getClientFilename()); $this->assertEquals('text/plain', $uploadedFiles['f1']->getClientMediaType()); - $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['f1']->getError()); + $this->assertEquals(\UPLOAD_ERR_OK, $uploadedFiles['f1']->getError()); $this->assertEquals('F2', $uploadedFiles['foo']['f2']->getStream()->__toString()); $this->assertEquals('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename()); $this->assertEquals('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType()); - $this->assertEquals(UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError()); + $this->assertEquals(\UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError()); $serverParams = $psrRequest->getServerParams(); $this->assertEquals('POST', $serverParams['REQUEST_METHOD']); @@ -201,10 +201,10 @@ public function testCreateResponseFromBinaryFileWithRange() public function testUploadErrNoFile() { - $file = new UploadedFile('', '', null, UPLOAD_ERR_NO_FILE, true); + $file = new UploadedFile('', '', null, \UPLOAD_ERR_NO_FILE, true); $this->assertEquals(0, $file->getSize()); - $this->assertEquals(UPLOAD_ERR_NO_FILE, $file->getError()); + $this->assertEquals(\UPLOAD_ERR_NO_FILE, $file->getError()); $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); $request = new Request( @@ -214,7 +214,7 @@ public function testUploadErrNoFile() [], [ 'f1' => $file, - 'f2' => ['name' => null, 'type' => null, 'tmp_name' => null, 'error' => UPLOAD_ERR_NO_FILE, 'size' => 0], + 'f2' => ['name' => null, 'type' => null, 'tmp_name' => null, 'error' => \UPLOAD_ERR_NO_FILE, 'size' => 0], ], [ 'REQUEST_METHOD' => 'POST', @@ -228,7 +228,7 @@ public function testUploadErrNoFile() $uploadedFiles = $psrRequest->getUploadedFiles(); - $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); - $this->assertEquals(UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); + $this->assertEquals(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); + $this->assertEquals(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); } } diff --git a/Tests/Factory/HttpFoundationFactoryTest.php b/Tests/Factory/HttpFoundationFactoryTest.php index e35a789..3a00e2f 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -34,7 +34,7 @@ class HttpFoundationFactoryTest extends TestCase /** @var string */ private $tmpDir; - public function setUp(): void + protected function setUp(): void { $this->factory = new HttpFoundationFactory(); $this->tmpDir = sys_get_temp_dir(); @@ -57,11 +57,11 @@ public function testCreateRequest() ['city' => 'Lille'], ['url' => 'http://les-tilleuls.coop'], [ - 'doc1' => $this->createUploadedFile('Doc 1', UPLOAD_ERR_OK, 'doc1.txt', 'text/plain'), + 'doc1' => $this->createUploadedFile('Doc 1', \UPLOAD_ERR_OK, 'doc1.txt', 'text/plain'), 'nested' => [ 'docs' => [ - $this->createUploadedFile('Doc 2', UPLOAD_ERR_OK, 'doc2.txt', 'text/plain'), - $this->createUploadedFile('Doc 3', UPLOAD_ERR_OK, 'doc3.txt', 'text/plain'), + $this->createUploadedFile('Doc 2', \UPLOAD_ERR_OK, 'doc2.txt', 'text/plain'), + $this->createUploadedFile('Doc 3', \UPLOAD_ERR_OK, 'doc3.txt', 'text/plain'), ], ], ], @@ -168,7 +168,7 @@ public function testCreateRequestWithUri() public function testCreateUploadedFile() { - $uploadedFile = $this->createUploadedFile('An uploaded file.', UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); + $uploadedFile = $this->createUploadedFile('An uploaded file.', \UPLOAD_ERR_OK, 'myfile.txt', 'text/plain'); $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); $size = $symfonyUploadedFile->getSize(); @@ -176,7 +176,7 @@ public function testCreateUploadedFile() $symfonyUploadedFile->move($this->tmpDir, $uniqid); $this->assertEquals($uploadedFile->getSize(), $size); - $this->assertEquals(UPLOAD_ERR_OK, $symfonyUploadedFile->getError()); + $this->assertEquals(\UPLOAD_ERR_OK, $symfonyUploadedFile->getError()); $this->assertEquals('myfile.txt', $symfonyUploadedFile->getClientOriginalName()); $this->assertEquals('txt', $symfonyUploadedFile->getClientOriginalExtension()); $this->assertEquals('text/plain', $symfonyUploadedFile->getClientMimeType()); @@ -188,10 +188,10 @@ 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'); + $uploadedFile = $this->createUploadedFile('Error.', \UPLOAD_ERR_CANT_WRITE, 'e', 'text/plain'); $symfonyUploadedFile = $this->callCreateUploadedFile($uploadedFile); - $this->assertEquals(UPLOAD_ERR_CANT_WRITE, $symfonyUploadedFile->getError()); + $this->assertEquals(\UPLOAD_ERR_CANT_WRITE, $symfonyUploadedFile->getError()); $symfonyUploadedFile->move($this->tmpDir, 'shouldFail.txt'); } diff --git a/Tests/Fixtures/Stream.php b/Tests/Fixtures/Stream.php index 06fff28..2cb4ab2 100644 --- a/Tests/Fixtures/Stream.php +++ b/Tests/Fixtures/Stream.php @@ -59,7 +59,7 @@ public function isSeekable() return true; } - public function seek($offset, $whence = SEEK_SET) + public function seek($offset, $whence = \SEEK_SET) { } diff --git a/Tests/Fixtures/UploadedFile.php b/Tests/Fixtures/UploadedFile.php index f58a4bd..93b3214 100644 --- a/Tests/Fixtures/UploadedFile.php +++ b/Tests/Fixtures/UploadedFile.php @@ -24,7 +24,7 @@ class UploadedFile implements UploadedFileInterface private $clientFileName; private $clientMediaType; - public function __construct($filePath, $size = null, $error = UPLOAD_ERR_OK, $clientFileName = null, $clientMediaType = null) + public function __construct($filePath, $size = null, $error = \UPLOAD_ERR_OK, $clientFileName = null, $clientMediaType = null) { $this->filePath = $filePath; $this->size = $size; diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php index 4fc6890..f460b0e 100644 --- a/Tests/Functional/CovertTest.php +++ b/Tests/Functional/CovertTest.php @@ -36,9 +36,9 @@ class CovertTest extends TestCase { private $tmpDir; - public function setUp(): void + protected function setUp(): void { - if (!class_exists('Nyholm\Psr7\ServerRequest')) { + if (!class_exists(Psr7Request::class)) { $this->markTestSkipped('nyholm/psr7 is not installed.'); } @@ -124,8 +124,8 @@ public function requestProvider() 'c2' => ['c3' => 'bar'], ], [ - 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', UPLOAD_ERR_OK), - 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', UPLOAD_ERR_OK)], + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', \UPLOAD_ERR_OK), + 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', \UPLOAD_ERR_OK)], ], [ 'REQUEST_METHOD' => 'POST', From aa26e610d8f53976fd15f7c38fd88c0ac4933b82 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 16 Feb 2021 14:07:31 +0100 Subject: [PATCH 63/91] PSR HTTP message converters for controllers --- .gitignore | 1 + .../PsrServerRequestResolver.php | 49 ++++++++++++ CHANGELOG.md | 5 ++ EventListener/PsrResponseListener.php | 50 ++++++++++++ .../PsrServerRequestResolverTest.php | 59 ++++++++++++++ .../EventListener/PsrResponseListenerTest.php | 44 +++++++++++ .../App/Controller/PsrRequestController.php | 45 +++++++++++ Tests/Fixtures/App/Kernel.php | 76 +++++++++++++++++++ Tests/Fixtures/App/Kernel44.php | 67 ++++++++++++++++ Tests/Functional/ControllerTest.php | 46 +++++++++++ composer.json | 10 ++- 11 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 ArgumentValueResolver/PsrServerRequestResolver.php create mode 100644 EventListener/PsrResponseListener.php create mode 100644 Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php create mode 100644 Tests/EventListener/PsrResponseListenerTest.php create mode 100644 Tests/Fixtures/App/Controller/PsrRequestController.php create mode 100644 Tests/Fixtures/App/Kernel.php create mode 100644 Tests/Fixtures/App/Kernel44.php create mode 100644 Tests/Functional/ControllerTest.php diff --git a/.gitignore b/.gitignore index 082fd22..55ce5dd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ composer.lock phpunit.xml .php_cs.cache .phpunit.result.cache +/Tests/Fixtures/App/var diff --git a/ArgumentValueResolver/PsrServerRequestResolver.php b/ArgumentValueResolver/PsrServerRequestResolver.php new file mode 100644 index 0000000..094f0c8 --- /dev/null +++ b/ArgumentValueResolver/PsrServerRequestResolver.php @@ -0,0 +1,49 @@ + + * @author Alexander M. Turek + */ +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); + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 47f3e55..d70e5a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +# 2.1.0 (2021-02-17) + + * 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.0.2 (2020-09-29) * Fix populating server params from URI in HttpFoundationFactory diff --git a/EventListener/PsrResponseListener.php b/EventListener/PsrResponseListener.php new file mode 100644 index 0000000..ee0e047 --- /dev/null +++ b/EventListener/PsrResponseListener.php @@ -0,0 +1,50 @@ + + * @author Alexander M. Turek + */ +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', + ]; + } +} diff --git a/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php b/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php new file mode 100644 index 0000000..1c0973a --- /dev/null +++ b/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php @@ -0,0 +1,59 @@ + + */ +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)]); + } +} diff --git a/Tests/EventListener/PsrResponseListenerTest.php b/Tests/EventListener/PsrResponseListenerTest.php new file mode 100644 index 0000000..7b47d66 --- /dev/null +++ b/Tests/EventListener/PsrResponseListenerTest.php @@ -0,0 +1,44 @@ + + */ +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); + } +} diff --git a/Tests/Fixtures/App/Controller/PsrRequestController.php b/Tests/Fixtures/App/Controller/PsrRequestController.php new file mode 100644 index 0000000..18b7741 --- /dev/null +++ b/Tests/Fixtures/App/Controller/PsrRequestController.php @@ -0,0 +1,45 @@ +responseFactory = $responseFactory; + $this->streamFactory = $streamFactory; + } + + public function serverRequestAction(ServerRequestInterface $request): ResponseInterface + { + return $this->responseFactory + ->createResponse() + ->withBody($this->streamFactory->createStream(sprintf('%s', $request->getMethod()))); + } + + public function requestAction(RequestInterface $request): ResponseInterface + { + return $this->responseFactory + ->createResponse() + ->withStatus(403) + ->withBody($this->streamFactory->createStream(sprintf('%s %s', $request->getMethod(), $request->getBody()->getContents()))); + } + + public function messageAction(MessageInterface $request): ResponseInterface + { + return $this->responseFactory + ->createResponse() + ->withStatus(422) + ->withBody($this->streamFactory->createStream(sprintf('%s', $request->getHeader('X-My-Header')[0]))); + } +} diff --git a/Tests/Fixtures/App/Kernel.php b/Tests/Fixtures/App/Kernel.php new file mode 100644 index 0000000..aef8193 --- /dev/null +++ b/Tests/Fixtures/App/Kernel.php @@ -0,0 +1,76 @@ +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') + ; + + $container->services() + ->defaults()->autowire()->autoconfigure() + ->set(HttpFoundationFactoryInterface::class, HttpFoundationFactory::class) + ->set(HttpMessageFactoryInterface::class, PsrHttpFactory::class) + ->set(PsrResponseListener::class) + ->set(PsrServerRequestResolver::class) + ; + + $container->services() + ->set('logger', NullLogger::class) + ->set(PsrRequestController::class)->public()->autowire() + ; + } +} diff --git a/Tests/Fixtures/App/Kernel44.php b/Tests/Fixtures/App/Kernel44.php new file mode 100644 index 0000000..e976ae2 --- /dev/null +++ b/Tests/Fixtures/App/Kernel44.php @@ -0,0 +1,67 @@ +add('/server-request', PsrRequestController::class.'::serverRequestAction')->setMethods(['GET']); + $routes->add('/request', PsrRequestController::class.'::requestAction')->setMethods(['POST']); + $routes->add('/message', PsrRequestController::class.'::messageAction')->setMethods(['PUT']); + } + + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void + { + $container->loadFromExtension('framework', [ + 'secret' => 'for your eyes only', + 'test' => true, + ]); + + $container->register('nyholm.psr_factory', Psr17Factory::class); + $container->setAlias(ResponseFactoryInterface::class, 'nyholm.psr_factory'); + $container->setAlias(ServerRequestFactoryInterface::class, 'nyholm.psr_factory'); + $container->setAlias(StreamFactoryInterface::class, 'nyholm.psr_factory'); + $container->setAlias(UploadedFileFactoryInterface::class, 'nyholm.psr_factory'); + + $container->register(HttpFoundationFactoryInterface::class, HttpFoundationFactory::class)->setAutowired(true)->setAutoconfigured(true); + $container->register(HttpMessageFactoryInterface::class, PsrHttpFactory::class)->setAutowired(true)->setAutoconfigured(true); + $container->register(PsrResponseListener::class)->setAutowired(true)->setAutoconfigured(true); + $container->register(PsrServerRequestResolver::class)->setAutowired(true)->setAutoconfigured(true); + + $container->register('logger', NullLogger::class); + $container->register(PsrRequestController::class)->setPublic(true)->setAutowired(true); + } +} diff --git a/Tests/Functional/ControllerTest.php b/Tests/Functional/ControllerTest.php new file mode 100644 index 0000000..1ab5419 --- /dev/null +++ b/Tests/Functional/ControllerTest.php @@ -0,0 +1,46 @@ + + */ +final class ControllerTest extends WebTestCase +{ + public function testServerRequestAction() + { + $client = self::createClient(); + $crawler = $client->request('GET', '/server-request'); + + self::assertResponseStatusCodeSame(200); + self::assertSame('GET', $crawler->text()); + } + + public function testRequestAction() + { + $client = self::createClient(); + $crawler = $client->request('POST', '/request', [], [], [], 'some content'); + + self::assertResponseStatusCodeSame(403); + self::assertSame('POST some content', $crawler->text()); + } + + public function testMessageAction() + { + $client = self::createClient(); + $crawler = $client->request('PUT', '/message', [], [], ['HTTP_X_MY_HEADER' => 'some content']); + + self::assertResponseStatusCodeSame(422); + self::assertSame('some content', $crawler->text()); + } + + protected static function getKernelClass(): string + { + return SymfonyKernel::VERSION_ID >= 50200 ? Kernel::class : Kernel44::class; + } +} diff --git a/composer.json b/composer.json index 6fa97f0..07563bc 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,14 @@ "symfony/http-foundation": "^4.4 || ^5.0" }, "require-dev": { + "symfony/browser-kit": "^4.4 || ^5.0", + "symfony/config": "^4.4 || ^5.0", + "symfony/event-dispatcher": "^4.4 || ^5.0", + "symfony/framework-bundle": "^4.4 || ^5.0", + "symfony/http-kernel": "^4.4 || ^5.0", "symfony/phpunit-bridge": "^4.4.19 || ^5.2", - "nyholm/psr7": "^1.1" + "nyholm/psr7": "^1.1", + "psr/log": "^1.1" }, "suggest": { "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" @@ -35,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "2.1-dev" } } } From 70787392326def5ed9bfe7dc930af99f4ef12ff2 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Wed, 17 Feb 2021 14:04:17 +0100 Subject: [PATCH 64/91] remove link to sensio extra bundle which removed psr7 support --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 87fbd43..dcbc09a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ Resources --------- * [Documentation](https://symfony.com/doc/current/components/psr7.html) - * [SensioFrameworkExtraBundle](https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html#psr-7-support) Running the tests ----------------- From 87fabb9e1655a1afa66ff8d12771aa2cccc079de Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 10 Mar 2021 21:15:19 +0100 Subject: [PATCH 65/91] Fix copyright year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 12a7453..9ff2d0d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 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 From 26068fa40d91305529cb168997c0b528bb6eef07 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 10 Mar 2021 16:28:02 +0100 Subject: [PATCH 66/91] Minor cleanups --- ArgumentValueResolver/PsrServerRequestResolver.php | 9 +++++++++ .../PsrServerRequestResolverTest.php | 9 +++++++++ Tests/EventListener/PsrResponseListenerTest.php | 9 +++++++++ Tests/Functional/ControllerTest.php | 9 +++++++++ 4 files changed, 36 insertions(+) diff --git a/ArgumentValueResolver/PsrServerRequestResolver.php b/ArgumentValueResolver/PsrServerRequestResolver.php index 094f0c8..29dc7dc 100644 --- a/ArgumentValueResolver/PsrServerRequestResolver.php +++ b/ArgumentValueResolver/PsrServerRequestResolver.php @@ -1,5 +1,14 @@ + * + * 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 Psr\Http\Message\MessageInterface; diff --git a/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php b/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php index 1c0973a..662b186 100644 --- a/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php +++ b/Tests/ArgumentValueResolver/PsrServerRequestResolverTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Bridge\PsrHttpMessage\Tests\ArgumentValueResolver; use PHPUnit\Framework\TestCase; diff --git a/Tests/EventListener/PsrResponseListenerTest.php b/Tests/EventListener/PsrResponseListenerTest.php index 7b47d66..9a94b20 100644 --- a/Tests/EventListener/PsrResponseListenerTest.php +++ b/Tests/EventListener/PsrResponseListenerTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Bridge\PsrHttpMessage\Tests\EventListener; use PHPUnit\Framework\TestCase; diff --git a/Tests/Functional/ControllerTest.php b/Tests/Functional/ControllerTest.php index 1ab5419..0b88405 100644 --- a/Tests/Functional/ControllerTest.php +++ b/Tests/Functional/ControllerTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Bridge\PsrHttpMessage\Tests\Functional; use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App\Kernel; From 8e13ae471249091f892f99f574c9836d675ccdd6 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 18 Jul 2021 22:25:09 +0200 Subject: [PATCH 67/91] Allow psr/log 2 and 3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 07563bc..ca55221 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "symfony/http-kernel": "^4.4 || ^5.0", "symfony/phpunit-bridge": "^4.4.19 || ^5.2", "nyholm/psr7": "^1.1", - "psr/log": "^1.1" + "psr/log": "^1.1 || ^2 || ^3" }, "suggest": { "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" From ab64c694bfb8292f76b81b8aaeac680d02d7f3bd Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 19 Jul 2021 00:42:11 +0200 Subject: [PATCH 68/91] Run PHPUnit on GitHub Actions --- .github/workflows/ci.yml | 48 ++++++++++++++++++++++++++++++++++++ .travis.yml | 53 ---------------------------------------- 2 files changed, 48 insertions(+), 53 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..89e6458 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +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.1.3', '7.2', '7.3', '7.4', '8.0'] + include: + - php: '7.4' + deps: lowest + deprecations: max[self]=0 + - php: '8.0' + deps: highest + + 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/.travis.yml b/.travis.yml deleted file mode 100644 index 5409284..0000000 --- a/.travis.yml +++ /dev/null @@ -1,53 +0,0 @@ -language: php -sudo: false -cache: - directories: - - $HOME/.composer/cache/files - - $HOME/symfony-bridge/.phpunit - -env: - global: - - PHPUNIT_FLAGS="-v" - - SYMFONY_PHPUNIT_VERSION=9.5 - - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" - -matrix: - fast_finish: true - include: - # Minimum supported dependencies with the latest and oldest PHP version - - php: 7.4 - env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors" - - - php: 7.1 - env: SYMFONY_PHPUNIT_VERSION=7.5 - - php: 7.2 - env: SYMFONY_PHPUNIT_VERSION=8.5 - - php: 7.3 - - php: 7.4 - env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" XDEBUG_MODE=coverage - - php: 8.0 - - # Latest commit to master - - php: 7.4 - env: STABILITY="dev" - - allow_failures: - # Dev-master is allowed to fail. - - env: STABILITY="dev" - -before_install: - - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi - - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi; - - if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi; - -install: - # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355 - - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi - - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction - - ./vendor/bin/simple-phpunit install - -script: - - composer validate --strict --no-check-lock - # simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and - # it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge) - - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS From d1526499b8a155064f8b8e893b046392abfffabc Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 4 Aug 2021 23:12:43 +0200 Subject: [PATCH 69/91] Inline $tmpDir --- Tests/Functional/CovertTest.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php index f460b0e..3d72b71 100644 --- a/Tests/Functional/CovertTest.php +++ b/Tests/Functional/CovertTest.php @@ -34,15 +34,11 @@ */ class CovertTest extends TestCase { - private $tmpDir; - protected function setUp(): void { if (!class_exists(Psr7Request::class)) { $this->markTestSkipped('nyholm/psr7 is not installed.'); } - - $this->tmpDir = sys_get_temp_dir(); } /** @@ -233,7 +229,7 @@ public function responseProvider() private function createUploadedFile($content, $originalName, $mimeType, $error) { - $path = tempnam($this->tmpDir, uniqid()); + $path = tempnam(sys_get_temp_dir(), uniqid()); file_put_contents($path, $content); return new UploadedFile($path, $originalName, $mimeType, $error, true); From f8f70fad692d5e0db10a42171528080a8ea1279d Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 5 Oct 2021 13:59:44 +0200 Subject: [PATCH 70/91] Add return types to fixtures --- .github/workflows/ci.yml | 1 + Tests/Fixtures/Message.php | 39 +++++++++++++--- Tests/Fixtures/Response.php | 7 ++- Tests/Fixtures/ServerRequest.php | 77 ++++++++++++++++++++++++++++---- Tests/Fixtures/Stream.php | 34 ++++++++------ Tests/Fixtures/UploadedFile.php | 12 ++--- Tests/Fixtures/Uri.php | 69 +++++++++++++++++++++------- 7 files changed, 186 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89e6458..9e039c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: deprecations: max[self]=0 - php: '8.0' deps: highest + deprecations: max[indirect]=5 steps: - name: Checkout code diff --git a/Tests/Fixtures/Message.php b/Tests/Fixtures/Message.php index 0cda6fc..d561086 100644 --- a/Tests/Fixtures/Message.php +++ b/Tests/Fixtures/Message.php @@ -29,39 +29,49 @@ public function __construct($version = '1.1', array $headers = [], StreamInterfa { $this->version = $version; $this->headers = $headers; - $this->body = null === $body ? new Stream() : $body; + $this->body = $body ?? new Stream(); } - public function getProtocolVersion() + public function getProtocolVersion(): string { return $this->version; } + /** + * {@inheritdoc} + * + * @return static + */ public function withProtocolVersion($version) { throw new \BadMethodCallException('Not implemented.'); } - public function getHeaders() + public function getHeaders(): array { return $this->headers; } - public function hasHeader($name) + public function hasHeader($name): bool { return isset($this->headers[$name]); } - public function getHeader($name) + public function getHeader($name): array { return $this->hasHeader($name) ? $this->headers[$name] : []; } - public function getHeaderLine($name) + public function getHeaderLine($name): string { return $this->hasHeader($name) ? implode(',', $this->headers[$name]) : ''; } + /** + * {@inheritdoc} + * + * @return static + */ public function withHeader($name, $value) { $this->headers[$name] = (array) $value; @@ -69,11 +79,21 @@ public function withHeader($name, $value) return $this; } + /** + * {@inheritdoc} + * + * @return static + */ public function withAddedHeader($name, $value) { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return static + */ public function withoutHeader($name) { unset($this->headers[$name]); @@ -81,11 +101,16 @@ public function withoutHeader($name) return $this; } - public function getBody() + public function getBody(): StreamInterface { return $this->body; } + /** + * {@inheritdoc} + * + * @return static + */ public function withBody(StreamInterface $body) { throw new \BadMethodCallException('Not implemented.'); diff --git a/Tests/Fixtures/Response.php b/Tests/Fixtures/Response.php index a890792..0bcf7f4 100644 --- a/Tests/Fixtures/Response.php +++ b/Tests/Fixtures/Response.php @@ -28,17 +28,20 @@ public function __construct($version = '1.1', array $headers = [], StreamInterfa $this->statusCode = $statusCode; } - public function getStatusCode() + public function getStatusCode(): int { return $this->statusCode; } + /** + * @return static + */ public function withStatus($code, $reasonPhrase = '') { throw new \BadMethodCallException('Not implemented.'); } - public function getReasonPhrase() + public function getReasonPhrase(): string { throw new \BadMethodCallException('Not implemented.'); } diff --git a/Tests/Fixtures/ServerRequest.php b/Tests/Fixtures/ServerRequest.php index 88ec984..b8df06a 100644 --- a/Tests/Fixtures/ServerRequest.php +++ b/Tests/Fixtures/ServerRequest.php @@ -45,95 +45,156 @@ public function __construct($version = '1.1', array $headers = [], StreamInterfa $this->attributes = $attributes; } - public function getRequestTarget() + public function getRequestTarget(): string { return $this->requestTarget; } + /** + * {@inheritdoc} + * + * @return static + */ public function withRequestTarget($requestTarget) { throw new \BadMethodCallException('Not implemented.'); } - public function getMethod() + public function getMethod(): string { return $this->method; } + /** + * {@inheritdoc} + * + * @return static + */ public function withMethod($method) { + throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return UriInterface + */ public function getUri() { return $this->uri; } + /** + * {@inheritdoc} + * + * @return static + */ public function withUri(UriInterface $uri, $preserveHost = false) { throw new \BadMethodCallException('Not implemented.'); } - public function getServerParams() + public function getServerParams(): array { return $this->server; } - public function getCookieParams() + public function getCookieParams(): array { return $this->cookies; } + /** + * {@inheritdoc} + * + * @return static + */ public function withCookieParams(array $cookies) { throw new \BadMethodCallException('Not implemented.'); } - public function getQueryParams() + public function getQueryParams(): array { return $this->query; } + /** + * {@inheritdoc} + * + * @return static + */ public function withQueryParams(array $query) { throw new \BadMethodCallException('Not implemented.'); } - public function getUploadedFiles() + public function getUploadedFiles(): array { return $this->uploadedFiles; } + /** + * {@inheritdoc} + * + * @return static + */ public function withUploadedFiles(array $uploadedFiles) { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return array|object|null + */ public function getParsedBody() { return $this->data; } + /** + * {@inheritdoc} + * + * @return static + */ public function withParsedBody($data) { throw new \BadMethodCallException('Not implemented.'); } - public function getAttributes() + public function getAttributes(): array { return $this->attributes; } + /** + * {@inheritdoc} + * + * @return mixed + */ public function getAttribute($name, $default = null) { - return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + return $this->attributes[$name] ?? $default; } + /** + * {@inheritdoc} + * + * @return static + */ public function withAttribute($name, $value) { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return static + */ public function withoutAttribute($name) { throw new \BadMethodCallException('Not implemented.'); diff --git a/Tests/Fixtures/Stream.php b/Tests/Fixtures/Stream.php index 2cb4ab2..f664bae 100644 --- a/Tests/Fixtures/Stream.php +++ b/Tests/Fixtures/Stream.php @@ -26,12 +26,12 @@ public function __construct($stringContent = '') $this->stringContent = $stringContent; } - public function __toString() + public function __toString(): string { return $this->stringContent; } - public function close() + public function close(): void { } @@ -40,61 +40,69 @@ public function detach() return fopen('data://text/plain,'.$this->stringContent, 'r'); } - public function getSize() + public function getSize(): ?int { + return null; } - public function tell() + public function tell(): int { return 0; } - public function eof() + public function eof(): bool { return $this->eof; } - public function isSeekable() + public function isSeekable(): bool { return true; } - public function seek($offset, $whence = \SEEK_SET) + public function seek($offset, $whence = \SEEK_SET): void { } - public function rewind() + public function rewind(): void { $this->eof = false; } - public function isWritable() + public function isWritable(): bool { return false; } - public function write($string) + public function write($string): int { + return \strlen($string); } - public function isReadable() + public function isReadable(): bool { return true; } - public function read($length) + public function read($length): string { $this->eof = true; return $this->stringContent; } - public function getContents() + public function getContents(): string { return $this->stringContent; } + /** + * {@inheritdoc} + * + * @return mixed + */ public function getMetadata($key = null) { + return null; } } diff --git a/Tests/Fixtures/UploadedFile.php b/Tests/Fixtures/UploadedFile.php index 93b3214..9004008 100644 --- a/Tests/Fixtures/UploadedFile.php +++ b/Tests/Fixtures/UploadedFile.php @@ -33,32 +33,32 @@ public function __construct($filePath, $size = null, $error = \UPLOAD_ERR_OK, $c $this->clientMediaType = $clientMediaType; } - public function getStream() + public function getStream(): Stream { return new Stream(file_get_contents($this->filePath)); } - public function moveTo($targetPath) + public function moveTo($targetPath): void { rename($this->filePath, $targetPath); } - public function getSize() + public function getSize(): ?int { return $this->size; } - public function getError() + public function getError(): int { return $this->error; } - public function getClientFilename() + public function getClientFilename(): ?string { return $this->clientFileName; } - public function getClientMediaType() + public function getClientMediaType(): ?string { return $this->clientMediaType; } diff --git a/Tests/Fixtures/Uri.php b/Tests/Fixtures/Uri.php index f11c7e5..48f513d 100644 --- a/Tests/Fixtures/Uri.php +++ b/Tests/Fixtures/Uri.php @@ -27,26 +27,26 @@ class Uri implements UriInterface private $fragment = ''; private $uriString; - public function __construct($uri = '') + public function __construct(string $uri = '') { $parts = parse_url($uri); - $this->scheme = isset($parts['scheme']) ? $parts['scheme'] : ''; - $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; - $this->host = isset($parts['host']) ? $parts['host'] : ''; - $this->port = isset($parts['port']) ? $parts['port'] : null; - $this->path = isset($parts['path']) ? $parts['path'] : ''; - $this->query = isset($parts['query']) ? $parts['query'] : ''; - $this->fragment = isset($parts['fragment']) ? $parts['fragment'] : ''; + $this->scheme = $parts['scheme'] ?? ''; + $this->userInfo = $parts['user'] ?? ''; + $this->host = $parts['host'] ?? ''; + $this->port = $parts['port'] ?? null; + $this->path = $parts['path'] ?? ''; + $this->query = $parts['query'] ?? ''; + $this->fragment = $parts['fragment'] ?? ''; $this->uriString = $uri; } - public function getScheme() + public function getScheme(): string { return $this->scheme; } - public function getAuthority() + public function getAuthority(): string { if (empty($this->host)) { return ''; @@ -63,72 +63,107 @@ public function getAuthority() return $authority; } - public function getUserInfo() + public function getUserInfo(): string { return $this->userInfo; } - public function getHost() + public function getHost(): string { return $this->host; } - public function getPort() + public function getPort(): ?int { return $this->port; } - public function getPath() + public function getPath(): string { return $this->path; } - public function getQuery() + public function getQuery(): string { return $this->query; } - public function getFragment() + public function getFragment(): string { return $this->fragment; } + /** + * {@inheritdoc} + * + * @return static + */ public function withScheme($scheme) { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return static + */ public function withUserInfo($user, $password = null) { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return static + */ public function withHost($host) { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return static + */ public function withPort($port) { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return static + */ public function withPath($path) { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return static + */ public function withQuery($query) { throw new \BadMethodCallException('Not implemented.'); } + /** + * {@inheritdoc} + * + * @return static + */ public function withFragment($fragment) { throw new \BadMethodCallException('Not implemented.'); } - public function __toString() + public function __toString(): string { return $this->uriString; } From b2bd334ced3da1d48e3869e87cc5e7ca63d0a101 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 6 Oct 2021 17:50:48 +0200 Subject: [PATCH 71/91] Add PHP 8.1 to CI Signed-off-by: Alexander M. Turek --- .github/workflows/ci.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e039c0..4c0e8ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.1.3', '7.2', '7.3', '7.4', '8.0'] + php: ['7.1.3', '7.2', '7.3', '7.4', '8.0', '8.1'] include: - php: '7.4' deps: lowest diff --git a/composer.json b/composer.json index ca55221..e1e6c0f 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "symfony/event-dispatcher": "^4.4 || ^5.0", "symfony/framework-bundle": "^4.4 || ^5.0", "symfony/http-kernel": "^4.4 || ^5.0", - "symfony/phpunit-bridge": "^4.4.19 || ^5.2", + "symfony/phpunit-bridge": "^5.4@dev", "nyholm/psr7": "^1.1", "psr/log": "^1.1 || ^2 || ^3" }, From c7a0be3024abe4005c9fbf4f6b041995dbfbc6ef Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Mon, 1 Nov 2021 23:04:37 +0100 Subject: [PATCH 72/91] Allow Symfony 6 --- composer.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index e1e6c0f..0282307 100644 --- a/composer.json +++ b/composer.json @@ -18,15 +18,15 @@ "require": { "php": ">=7.1", "psr/http-message": "^1.0", - "symfony/http-foundation": "^4.4 || ^5.0" + "symfony/http-foundation": "^4.4 || ^5.0 || ^6.0" }, "require-dev": { - "symfony/browser-kit": "^4.4 || ^5.0", - "symfony/config": "^4.4 || ^5.0", - "symfony/event-dispatcher": "^4.4 || ^5.0", - "symfony/framework-bundle": "^4.4 || ^5.0", - "symfony/http-kernel": "^4.4 || ^5.0", - "symfony/phpunit-bridge": "^5.4@dev", + "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", + "symfony/config": "^4.4 || ^5.0 || ^6.0", + "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", + "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", + "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^5.4@dev || ^6.0", "nyholm/psr7": "^1.1", "psr/log": "^1.1 || ^2 || ^3" }, From c382d76c4de890670efda7b7eb7fe2049c9986bc Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Fri, 5 Nov 2021 14:10:50 +0100 Subject: [PATCH 73/91] Release v2.1.2 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d70e5a5..c17d8f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG ========= +# 2.1.2 (2021-11-05) + +* Allow Symfony 6 + # 2.1.0 (2021-02-17) * Added a `PsrResponseListener` to automatically convert PSR-7 responses returned by controllers From 7f3b5c1a724016501b9b994ef16a90da7e9c435d Mon Sep 17 00:00:00 2001 From: lemon-juice Date: Tue, 16 Nov 2021 10:18:48 +0100 Subject: [PATCH 74/91] Fix for wrong type passed to moveTo() --- Factory/UploadedFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Factory/UploadedFile.php b/Factory/UploadedFile.php index 53aa37a..b951045 100644 --- a/Factory/UploadedFile.php +++ b/Factory/UploadedFile.php @@ -61,7 +61,7 @@ public function move($directory, $name = null): File $target = $this->getTargetFile($directory, $name); try { - $this->psrUploadedFile->moveTo($target); + $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); } From 808561a68f41107c91bc72c5c105ba7abe8f2a7b Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Thu, 17 Mar 2022 17:09:33 +0100 Subject: [PATCH 75/91] Add missing .gitattributes --- .gitattributes | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2df4520 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.github export-ignore +/.gitignore export-ignore +/.php_cs.dist export-ignore From 9a78a160b95e30e591aa992ab8c84978e3cd3e10 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 4 Sep 2022 20:36:52 +0200 Subject: [PATCH 76/91] Ignore invalid HTTP headers when creating PSR7 objects --- Factory/PsrHttpFactory.php | 12 +- .../AbstractHttpMessageFactoryTest.php | 234 ------------------ Tests/Factory/PsrHttpFactoryTest.php | 218 +++++++++++++++- Tests/Fixtures/App/Kernel.php | 1 + Tests/Fixtures/App/Kernel44.php | 1 + 5 files changed, 229 insertions(+), 237 deletions(-) delete mode 100644 Tests/Factory/AbstractHttpMessageFactoryTest.php diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index d19baa1..61650df 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -58,7 +58,11 @@ public function createRequest(Request $symfonyRequest) ); foreach ($symfonyRequest->headers->all() as $name => $value) { - $request = $request->withHeader($name, $value); + try { + $request = $request->withHeader($name, $value); + } catch (\InvalidArgumentException $e) { + // ignore invalid header + } } $body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true)); @@ -160,7 +164,11 @@ public function createResponse(Response $symfonyResponse) } foreach ($headers as $name => $value) { - $response = $response->withHeader($name, $value); + try { + $response = $response->withHeader($name, $value); + } catch (\InvalidArgumentException $e) { + // ignore invalid header + } } $protocolVersion = $symfonyResponse->getProtocolVersion(); diff --git a/Tests/Factory/AbstractHttpMessageFactoryTest.php b/Tests/Factory/AbstractHttpMessageFactoryTest.php deleted file mode 100644 index 82d3fc7..0000000 --- a/Tests/Factory/AbstractHttpMessageFactoryTest.php +++ /dev/null @@ -1,234 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; - -use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; -use Symfony\Component\HttpFoundation\BinaryFileResponse; -use Symfony\Component\HttpFoundation\Cookie; -use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\StreamedResponse; - -/** - * @author Kévin Dunglas - * @author Antonio J. García Lagar - */ -abstract class AbstractHttpMessageFactoryTest extends TestCase -{ - private $factory; - private $tmpDir; - - abstract protected function buildHttpMessageFactory(): HttpMessageFactoryInterface; - - protected function setUp(): void - { - $this->factory = $this->buildHttpMessageFactory(); - $this->tmpDir = sys_get_temp_dir(); - } - - public function testCreateRequest() - { - $stdClass = new \stdClass(); - $request = new Request( - [ - 'bar' => ['baz' => '42'], - 'foo' => '1', - ], - [ - 'twitter' => [ - '@dunglas' => 'Kévin Dunglas', - '@coopTilleuls' => 'Les-Tilleuls.coop', - ], - 'baz' => '2', - ], - [ - 'a1' => $stdClass, - 'a2' => ['foo' => 'bar'], - ], - [ - 'c1' => 'foo', - 'c2' => ['c3' => 'bar'], - ], - [ - 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', \UPLOAD_ERR_OK), - 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', \UPLOAD_ERR_OK)], - ], - [ - 'REQUEST_METHOD' => 'POST', - 'HTTP_HOST' => 'dunglas.fr', - 'HTTP_X_SYMFONY' => '2.8', - 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1', - 'QUERY_STRING' => 'bar[baz]=42&foo=1', - ], - 'Content' - ); - - $psrRequest = $this->factory->createRequest($request); - - $this->assertEquals('Content', $psrRequest->getBody()->__toString()); - - $queryParams = $psrRequest->getQueryParams(); - $this->assertEquals('1', $queryParams['foo']); - $this->assertEquals('42', $queryParams['bar']['baz']); - - $requestTarget = $psrRequest->getRequestTarget(); - $this->assertEquals('/testCreateRequest?bar[baz]=42&foo=1', urldecode($requestTarget)); - - $parsedBody = $psrRequest->getParsedBody(); - $this->assertEquals('Kévin Dunglas', $parsedBody['twitter']['@dunglas']); - $this->assertEquals('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']); - $this->assertEquals('2', $parsedBody['baz']); - - $attributes = $psrRequest->getAttributes(); - $this->assertEquals($stdClass, $attributes['a1']); - $this->assertEquals('bar', $attributes['a2']['foo']); - - $cookies = $psrRequest->getCookieParams(); - $this->assertEquals('foo', $cookies['c1']); - $this->assertEquals('bar', $cookies['c2']['c3']); - - $uploadedFiles = $psrRequest->getUploadedFiles(); - $this->assertEquals('F1', $uploadedFiles['f1']->getStream()->__toString()); - $this->assertEquals('f1.txt', $uploadedFiles['f1']->getClientFilename()); - $this->assertEquals('text/plain', $uploadedFiles['f1']->getClientMediaType()); - $this->assertEquals(\UPLOAD_ERR_OK, $uploadedFiles['f1']->getError()); - - $this->assertEquals('F2', $uploadedFiles['foo']['f2']->getStream()->__toString()); - $this->assertEquals('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename()); - $this->assertEquals('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType()); - $this->assertEquals(\UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError()); - - $serverParams = $psrRequest->getServerParams(); - $this->assertEquals('POST', $serverParams['REQUEST_METHOD']); - $this->assertEquals('2.8', $serverParams['HTTP_X_SYMFONY']); - $this->assertEquals('POST', $psrRequest->getMethod()); - $this->assertEquals(['2.8'], $psrRequest->getHeader('X-Symfony')); - } - - public function testGetContentCanBeCalledAfterRequestCreation() - { - $header = ['HTTP_HOST' => 'dunglas.fr']; - $request = new Request([], [], [], [], [], $header, 'Content'); - - $psrRequest = $this->factory->createRequest($request); - - $this->assertEquals('Content', $psrRequest->getBody()->__toString()); - $this->assertEquals('Content', $request->getContent()); - } - - private function createUploadedFile($content, $originalName, $mimeType, $error) - { - $path = tempnam($this->tmpDir, uniqid()); - file_put_contents($path, $content); - - return new UploadedFile($path, $originalName, $mimeType, $error, true); - } - - public function testCreateResponse() - { - $response = new Response( - 'Response content.', - 202, - ['X-Symfony' => ['3.4']] - ); - $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); - $this->assertEquals('Response content.', $psrResponse->getBody()->__toString()); - $this->assertEquals(202, $psrResponse->getStatusCode()); - $this->assertEquals(['3.4'], $psrResponse->getHeader('X-Symfony')); - - $cookieHeader = $psrResponse->getHeader('Set-Cookie'); - $this->assertIsArray($cookieHeader); - $this->assertCount(1, $cookieHeader); - $this->assertMatchesRegularExpression('{city=Lille; expires=Wed, 13-Jan-2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); - } - - public function testCreateResponseFromStreamed() - { - $response = new StreamedResponse(function () { - echo "Line 1\n"; - flush(); - - echo "Line 2\n"; - flush(); - }); - - $psrResponse = $this->factory->createResponse($response); - - $this->assertEquals("Line 1\nLine 2\n", $psrResponse->getBody()->__toString()); - } - - public function testCreateResponseFromBinaryFile() - { - $path = tempnam($this->tmpDir, uniqid()); - file_put_contents($path, 'Binary'); - - $response = new BinaryFileResponse($path); - - $psrResponse = $this->factory->createResponse($response); - - $this->assertEquals('Binary', $psrResponse->getBody()->__toString()); - } - - public function testCreateResponseFromBinaryFileWithRange() - { - $path = tempnam($this->tmpDir, uniqid()); - file_put_contents($path, 'Binary'); - - $request = new Request(); - $request->headers->set('Range', 'bytes=1-4'); - - $response = new BinaryFileResponse($path, 200, ['Content-Type' => 'plain/text']); - $response->prepare($request); - - $psrResponse = $this->factory->createResponse($response); - - $this->assertEquals('inar', $psrResponse->getBody()->__toString()); - $this->assertSame('bytes 1-4/6', $psrResponse->getHeaderLine('Content-Range')); - } - - public function testUploadErrNoFile() - { - $file = new UploadedFile('', '', null, \UPLOAD_ERR_NO_FILE, true); - - $this->assertEquals(0, $file->getSize()); - $this->assertEquals(\UPLOAD_ERR_NO_FILE, $file->getError()); - $this->assertFalse($file->getSize(), 'SplFile::getSize() returns false on error'); - - $request = new Request( - [], - [], - [], - [], - [ - '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', - ], - 'Content' - ); - - $psrRequest = $this->factory->createRequest($request); - - $uploadedFiles = $psrRequest->getUploadedFiles(); - - $this->assertEquals(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); - $this->assertEquals(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); - } -} diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php index b47cefc..1923bf4 100644 --- a/Tests/Factory/PsrHttpFactoryTest.php +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -11,20 +11,236 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; +use PHPUnit\Framework\TestCase; use Nyholm\Psr7\Factory\Psr17Factory; 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; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; /** * @author Kévin Dunglas * @author Antonio J. García Lagar */ -class PsrHttpFactoryTest extends AbstractHttpMessageFactoryTest +class PsrHttpFactoryTest extends TestCase { + private $factory; + 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(); + } + + public function testCreateRequest() + { + $stdClass = new \stdClass(); + $request = new Request( + [ + 'bar' => ['baz' => '42'], + 'foo' => '1', + ], + [ + 'twitter' => [ + '@dunglas' => 'Kévin Dunglas', + '@coopTilleuls' => 'Les-Tilleuls.coop', + ], + 'baz' => '2', + ], + [ + 'a1' => $stdClass, + 'a2' => ['foo' => 'bar'], + ], + [ + 'c1' => 'foo', + 'c2' => ['c3' => 'bar'], + ], + [ + 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', \UPLOAD_ERR_OK), + 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', \UPLOAD_ERR_OK)], + ], + [ + 'REQUEST_METHOD' => 'POST', + 'HTTP_HOST' => 'dunglas.fr', + 'HTTP_X_SYMFONY' => '2.8', + 'REQUEST_URI' => '/testCreateRequest?bar[baz]=42&foo=1', + 'QUERY_STRING' => 'bar[baz]=42&foo=1', + ], + 'Content' + ); + $request->headers->set(' X-Broken', 'abc'); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertSame('Content', $psrRequest->getBody()->__toString()); + + $queryParams = $psrRequest->getQueryParams(); + $this->assertSame('1', $queryParams['foo']); + $this->assertSame('42', $queryParams['bar']['baz']); + + $requestTarget = $psrRequest->getRequestTarget(); + $this->assertSame('/testCreateRequest?bar[baz]=42&foo=1', urldecode($requestTarget)); + + $parsedBody = $psrRequest->getParsedBody(); + $this->assertSame('Kévin Dunglas', $parsedBody['twitter']['@dunglas']); + $this->assertSame('Les-Tilleuls.coop', $parsedBody['twitter']['@coopTilleuls']); + $this->assertSame('2', $parsedBody['baz']); + + $attributes = $psrRequest->getAttributes(); + $this->assertSame($stdClass, $attributes['a1']); + $this->assertSame('bar', $attributes['a2']['foo']); + + $cookies = $psrRequest->getCookieParams(); + $this->assertSame('foo', $cookies['c1']); + $this->assertSame('bar', $cookies['c2']['c3']); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + $this->assertSame('F1', $uploadedFiles['f1']->getStream()->__toString()); + $this->assertSame('f1.txt', $uploadedFiles['f1']->getClientFilename()); + $this->assertSame('text/plain', $uploadedFiles['f1']->getClientMediaType()); + $this->assertSame(\UPLOAD_ERR_OK, $uploadedFiles['f1']->getError()); + + $this->assertSame('F2', $uploadedFiles['foo']['f2']->getStream()->__toString()); + $this->assertSame('f2.txt', $uploadedFiles['foo']['f2']->getClientFilename()); + $this->assertSame('text/plain', $uploadedFiles['foo']['f2']->getClientMediaType()); + $this->assertSame(\UPLOAD_ERR_OK, $uploadedFiles['foo']['f2']->getError()); + + $serverParams = $psrRequest->getServerParams(); + $this->assertSame('POST', $serverParams['REQUEST_METHOD']); + $this->assertSame('2.8', $serverParams['HTTP_X_SYMFONY']); + $this->assertSame('POST', $psrRequest->getMethod()); + $this->assertSame(['2.8'], $psrRequest->getHeader('X-Symfony')); + } + + public function testGetContentCanBeCalledAfterRequestCreation() + { + $header = ['HTTP_HOST' => 'dunglas.fr']; + $request = new Request([], [], [], [], [], $header, 'Content'); + + $psrRequest = $this->factory->createRequest($request); + + $this->assertSame('Content', $psrRequest->getBody()->__toString()); + $this->assertSame('Content', $request->getContent()); + } + + private function createUploadedFile($content, $originalName, $mimeType, $error) + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, $content); + + return new UploadedFile($path, $originalName, $mimeType, $error, true); + } + + public function testCreateResponse() + { + $response = new Response( + 'Response content.', + 202, + [ + 'X-Symfony' => ['3.4'], + ' X-Broken-Header' => 'abc', + ] + ); + $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); + $this->assertSame('Response content.', $psrResponse->getBody()->__toString()); + $this->assertSame(202, $psrResponse->getStatusCode()); + $this->assertSame(['3.4'], $psrResponse->getHeader('x-symfony')); + $this->assertFalse($psrResponse->hasHeader(' X-Broken-Header')); + $this->assertFalse($psrResponse->hasHeader('X-Broken-Header')); + + $cookieHeader = $psrResponse->getHeader('Set-Cookie'); + $this->assertIsArray($cookieHeader); + $this->assertCount(1, $cookieHeader); + $this->assertMatchesRegularExpression('{city=Lille; expires=Wed, 13.Jan.2021 22:23:01 GMT;( max-age=\d+;)? path=/; httponly}i', $cookieHeader[0]); + } + + public function testCreateResponseFromStreamed() + { + $response = new StreamedResponse(function () { + echo "Line 1\n"; + flush(); + + echo "Line 2\n"; + flush(); + }); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertSame("Line 1\nLine 2\n", $psrResponse->getBody()->__toString()); + } + + public function testCreateResponseFromBinaryFile() + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, 'Binary'); + + $response = new BinaryFileResponse($path); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertSame('Binary', $psrResponse->getBody()->__toString()); + } + + public function testCreateResponseFromBinaryFileWithRange() + { + $path = tempnam($this->tmpDir, uniqid()); + file_put_contents($path, 'Binary'); + + $request = new Request(); + $request->headers->set('Range', 'bytes=1-4'); + + $response = new BinaryFileResponse($path, 200, ['Content-Type' => 'plain/text']); + $response->prepare($request); + + $psrResponse = $this->factory->createResponse($response); + + $this->assertSame('inar', $psrResponse->getBody()->__toString()); + $this->assertSame('bytes 1-4/6', $psrResponse->getHeaderLine('Content-Range')); + } + + public function testUploadErrNoFile() + { + $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( + [], + [], + [], + [], + [ + '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', + ], + 'Content' + ); + + $psrRequest = $this->factory->createRequest($request); + + $uploadedFiles = $psrRequest->getUploadedFiles(); + + $this->assertSame(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); + $this->assertSame(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); + } } diff --git a/Tests/Fixtures/App/Kernel.php b/Tests/Fixtures/App/Kernel.php index aef8193..611154d 100644 --- a/Tests/Fixtures/App/Kernel.php +++ b/Tests/Fixtures/App/Kernel.php @@ -50,6 +50,7 @@ protected function configureContainer(ContainerConfigurator $container): void 'router' => ['utf8' => true], 'secret' => 'for your eyes only', 'test' => true, + 'http_method_override' => false, ]); $container->services() diff --git a/Tests/Fixtures/App/Kernel44.php b/Tests/Fixtures/App/Kernel44.php index e976ae2..31ac7a1 100644 --- a/Tests/Fixtures/App/Kernel44.php +++ b/Tests/Fixtures/App/Kernel44.php @@ -48,6 +48,7 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa $container->loadFromExtension('framework', [ 'secret' => 'for your eyes only', 'test' => true, + 'http_method_override' => false, ]); $container->register('nyholm.psr_factory', Psr17Factory::class); From d444f85dddf65c7e57c58d8e5b3a4dbb593b1840 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 5 Sep 2022 12:34:54 +0200 Subject: [PATCH 77/91] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c17d8f3..87816cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +# 2.1.3 (2022-09-05) + +* Ignore invalid HTTP headers when creating PSR7 objects +* Fix for wrong type passed to `moveTo()` + # 2.1.2 (2021-11-05) * Allow Symfony 6 From 8c8a75b730dd5d4c640f561cf1facac689969172 Mon Sep 17 00:00:00 2001 From: Cidos Date: Wed, 23 Nov 2022 00:52:22 +0800 Subject: [PATCH 78/91] perf: ensure timely flush stream buffers Resolve OOM problem while handle a large streamed response --- Factory/PsrHttpFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index 61650df..b1b6f9a 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -142,7 +142,7 @@ public function createResponse(Response $symfonyResponse) $stream->write($buffer); return ''; - }); + }, 1); $symfonyResponse->sendContent(); ob_end_clean(); From 4592df2f40ef0673a1e8d6a382bf5f90f1d46d87 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 28 Nov 2022 23:36:27 +0100 Subject: [PATCH 79/91] Add PHP 8.2 to CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c0e8ae..fd976fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,12 +12,12 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.1.3', '7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['7.1.3', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] include: - php: '7.4' deps: lowest deprecations: max[self]=0 - - php: '8.0' + - php: '8.1' deps: highest deprecations: max[indirect]=5 From 5fa5f62409b9472442dbaee336ac8d06bd0bfa57 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 29 Nov 2022 00:12:51 +0100 Subject: [PATCH 80/91] Adjustments for PHP CS Fixer 3 --- .gitignore | 3 ++- .php_cs.dist => .php-cs-fixer.dist.php | 5 +++-- Tests/Factory/PsrHttpFactoryTest.php | 2 +- Tests/Functional/CovertTest.php | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) rename .php_cs.dist => .php-cs-fixer.dist.php (91%) diff --git a/.gitignore b/.gitignore index 55ce5dd..2fb17b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ vendor/ composer.lock phpunit.xml -.php_cs.cache +.php-cs-fixer.cache +.php-cs-fixer.php .phpunit.result.cache /Tests/Fixtures/App/var diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 91% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index d741d39..e9b256a 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -1,6 +1,6 @@ setRules([ '@Symfony' => true, '@Symfony:risky' => true, @@ -17,8 +17,9 @@ ]) ->setRiskyAllowed(true) ->setFinder( - PhpCsFixer\Finder::create() + (new PhpCsFixer\Finder()) ->in(__DIR__) + ->exclude('vendor') ->name('*.php') ) ; diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php index 1923bf4..a1cd1b1 100644 --- a/Tests/Factory/PsrHttpFactoryTest.php +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -11,8 +11,8 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Factory; -use PHPUnit\Framework\TestCase; 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; diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php index 3d72b71..cf82f82 100644 --- a/Tests/Functional/CovertTest.php +++ b/Tests/Functional/CovertTest.php @@ -85,7 +85,7 @@ public function testConvertRequestMultipleTimes($request, $firstFactory, $second $this->assertEquals((array) $request->getParsedBody(), (array) $finalRequest->getParsedBody()); $this->assertEquals($request->getQueryParams(), $finalRequest->getQueryParams()); // PSR7 does not define a "withServerParams" so this is impossible to implement without knowing the PSR7 implementation. - //$this->assertEquals($request->getServerParams(), $finalRequest->getServerParams()); + // $this->assertEquals($request->getServerParams(), $finalRequest->getServerParams()); $this->assertEquals($request->getUploadedFiles(), $finalRequest->getUploadedFiles()); $this->assertEquals($request->getMethod(), $finalRequest->getMethod()); $this->assertEquals($request->getRequestTarget(), $finalRequest->getRequestTarget()); From b360b35715b26445ce27efa72ef04a7227f1afa8 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 20 Apr 2023 15:15:00 +0200 Subject: [PATCH 81/91] Drop support for Symfony 4 --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 5 ++ .../EventListener/PsrResponseListenerTest.php | 2 +- Tests/Fixtures/App/Kernel44.php | 68 ------------------- Tests/Functional/ControllerTest.php | 4 +- composer.json | 18 ++--- 6 files changed, 17 insertions(+), 82 deletions(-) delete mode 100644 Tests/Fixtures/App/Kernel44.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd976fa..6a67387 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.1.3', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2'] include: - php: '7.4' deps: lowest diff --git a/CHANGELOG.md b/CHANGELOG.md index 87816cc..06f1435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +# 2.2.0 (TBA) + +* Drop support for Symfony 4 +* Bump minimum version of PHP to 7.2 + # 2.1.3 (2022-09-05) * Ignore invalid HTTP headers when creating PSR7 objects diff --git a/Tests/EventListener/PsrResponseListenerTest.php b/Tests/EventListener/PsrResponseListenerTest.php index 9a94b20..3f6060c 100644 --- a/Tests/EventListener/PsrResponseListenerTest.php +++ b/Tests/EventListener/PsrResponseListenerTest.php @@ -48,6 +48,6 @@ public function testDoesNotConvertControllerResult() private function createEventMock($controllerResult): ViewEvent { - return new ViewEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MASTER_REQUEST, $controllerResult); + return new ViewEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $controllerResult); } } diff --git a/Tests/Fixtures/App/Kernel44.php b/Tests/Fixtures/App/Kernel44.php deleted file mode 100644 index 31ac7a1..0000000 --- a/Tests/Fixtures/App/Kernel44.php +++ /dev/null @@ -1,68 +0,0 @@ -add('/server-request', PsrRequestController::class.'::serverRequestAction')->setMethods(['GET']); - $routes->add('/request', PsrRequestController::class.'::requestAction')->setMethods(['POST']); - $routes->add('/message', PsrRequestController::class.'::messageAction')->setMethods(['PUT']); - } - - protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void - { - $container->loadFromExtension('framework', [ - 'secret' => 'for your eyes only', - 'test' => true, - 'http_method_override' => false, - ]); - - $container->register('nyholm.psr_factory', Psr17Factory::class); - $container->setAlias(ResponseFactoryInterface::class, 'nyholm.psr_factory'); - $container->setAlias(ServerRequestFactoryInterface::class, 'nyholm.psr_factory'); - $container->setAlias(StreamFactoryInterface::class, 'nyholm.psr_factory'); - $container->setAlias(UploadedFileFactoryInterface::class, 'nyholm.psr_factory'); - - $container->register(HttpFoundationFactoryInterface::class, HttpFoundationFactory::class)->setAutowired(true)->setAutoconfigured(true); - $container->register(HttpMessageFactoryInterface::class, PsrHttpFactory::class)->setAutowired(true)->setAutoconfigured(true); - $container->register(PsrResponseListener::class)->setAutowired(true)->setAutoconfigured(true); - $container->register(PsrServerRequestResolver::class)->setAutowired(true)->setAutoconfigured(true); - - $container->register('logger', NullLogger::class); - $container->register(PsrRequestController::class)->setPublic(true)->setAutowired(true); - } -} diff --git a/Tests/Functional/ControllerTest.php b/Tests/Functional/ControllerTest.php index 0b88405..ab8e11f 100644 --- a/Tests/Functional/ControllerTest.php +++ b/Tests/Functional/ControllerTest.php @@ -12,9 +12,7 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Functional; use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App\Kernel; -use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\App\Kernel44; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -use Symfony\Component\HttpKernel\Kernel as SymfonyKernel; /** * @author Alexander M. Turek @@ -50,6 +48,6 @@ public function testMessageAction() protected static function getKernelClass(): string { - return SymfonyKernel::VERSION_ID >= 50200 ? Kernel::class : Kernel44::class; + return Kernel::class; } } diff --git a/composer.json b/composer.json index 0282307..07968ad 100644 --- a/composer.json +++ b/composer.json @@ -16,17 +16,17 @@ } ], "require": { - "php": ">=7.1", + "php": ">=7.2.5", "psr/http-message": "^1.0", - "symfony/http-foundation": "^4.4 || ^5.0 || ^6.0" + "symfony/http-foundation": "^5.4 || ^6.0" }, "require-dev": { - "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", - "symfony/config": "^4.4 || ^5.0 || ^6.0", - "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", - "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", - "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^5.4@dev || ^6.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", "psr/log": "^1.1 || ^2 || ^3" }, @@ -41,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-main": "2.1-dev" + "dev-main": "2.2-dev" } } } From ec83c1cc6ce8964da07019c182ed4ace30d5d03e Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 20 Apr 2023 09:23:18 -0500 Subject: [PATCH 82/91] Bump psr/http-message version --- Tests/Fixtures/Message.php | 10 +++++----- Tests/Fixtures/Response.php | 2 +- Tests/Fixtures/ServerRequest.php | 31 +++++++++++++++---------------- Tests/Fixtures/UploadedFile.php | 3 ++- Tests/Fixtures/Uri.php | 14 +++++++------- composer.json | 2 +- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Tests/Fixtures/Message.php b/Tests/Fixtures/Message.php index d561086..2f511ae 100644 --- a/Tests/Fixtures/Message.php +++ b/Tests/Fixtures/Message.php @@ -42,7 +42,7 @@ public function getProtocolVersion(): string * * @return static */ - public function withProtocolVersion($version) + public function withProtocolVersion($version): MessageInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -72,7 +72,7 @@ public function getHeaderLine($name): string * * @return static */ - public function withHeader($name, $value) + public function withHeader($name, $value): MessageInterface { $this->headers[$name] = (array) $value; @@ -84,7 +84,7 @@ public function withHeader($name, $value) * * @return static */ - public function withAddedHeader($name, $value) + public function withAddedHeader($name, $value): MessageInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -94,7 +94,7 @@ public function withAddedHeader($name, $value) * * @return static */ - public function withoutHeader($name) + public function withoutHeader($name): MessageInterface { unset($this->headers[$name]); @@ -111,7 +111,7 @@ public function getBody(): StreamInterface * * @return static */ - public function withBody(StreamInterface $body) + public function withBody(StreamInterface $body): MessageInterface { throw new \BadMethodCallException('Not implemented.'); } diff --git a/Tests/Fixtures/Response.php b/Tests/Fixtures/Response.php index 0bcf7f4..e7ec334 100644 --- a/Tests/Fixtures/Response.php +++ b/Tests/Fixtures/Response.php @@ -36,7 +36,7 @@ public function getStatusCode(): int /** * @return static */ - public function withStatus($code, $reasonPhrase = '') + public function withStatus($code, $reasonPhrase = ''): ResponseInterface { throw new \BadMethodCallException('Not implemented.'); } diff --git a/Tests/Fixtures/ServerRequest.php b/Tests/Fixtures/ServerRequest.php index b8df06a..50694cd 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; @@ -34,6 +35,10 @@ public function __construct($version = '1.1', array $headers = [], StreamInterfa { parent::__construct($version, $headers, $body); + if (!($uri instanceof UriInterface)) { + $uri = new Uri((string) $uri); + } + $this->requestTarget = $requestTarget; $this->method = $method; $this->uri = $uri; @@ -52,10 +57,8 @@ public function getRequestTarget(): string /** * {@inheritdoc} - * - * @return static */ - public function withRequestTarget($requestTarget) + public function withRequestTarget($requestTarget): RequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -67,20 +70,16 @@ public function getMethod(): string /** * {@inheritdoc} - * - * @return static */ - public function withMethod($method) + public function withMethod($method): RequestInterface { throw new \BadMethodCallException('Not implemented.'); } /** * {@inheritdoc} - * - * @return UriInterface */ - public function getUri() + public function getUri(): UriInterface { return $this->uri; } @@ -90,7 +89,7 @@ public function getUri() * * @return static */ - public function withUri(UriInterface $uri, $preserveHost = false) + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -110,7 +109,7 @@ public function getCookieParams(): array * * @return static */ - public function withCookieParams(array $cookies) + public function withCookieParams(array $cookies): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -125,7 +124,7 @@ public function getQueryParams(): array * * @return static */ - public function withQueryParams(array $query) + public function withQueryParams(array $query): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -140,7 +139,7 @@ public function getUploadedFiles(): array * * @return static */ - public function withUploadedFiles(array $uploadedFiles) + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -160,7 +159,7 @@ public function getParsedBody() * * @return static */ - public function withParsedBody($data) + public function withParsedBody($data): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -185,7 +184,7 @@ public function getAttribute($name, $default = null) * * @return static */ - public function withAttribute($name, $value) + public function withAttribute($name, $value): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -195,7 +194,7 @@ public function withAttribute($name, $value) * * @return static */ - public function withoutAttribute($name) + public function withoutAttribute($name): ServerRequestInterface { throw new \BadMethodCallException('Not implemented.'); } diff --git a/Tests/Fixtures/UploadedFile.php b/Tests/Fixtures/UploadedFile.php index 9004008..7b98057 100644 --- a/Tests/Fixtures/UploadedFile.php +++ b/Tests/Fixtures/UploadedFile.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\PsrHttpMessage\Tests\Fixtures; +use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; /** @@ -33,7 +34,7 @@ public function __construct($filePath, $size = null, $error = \UPLOAD_ERR_OK, $c $this->clientMediaType = $clientMediaType; } - public function getStream(): Stream + public function getStream(): StreamInterface { return new Stream(file_get_contents($this->filePath)); } diff --git a/Tests/Fixtures/Uri.php b/Tests/Fixtures/Uri.php index 48f513d..d03e032 100644 --- a/Tests/Fixtures/Uri.php +++ b/Tests/Fixtures/Uri.php @@ -98,7 +98,7 @@ public function getFragment(): string * * @return static */ - public function withScheme($scheme) + public function withScheme($scheme): UriInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -108,7 +108,7 @@ public function withScheme($scheme) * * @return static */ - public function withUserInfo($user, $password = null) + public function withUserInfo($user, $password = null): UriInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -118,7 +118,7 @@ public function withUserInfo($user, $password = null) * * @return static */ - public function withHost($host) + public function withHost($host): UriInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -128,7 +128,7 @@ public function withHost($host) * * @return static */ - public function withPort($port) + public function withPort($port): UriInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -138,7 +138,7 @@ public function withPort($port) * * @return static */ - public function withPath($path) + public function withPath($path): UriInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -148,7 +148,7 @@ public function withPath($path) * * @return static */ - public function withQuery($query) + public function withQuery($query): UriInterface { throw new \BadMethodCallException('Not implemented.'); } @@ -158,7 +158,7 @@ public function withQuery($query) * * @return static */ - public function withFragment($fragment) + public function withFragment($fragment): UriInterface { throw new \BadMethodCallException('Not implemented.'); } diff --git a/composer.json b/composer.json index 07968ad..e08b15c 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=7.2.5", - "psr/http-message": "^1.0", + "psr/http-message": "^1.0 || ^2.0", "symfony/http-foundation": "^5.4 || ^6.0" }, "require-dev": { From 99ddcaa7650127a0bb5116606aa3ee416b4d3a70 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 21 Apr 2023 10:33:52 +0200 Subject: [PATCH 83/91] Prepare the 2.2.0 release --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06f1435..1378fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ CHANGELOG ========= -# 2.2.0 (TBA) +# 2.2.0 (2023-04-21) * Drop support for Symfony 4 * Bump minimum version of PHP to 7.2 +* Support version 2 of the psr/http-message contracts # 2.1.3 (2022-09-05) From 79448316174d1307638b72976a81579df409e7ac Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Apr 2023 10:36:04 +0200 Subject: [PATCH 84/91] cs fix --- Tests/Fixtures/ServerRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Fixtures/ServerRequest.php b/Tests/Fixtures/ServerRequest.php index 50694cd..f6c85c1 100644 --- a/Tests/Fixtures/ServerRequest.php +++ b/Tests/Fixtures/ServerRequest.php @@ -35,7 +35,7 @@ public function __construct($version = '1.1', array $headers = [], StreamInterfa { parent::__construct($version, $headers, $body); - if (!($uri instanceof UriInterface)) { + if (!$uri instanceof UriInterface) { $uri = new Uri((string) $uri); } From 4fd4323581b0fca927fc720236d049a13db41425 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 25 Jul 2023 10:20:48 +0200 Subject: [PATCH 85/91] Add native types where possible --- Factory/HttpFoundationFactory.php | 4 ++++ Factory/PsrHttpFactory.php | 14 ++++++++------ Factory/UploadedFile.php | 2 +- Tests/EventListener/PsrResponseListenerTest.php | 3 +++ Tests/Factory/HttpFoundationFactoryTest.php | 2 +- Tests/Factory/PsrHttpFactoryTest.php | 5 ++++- Tests/Fixtures/Message.php | 2 +- Tests/Fixtures/Response.php | 2 +- Tests/Fixtures/ServerRequest.php | 6 +++++- Tests/Fixtures/Stream.php | 2 +- Tests/Fixtures/UploadedFile.php | 2 +- Tests/Functional/CovertTest.php | 10 +++++----- 12 files changed, 35 insertions(+), 19 deletions(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index 457e346..bbe8c27 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -41,6 +41,8 @@ public function __construct(int $responseBufferMaxLength = 16372) /** * {@inheritdoc} + * + * @return Request */ public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false) { @@ -121,6 +123,8 @@ protected function getTemporaryPath() /** * {@inheritdoc} + * + * @return Response */ public function createResponse(ResponseInterface $psrResponse, bool $streamed = false) { diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index b1b6f9a..ed4536a 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -12,7 +12,9 @@ namespace Symfony\Bridge\PsrHttpMessage\Factory; use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; +use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UploadedFileInterface; @@ -45,6 +47,8 @@ public function __construct(ServerRequestFactoryInterface $serverRequestFactory, /** * {@inheritdoc} + * + * @return ServerRequestInterface */ public function createRequest(Request $symfonyRequest) { @@ -84,10 +88,8 @@ public function createRequest(Request $symfonyRequest) /** * Converts Symfony uploaded files array to the PSR one. - * - * @return array */ - private function getFiles(array $uploadedFiles) + private function getFiles(array $uploadedFiles): array { $files = []; @@ -108,10 +110,8 @@ private function getFiles(array $uploadedFiles) /** * Creates a PSR-7 UploadedFile instance from a Symfony one. - * - * @return UploadedFileInterface */ - private function createUploadedFile(UploadedFile $symfonyUploadedFile) + private function createUploadedFile(UploadedFile $symfonyUploadedFile): UploadedFileInterface { return $this->uploadedFileFactory->createUploadedFile( $this->streamFactory->createStreamFromFile( @@ -126,6 +126,8 @@ private function createUploadedFile(UploadedFile $symfonyUploadedFile) /** * {@inheritdoc} + * + * @return ResponseInterface */ public function createResponse(Response $symfonyResponse) { diff --git a/Factory/UploadedFile.php b/Factory/UploadedFile.php index b951045..4d38a1f 100644 --- a/Factory/UploadedFile.php +++ b/Factory/UploadedFile.php @@ -52,7 +52,7 @@ public function __construct(UploadedFileInterface $psrUploadedFile, callable $ge /** * {@inheritdoc} */ - public function move($directory, $name = null): File + public function move(string $directory, string $name = null): File { if (!$this->isValid() || $this->test) { return parent::move($directory, $name); diff --git a/Tests/EventListener/PsrResponseListenerTest.php b/Tests/EventListener/PsrResponseListenerTest.php index 3f6060c..c700cca 100644 --- a/Tests/EventListener/PsrResponseListenerTest.php +++ b/Tests/EventListener/PsrResponseListenerTest.php @@ -46,6 +46,9 @@ public function testDoesNotConvertControllerResult() self::assertFalse($event->hasResponse()); } + /** + * @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 3a00e2f..cc389a5 100644 --- a/Tests/Factory/HttpFoundationFactoryTest.php +++ b/Tests/Factory/HttpFoundationFactoryTest.php @@ -196,7 +196,7 @@ public function testCreateUploadedFileWithError() $symfonyUploadedFile->move($this->tmpDir, 'shouldFail.txt'); } - private function createUploadedFile($content, $error, $clientFileName, $clientMediaType): UploadedFile + private function createUploadedFile(string $content, int $error, string $clientFileName, string $clientMediaType): UploadedFile { $filePath = tempnam($this->tmpDir, uniqid()); file_put_contents($filePath, $content); diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php index a1cd1b1..db37c15 100644 --- a/Tests/Factory/PsrHttpFactoryTest.php +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -28,7 +28,10 @@ */ class PsrHttpFactoryTest extends TestCase { + /** @var HttpMessageFactoryInterface */ private $factory; + + /** @var string */ private $tmpDir; protected function buildHttpMessageFactory(): HttpMessageFactoryInterface @@ -135,7 +138,7 @@ public function testGetContentCanBeCalledAfterRequestCreation() $this->assertSame('Content', $request->getContent()); } - private function createUploadedFile($content, $originalName, $mimeType, $error) + private function createUploadedFile(string $content, string $originalName, string $mimeType, int $error): UploadedFile { $path = tempnam($this->tmpDir, uniqid()); file_put_contents($path, $content); diff --git a/Tests/Fixtures/Message.php b/Tests/Fixtures/Message.php index 2f511ae..8fc18f7 100644 --- a/Tests/Fixtures/Message.php +++ b/Tests/Fixtures/Message.php @@ -25,7 +25,7 @@ class Message implements MessageInterface private $headers = []; private $body; - public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null) + public function __construct(string $version = '1.1', array $headers = [], StreamInterface $body = null) { $this->version = $version; $this->headers = $headers; diff --git a/Tests/Fixtures/Response.php b/Tests/Fixtures/Response.php index e7ec334..39a1b56 100644 --- a/Tests/Fixtures/Response.php +++ b/Tests/Fixtures/Response.php @@ -21,7 +21,7 @@ class Response extends Message implements ResponseInterface { private $statusCode; - public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $statusCode = 200) + public function __construct(string $version = '1.1', array $headers = [], StreamInterface $body = null, int $statusCode = 200) { parent::__construct($version, $headers, $body); diff --git a/Tests/Fixtures/ServerRequest.php b/Tests/Fixtures/ServerRequest.php index f6c85c1..8cfc59f 100644 --- a/Tests/Fixtures/ServerRequest.php +++ b/Tests/Fixtures/ServerRequest.php @@ -31,7 +31,11 @@ class ServerRequest extends Message implements ServerRequestInterface private $data; private $attributes; - public function __construct($version = '1.1', array $headers = [], StreamInterface $body = null, $requestTarget = '/', $method = 'GET', $uri = null, array $server = [], array $cookies = [], array $query = [], array $uploadedFiles = [], $data = null, array $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); diff --git a/Tests/Fixtures/Stream.php b/Tests/Fixtures/Stream.php index f664bae..34e243f 100644 --- a/Tests/Fixtures/Stream.php +++ b/Tests/Fixtures/Stream.php @@ -21,7 +21,7 @@ class Stream implements StreamInterface private $stringContent; private $eof = true; - public function __construct($stringContent = '') + public function __construct(string $stringContent = '') { $this->stringContent = $stringContent; } diff --git a/Tests/Fixtures/UploadedFile.php b/Tests/Fixtures/UploadedFile.php index 7b98057..92254fc 100644 --- a/Tests/Fixtures/UploadedFile.php +++ b/Tests/Fixtures/UploadedFile.php @@ -25,7 +25,7 @@ class UploadedFile implements UploadedFileInterface private $clientFileName; private $clientMediaType; - public function __construct($filePath, $size = null, $error = \UPLOAD_ERR_OK, $clientFileName = null, $clientMediaType = null) + 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; diff --git a/Tests/Functional/CovertTest.php b/Tests/Functional/CovertTest.php index cf82f82..25bbdc9 100644 --- a/Tests/Functional/CovertTest.php +++ b/Tests/Functional/CovertTest.php @@ -98,7 +98,7 @@ public function testConvertRequestMultipleTimes($request, $firstFactory, $second } } - public function requestProvider() + public static function requestProvider(): array { $sfRequest = new Request( [ @@ -120,8 +120,8 @@ public function requestProvider() 'c2' => ['c3' => 'bar'], ], [ - 'f1' => $this->createUploadedFile('F1', 'f1.txt', 'text/plain', \UPLOAD_ERR_OK), - 'foo' => ['f2' => $this->createUploadedFile('F2', 'f2.txt', 'text/plain', \UPLOAD_ERR_OK)], + 'f1' => self::createUploadedFile('F1', 'f1.txt', 'text/plain', \UPLOAD_ERR_OK), + 'foo' => ['f2' => self::createUploadedFile('F2', 'f2.txt', 'text/plain', \UPLOAD_ERR_OK)], ], [ 'REQUEST_METHOD' => 'POST', @@ -195,7 +195,7 @@ public function testConvertResponseMultipleTimes($response, $firstFactory, $seco } } - public function responseProvider() + public static function responseProvider(): array { $sfResponse = new Response( 'Response content.', @@ -227,7 +227,7 @@ public function responseProvider() ]; } - private function createUploadedFile($content, $originalName, $mimeType, $error) + private static function createUploadedFile(string $content, string $originalName, string $mimeType, int $error): UploadedFile { $path = tempnam(sys_get_temp_dir(), uniqid()); file_put_contents($path, $content); From 3a8caad62dfc301ad8c32adb778e859f6896c487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Pillevesse?= Date: Fri, 21 Jul 2023 14:42:44 +0200 Subject: [PATCH 86/91] Leverage `Request::getPayload()` to populate the parsed body of PSR-7 requests --- Factory/PsrHttpFactory.php | 20 +++++++++++- Tests/Factory/PsrHttpFactoryTest.php | 49 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index b1b6f9a..1c98e7b 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -18,6 +18,7 @@ use Psr\Http\Message\UploadedFileInterface; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Exception\JsonException; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -27,6 +28,7 @@ * Builds Psr\HttpMessage instances using a PSR-17 implementation. * * @author Antonio J. García Lagar + * @author Aurélien Pillevesse */ class PsrHttpFactory implements HttpMessageFactoryInterface { @@ -67,12 +69,28 @@ public function createRequest(Request $symfonyRequest) $body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true)); + if (method_exists(Request::class, 'getContentTypeFormat')) { + $format = $symfonyRequest->getContentTypeFormat(); + } else { + $format = $symfonyRequest->getContentType(); + } + + if (method_exists(Request::class, 'getPayload') && 'json' === $format) { + try { + $parsedBody = $symfonyRequest->getPayload()->all(); + } catch (JsonException $e) { + $parsedBody = []; + } + } else { + $parsedBody = $symfonyRequest->request->all(); + } + $request = $request ->withBody($body) ->withUploadedFiles($this->getFiles($symfonyRequest->files->all())) ->withCookieParams($symfonyRequest->cookies->all()) ->withQueryParams($symfonyRequest->query->all()) - ->withParsedBody($symfonyRequest->request->all()) + ->withParsedBody($parsedBody) ; foreach ($symfonyRequest->attributes->all() as $key => $value) { diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php index a1cd1b1..adc4368 100644 --- a/Tests/Factory/PsrHttpFactoryTest.php +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -25,6 +25,7 @@ /** * @author Kévin Dunglas * @author Antonio J. García Lagar + * @author Aurélien Pillevesse */ class PsrHttpFactoryTest extends TestCase { @@ -243,4 +244,52 @@ public function testUploadErrNoFile() $this->assertSame(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f1']->getError()); $this->assertSame(\UPLOAD_ERR_NO_FILE, $uploadedFiles['f2']->getError()); } + + 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 = $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 = $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 = $this->factory->createRequest($request); + + $this->assertSame([], $psrRequest->getParsedBody()); + } } From 0b54b85eb265a272222b780fbf37010bd4418ad2 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 25 Jul 2023 13:19:59 +0200 Subject: [PATCH 87/91] Implement ValueResolverInterface --- .../PsrServerRequestResolver.php | 11 +++++++- .../ValueResolverInterface.php | 26 +++++++++++++++++++ .../PsrServerRequestResolverTest.php | 20 ++++++++++++++ composer.json | 3 ++- 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 ArgumentValueResolver/ValueResolverInterface.php diff --git a/ArgumentValueResolver/PsrServerRequestResolver.php b/ArgumentValueResolver/PsrServerRequestResolver.php index 29dc7dc..61cd8c5 100644 --- a/ArgumentValueResolver/PsrServerRequestResolver.php +++ b/ArgumentValueResolver/PsrServerRequestResolver.php @@ -17,6 +17,7 @@ use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\Request; 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 ArgumentValueResolverInterface +final class PsrServerRequestResolver implements ArgumentValueResolverInterface, ValueResolverInterface { private const SUPPORTED_TYPES = [ ServerRequestInterface::class => true, @@ -45,6 +46,10 @@ public function __construct(HttpMessageFactoryInterface $httpMessageFactory) */ 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; } @@ -53,6 +58,10 @@ public function supports(Request $request, ArgumentMetadata $argument): bool */ public function resolve(Request $request, ArgumentMetadata $argument): \Traversable { + if (!isset(self::SUPPORTED_TYPES[$argument->getType()])) { + return; + } + yield $this->httpMessageFactory->createRequest($request); } } 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/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/composer.json b/composer.json index e08b15c..b705eb2 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "require": { "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": { @@ -41,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-main": "2.2-dev" + "dev-main": "2.3-dev" } } } From 96acbfdd7455b2e0e3fec074f3261559bc732887 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 25 Jul 2023 15:03:50 +0200 Subject: [PATCH 88/91] Prepare release 2.3.0 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1378fd6..e141756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +# 2.3.0 (2023-07-25) + +* Leverage `Request::getPayload()` to populate the parsed body of PSR-7 requests +* Implement `ValueResolverInterface` introduced with Symfony 6.2 + # 2.2.0 (2023-04-21) * Drop support for Symfony 4 From ef03b6dfb040da9615a11679306d707da7fee51f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 26 Jul 2023 09:26:19 +0200 Subject: [PATCH 89/91] Don't rely on Request::getPayload() to populate the parsed body --- Factory/PsrHttpFactory.php | 11 +++++------ Tests/Factory/PsrHttpFactoryTest.php | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Factory/PsrHttpFactory.php b/Factory/PsrHttpFactory.php index 6ad157f..09c4360 100644 --- a/Factory/PsrHttpFactory.php +++ b/Factory/PsrHttpFactory.php @@ -20,7 +20,6 @@ use Psr\Http\Message\UploadedFileInterface; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\BinaryFileResponse; -use Symfony\Component\HttpFoundation\Exception\JsonException; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -79,11 +78,11 @@ public function createRequest(Request $symfonyRequest) $format = $symfonyRequest->getContentType(); } - if (method_exists(Request::class, 'getPayload') && 'json' === $format) { - try { - $parsedBody = $symfonyRequest->getPayload()->all(); - } catch (JsonException $e) { - $parsedBody = []; + if ('json' === $format) { + $parsedBody = json_decode($symfonyRequest->getContent(), true, 512, \JSON_BIGINT_AS_STRING); + + if (!\is_array($parsedBody)) { + $parsedBody = null; } } else { $parsedBody = $symfonyRequest->request->all(); diff --git a/Tests/Factory/PsrHttpFactoryTest.php b/Tests/Factory/PsrHttpFactoryTest.php index a0a4a32..9d4c4c9 100644 --- a/Tests/Factory/PsrHttpFactoryTest.php +++ b/Tests/Factory/PsrHttpFactoryTest.php @@ -293,6 +293,6 @@ public function testWrongJsonContent() $request = new Request([], [], [], [], [], $headers, '{"city":"Paris"'); $psrRequest = $this->factory->createRequest($request); - $this->assertSame([], $psrRequest->getParsedBody()); + $this->assertNull($psrRequest->getParsedBody()); } } From 45d03491bc500839f63d6edc658c12392b610913 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 26 Jul 2023 13:52:03 +0200 Subject: [PATCH 90/91] Fix CS --- Factory/HttpFoundationFactory.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Factory/HttpFoundationFactory.php b/Factory/HttpFoundationFactory.php index bbe8c27..a69e8ff 100644 --- a/Factory/HttpFoundationFactory.php +++ b/Factory/HttpFoundationFactory.php @@ -221,13 +221,13 @@ private function createCookie(string $cookie): Cookie return new Cookie( $cookieName, $cookieValue, - isset($cookieExpire) ? $cookieExpire : 0, - isset($cookiePath) ? $cookiePath : '/', - isset($cookieDomain) ? $cookieDomain : null, + $cookieExpire ?? 0, + $cookiePath ?? '/', + $cookieDomain ?? null, isset($cookieSecure), isset($cookieHttpOnly), true, - isset($samesite) ? $samesite : null + $samesite ?? null ); } From 581ca6067eb62640de5ff08ee1ba6850a0ee472e Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 26 Jul 2023 13:53:26 +0200 Subject: [PATCH 91/91] Prepare release 2.3.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e141756..f32c06f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG ========= +# 2.3.1 (2023-07-26) + +* Don't rely on `Request::getPayload()` to populate the parsed body + # 2.3.0 (2023-07-25) * Leverage `Request::getPayload()` to populate the parsed body of PSR-7 requests