From a5ba088c1752cc0a80cc951b04a17e4e077b8cd9 Mon Sep 17 00:00:00 2001 From: Christoph Kappestein Date: Sat, 6 Jun 2015 23:17:03 +0200 Subject: [PATCH] add psr7 factory and use psr7 stream and uri interface --- LICENSE | 176 ++++++----- composer.json | 10 +- .../Factory.php => Factory/NativeFactory.php} | 53 +--- library/PSX/Http/Factory/Psr7Factory.php | 60 ++++ library/PSX/Http/Message.php | 2 +- library/PSX/Http/MessageInterface.php | 4 +- library/PSX/Http/Psr/FactoryInterface.php | 86 ------ library/PSX/Http/RequestInterface.php | 2 +- library/PSX/Http/ResponseInterface.php | 2 +- library/PSX/Http/StreamInterface.php | 156 +--------- library/PSX/Uri.php | 89 +++++- tests/PSX/Http/Factory/NativeFactoryTest.php | 78 +++++ tests/PSX/Http/Factory/Psr7FactoryTest.php | 83 ++++++ tests/PSX/Http/Psr/FactoryTest.php | 280 ------------------ tests/PSX/UriTest.php | 60 ++++ 15 files changed, 501 insertions(+), 640 deletions(-) rename library/PSX/Http/{Psr/Factory.php => Factory/NativeFactory.php} (56%) create mode 100644 library/PSX/Http/Factory/Psr7Factory.php delete mode 100644 library/PSX/Http/Psr/FactoryInterface.php create mode 100644 tests/PSX/Http/Factory/NativeFactoryTest.php create mode 100644 tests/PSX/Http/Factory/Psr7FactoryTest.php delete mode 100644 tests/PSX/Http/Psr/FactoryTest.php diff --git a/LICENSE b/LICENSE index 824ea131..f3eb0a09 100644 --- a/LICENSE +++ b/LICENSE @@ -183,78 +183,6 @@ PSX includes a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the following licenses. -################################################################################ -- psr/log - -Copyright (c) 2013 PHP Framework Interop Group - -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. - -################################################################################ -- monolog/monolog - -Copyright (c) 2011-2014 Jordi Boggiano - -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. - -################################################################################ -- symfony/console -- symfony/dependency-injection -- symfony/event-dispatcher -- symfony/yaml - -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 -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. - ################################################################################ - doctrine/annotations - doctrine/dbal @@ -484,3 +412,107 @@ SOFTWARE. See the License for the specific language governing permissions and limitations under the License. +################################################################################ +- monolog/monolog + +Copyright (c) 2011-2014 Jordi Boggiano + +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. + +################################################################################ +- psr/http-message +- psr/log + +Copyright (c) 2013 PHP Framework Interop Group + +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. + +################################################################################ +- symfony/console +- symfony/dependency-injection +- symfony/event-dispatcher +- symfony/yaml + +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 +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. + +################################################################################ +- zendframework/zend-diactoros + +Copyright (c) 2015, Zend Technologies USA, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +- Neither the name of Zend Technologies USA, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/composer.json b/composer.json index 43a908b8..e3c492f6 100644 --- a/composer.json +++ b/composer.json @@ -13,15 +13,17 @@ ], "require": { "php": ">=5.4", - "psr/log": "~1.0", + "doctrine/annotations": "~1.2", + "doctrine/dbal": "~2.5", + "jms/serializer": "0.16.0", "monolog/monolog": "~1.0", + "psr/http-message": "~1.0", + "psr/log": "~1.0", "symfony/console": "~2.6", "symfony/dependency-injection": "~2.6", "symfony/event-dispatcher": "~2.6", "symfony/yaml": "~2.6", - "doctrine/annotations": "~1.2", - "doctrine/dbal": "~2.5", - "jms/serializer": "0.16.0" + "zendframework/zend-diactoros": "~1.0" }, "require-dev": { "phpunit/phpunit": "~4.1", diff --git a/library/PSX/Http/Psr/Factory.php b/library/PSX/Http/Factory/NativeFactory.php similarity index 56% rename from library/PSX/Http/Psr/Factory.php rename to library/PSX/Http/Factory/NativeFactory.php index 1b1ba0ed..a52d0eea 100644 --- a/library/PSX/Http/Psr/Factory.php +++ b/library/PSX/Http/Factory/NativeFactory.php @@ -18,61 +18,24 @@ * limitations under the License. */ -namespace PSX\Http\Psr; +namespace PSX\Http\Factory; -use Psr\Http\Message\RequestInterface as PsrRequestInterface; use Psr\Http\Message\ResponseInterface as PsrResponseInterface; use Psr\Http\Message\ServerRequestInterface as PsrServerRequestInterface; use PSX\Http\Request; -use PSX\Http\RequestInterface; use PSX\Http\Response; -use PSX\Http\ResponseInterface; use PSX\Uri; /** - * Factory + * NativeFactory * * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 * @link http://phpsx.org */ -class Factory implements FactoryInterface +class NativeFactory { - public function getPsrRequest(RequestInterface $request) - { - // @TODO wait until the PSR gets accepted and use an open-source - // implementation - - return null; - } - - public function getPsrServerRequest(RequestInterface $request) - { - // @TODO wait until the PSR gets accepted and use an open-source - // implementation - - return null; - } - - public function getPsrResponse(ResponseInterface $response) - { - // @TODO wait until the PSR gets accepted and use an open-source - // implementation - - return null; - } - - public function getNativeRequest(PsrRequestInterface $psrRequest) - { - return new Request( - new Uri($psrRequest->getUri()), - $psrRequest->getMethod(), - $psrRequest->getHeaders(), - $psrRequest->getBody() - ); - } - - public function getNativeServerRequest(PsrServerRequestInterface $psrRequest) + public static function createRequest(PsrServerRequestInterface $psrRequest) { $request = new Request( new Uri($psrRequest->getUri()), @@ -81,10 +44,16 @@ public function getNativeServerRequest(PsrServerRequestInterface $psrRequest) $psrRequest->getBody() ); + $attributes = $psrRequest->getAttributes(); + foreach($attributes as $name => $value) + { + $request->setAttribute($name, $value); + } + return $request; } - public function getNativeResponse(PsrResponseInterface $psrResponse) + public static function createResponse(PsrResponseInterface $psrResponse) { return new Response( $psrResponse->getStatusCode(), diff --git a/library/PSX/Http/Factory/Psr7Factory.php b/library/PSX/Http/Factory/Psr7Factory.php new file mode 100644 index 00000000..7057a557 --- /dev/null +++ b/library/PSX/Http/Factory/Psr7Factory.php @@ -0,0 +1,60 @@ + + * + * Copyright 2010-2015 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Http\Factory; + +use Zend\Diactoros\Response as PsrResponse; +use Zend\Diactoros\ServerRequestFactory; +use PSX\Http\RequestInterface; +use PSX\Http\ResponseInterface; + +/** + * Psr7Factory + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link http://phpsx.org + */ +class Psr7Factory +{ + public static function createRequest(RequestInterface $request) + { + $psrRequest = ServerRequestFactory::fromGlobals() + ->withUri($request->getUri()) + ->withMethod($request->getMethod()) + ->withBody($request->getBody()); + + foreach($request->getHeaders() as $name => $values) + { + $psrRequest = $psrRequest->withHeader($name, $values); + } + + return $psrRequest; + } + + public static function createResponse(ResponseInterface $response) + { + return new PsrResponse( + $response->getBody(), + $response->getStatusCode(), + $response->getHeaders() + ); + } +} diff --git a/library/PSX/Http/Message.php b/library/PSX/Http/Message.php index 3e39da45..f39a178f 100644 --- a/library/PSX/Http/Message.php +++ b/library/PSX/Http/Message.php @@ -21,8 +21,8 @@ namespace PSX\Http; use InvalidArgumentException; +use Psr\Http\Message\StreamInterface; use PSX\Http\Stream; -use PSX\Http\StreamInterface; /** * Message diff --git a/library/PSX/Http/MessageInterface.php b/library/PSX/Http/MessageInterface.php index e213df96..492f928f 100644 --- a/library/PSX/Http/MessageInterface.php +++ b/library/PSX/Http/MessageInterface.php @@ -20,6 +20,8 @@ namespace PSX\Http; +use Psr\Http\Message\StreamInterface; + /** * This is a mutable version of the PSR HTTP message interface * @@ -32,7 +34,7 @@ * @link http://phpsx.org * @link http://www.ietf.org/rfc/rfc7230.txt * @link http://www.ietf.org/rfc/rfc7231.txt - * @see https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md + * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md */ interface MessageInterface { diff --git a/library/PSX/Http/Psr/FactoryInterface.php b/library/PSX/Http/Psr/FactoryInterface.php deleted file mode 100644 index 261ebd88..00000000 --- a/library/PSX/Http/Psr/FactoryInterface.php +++ /dev/null @@ -1,86 +0,0 @@ - - * - * Copyright 2010-2015 Christoph Kappestein - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace PSX\Http\Psr; - -use Psr\Http\Message\RequestInterface as PsrRequestInterface; -use Psr\Http\Message\ResponseInterface as PsrResponseInterface; -use Psr\Http\Message\ServerRequestInterface as PsrServerRequestInterface; -use PSX\Http\RequestInterface; -use PSX\Http\ResponseInterface; - -/** - * FactoryInterface - * - * @author Christoph Kappestein - * @license http://www.apache.org/licenses/LICENSE-2.0 - * @link http://phpsx.org - */ -interface FactoryInterface -{ - /** - * Converts an PSX request into an PSR request - * - * @param PSX\Http\RequestInterface - * @return Psr\Http\Message\RequestInterface - */ - public function getPsrRequest(RequestInterface $request); - - /** - * Converts an PSX request into an PSR server request - * - * @param PSX\Http\RequestInterface - * @return Psr\Http\Message\ServerRequestInterface - */ - public function getPsrServerRequest(RequestInterface $request); - - /** - * Converts an PSX response into an PSR response - * - * @param PSX\Http\ResponseInterface - * @return Psr\Http\Message\ResponseInterface - */ - public function getPsrResponse(ResponseInterface $response); - - /** - * Converts an PSR request into an PSX request - * - * @param Psr\Http\Message\RequestInterface - * @return PSX\Http\RequestInterface - */ - public function getNativeRequest(PsrRequestInterface $psrRequest); - - /** - * Converts an PSR server request into an PSX server request - * - * @param Psr\Http\Message\ServerRequestInterface - * @return PSX\Http\RequestInterface - */ - public function getNativeServerRequest(PsrServerRequestInterface $psrRequest); - - /** - * Converts an PSR response into an PSX response - * - * @param Psr\Http\Message\ResponseInterface - * @return PSX\Http\ResponseInterface - */ - public function getNativeResponse(PsrResponseInterface $psrResponse); -} - diff --git a/library/PSX/Http/RequestInterface.php b/library/PSX/Http/RequestInterface.php index 6b55be5e..114a722f 100644 --- a/library/PSX/Http/RequestInterface.php +++ b/library/PSX/Http/RequestInterface.php @@ -37,7 +37,7 @@ * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 * @link http://phpsx.org - * @see https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md + * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md */ interface RequestInterface extends MessageInterface { diff --git a/library/PSX/Http/ResponseInterface.php b/library/PSX/Http/ResponseInterface.php index 00af461f..3fde9051 100644 --- a/library/PSX/Http/ResponseInterface.php +++ b/library/PSX/Http/ResponseInterface.php @@ -34,7 +34,7 @@ * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 * @link http://phpsx.org - * @see https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md + * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md */ interface ResponseInterface extends MessageInterface { diff --git a/library/PSX/Http/StreamInterface.php b/library/PSX/Http/StreamInterface.php index 79d6782b..d4234252 100644 --- a/library/PSX/Http/StreamInterface.php +++ b/library/PSX/Http/StreamInterface.php @@ -20,162 +20,16 @@ namespace PSX\Http; +use Psr\Http\Message\StreamInterface as PsrStreamInterface; + /** - * This is a mutable version of the PSR HTTP message interface - * - * Typically, an instance will wrap a PHP stream; this interface provides - * a wrapper around the most common operations, including serialization of - * the entire stream to a string. + * StreamInterface * * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 * @link http://phpsx.org - * @see https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md + * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md */ -interface StreamInterface +interface StreamInterface extends PsrStreamInterface { - /** - * Reads all data from the stream into a string, from the beginning to end. - * - * This method MUST attempt to seek to the beginning of the stream before - * reading data and read the stream until the end is reached. - * - * Warning: This could attempt to load a large amount of data into memory. - * - * This method MUST NOT raise an exception in order to conform with PHP's - * string casting operations. - * - * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring - * @return string - */ - public function __toString(); - - /** - * Closes the stream and any underlying resources. - * - * @return void - */ - public function close(); - - /** - * Separates any underlying resources from the stream. - * - * After the stream has been detached, the stream is in an unusable state. - * - * @return resource|null Underlying PHP stream, if any - */ - public function detach(); - - /** - * Get the size of the stream if known - * - * @return int|null Returns the size in bytes if known, or null if unknown. - */ - public function getSize(); - - /** - * Returns the current position of the file read/write pointer - * - * @return int Position of the file pointer - * @throws \RuntimeException on error. - */ - public function tell(); - - /** - * Returns true if the stream is at the end of the stream. - * - * @return bool - */ - public function eof(); - - /** - * Returns whether or not the stream is seekable. - * - * @return bool - */ - public function isSeekable(); - - /** - * Seek to a position in the stream. - * - * @link http://www.php.net/manual/en/function.fseek.php - * @param int $offset Stream offset - * @param int $whence Specifies how the cursor position will be calculated - * based on the seek offset. Valid values are identical to the built-in - * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to - * offset bytes SEEK_CUR: Set position to current location plus offset - * SEEK_END: Set position to end-of-stream plus offset. - * @throws \RuntimeException on failure. - */ - public function seek($offset, $whence = SEEK_SET); - - /** - * Seek to the beginning of the stream. - * - * If the stream is not seekable, this method will raise an exception; - * otherwise, it will perform a seek(0). - * - * @see seek() - * @link http://www.php.net/manual/en/function.fseek.php - * @throws \RuntimeException on failure. - */ - public function rewind(); - - /** - * Returns whether or not the stream is writable. - * - * @return bool - */ - public function isWritable(); - - /** - * Write data to the stream. - * - * @param string $string The string that is to be written. - * @return int Returns the number of bytes written to the stream. - * @throws \RuntimeException on failure. - */ - public function write($string); - - /** - * Returns whether or not the stream is readable. - * - * @return bool - */ - public function isReadable(); - - /** - * Read data from the stream. - * - * @param int $length Read up to $length bytes from the object and return - * them. Fewer than $length bytes may be returned if underlying stream - * call returns fewer bytes. - * @return string Returns the data read from the stream, or an empty string - * if no bytes are available. - * @throws \RuntimeException if an error occurs. - */ - public function read($length); - - /** - * Returns the remaining contents in a string - * - * @return string - * @throws \RuntimeException if unable to read or an error occurs while - * reading. - */ - public function getContents(); - - /** - * Get stream metadata as an associative array or retrieve a specific key. - * - * The keys returned are identical to the keys returned from PHP's - * stream_get_meta_data() function. - * - * @link http://php.net/manual/en/function.stream-get-meta-data.php - * @param string $key Specific metadata to retrieve. - * @return array|mixed|null Returns an associative array if no key is - * provided. Returns a specific key value if a key is provided and the - * value is found, or null if the key is not found. - */ - public function getMetadata($key = null); } diff --git a/library/PSX/Uri.php b/library/PSX/Uri.php index 8981d7a7..fbf4fddf 100644 --- a/library/PSX/Uri.php +++ b/library/PSX/Uri.php @@ -20,6 +20,8 @@ namespace PSX; +use Psr\Http\Message\UriInterface; + /** * Represents an URI. Provides getters to retrieve parts of the URI. The class * tries to parse the given string into the URI specific components: @@ -34,7 +36,7 @@ * @link http://phpsx.org * @see http://www.ietf.org/rfc/rfc3986.txt */ -class Uri +class Uri implements UriInterface { protected $scheme; protected $authority; @@ -156,6 +158,91 @@ public function withAuthority($authority) ); } + public function withUserInfo($user, $password = null) + { + if(!empty($user)) + { + $userInfo = $user . ($password !== null ? ':' . $password : ''); + $authority = $userInfo . '@' . $this->host; + } + else + { + $authority = $this->host; + } + + if(!empty($this->port)) + { + $authority.= ':' . $this->port; + } + + return new static( + $this->scheme, + $authority, + $this->path, + $this->query, + $this->fragment + ); + } + + public function withHost($host) + { + if(!empty($host)) + { + $userInfo = $this->getUserInfo(); + if(!empty($userInfo)) + { + $authority = $userInfo . '@' . $host; + } + else + { + $authority = $host; + } + + if(!empty($this->port)) + { + $authority.= ':' . $this->port; + } + } + else + { + $authority = null; + } + + return new static( + $this->scheme, + $authority, + $this->path, + $this->query, + $this->fragment + ); + } + + public function withPort($port) + { + $userInfo = $this->getUserInfo(); + if(!empty($userInfo)) + { + $authority = $userInfo . '@' . $this->host; + } + else + { + $authority = $this->host; + } + + if(!empty($port)) + { + $authority.= ':' . $port; + } + + return new static( + $this->scheme, + $authority, + $this->path, + $this->query, + $this->fragment + ); + } + public function withPath($path) { return new static( diff --git a/tests/PSX/Http/Factory/NativeFactoryTest.php b/tests/PSX/Http/Factory/NativeFactoryTest.php new file mode 100644 index 00000000..3652c05d --- /dev/null +++ b/tests/PSX/Http/Factory/NativeFactoryTest.php @@ -0,0 +1,78 @@ + + * + * Copyright 2010-2015 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Http\Factory; + +use Zend\Diactoros\Response; +use Zend\Diactoros\ServerRequestFactory; +use Zend\Diactoros\Stream; +use Zend\Diactoros\Uri; + +/** + * RequestFactoryTest + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link http://phpsx.org + */ +class RequestFactoryTest extends \PHPUnit_Framework_TestCase +{ + public function testCreateRequest() + { + $psrRequest = ServerRequestFactory::fromGlobals() + ->withUri(new Uri('http://localhost.com/foo?bar=foo')) + ->withMethod('GET') + ->withBody(new Stream('php://memory', 'r+')) + ->withAddedHeader('User-Agent', 'foo'); + + $psrRequest->getBody()->write('foobar'); + + $request = NativeFactory::createRequest($psrRequest); + + $this->assertInstanceOf('PSX\Http\RequestInterface', $request); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('foo', $request->getHeader('User-Agent')); + $this->assertInstanceOf('PSX\Uri', $request->getUri()); + $this->assertEquals('localhost.com', $request->getUri()->getHost()); + $this->assertEquals('/foo', $request->getUri()->getPath()); + $this->assertEquals('bar=foo', $request->getUri()->getQuery()); + $this->assertEquals(['bar' => 'foo'], $request->getUri()->getParameters()); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $request->getBody()); + $this->assertEquals('foobar', (string) $request->getBody()); + } + + public function testCreateResponse() + { + $psrResponse = new Response(); + $psrResponse = $psrResponse->withStatus(200) + ->withHeader('Content-Type', 'text/plain') + ->withBody(new Stream('php://memory', 'r+')); + + $psrResponse->getBody()->write('foobar'); + + $response = NativeFactory::createResponse($psrResponse); + + $this->assertInstanceOf('PSX\Http\ResponseInterface', $response); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('text/plain', $response->getHeader('Content-Type')); + $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $response->getBody()); + $this->assertEquals('foobar', (string) $response->getBody()); + } +} diff --git a/tests/PSX/Http/Factory/Psr7FactoryTest.php b/tests/PSX/Http/Factory/Psr7FactoryTest.php new file mode 100644 index 00000000..d66667b4 --- /dev/null +++ b/tests/PSX/Http/Factory/Psr7FactoryTest.php @@ -0,0 +1,83 @@ + + * + * Copyright 2010-2015 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace PSX\Http\Factory; + +use PSX\Http\Request; +use PSX\Http\RequestInterface; +use PSX\Http\Response; +use PSX\Http\Stream\StringStream; +use PSX\Url; + +/** + * Psr7FactoryTest + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link http://phpsx.org + */ +class Psr7FactoryTest extends \PHPUnit_Framework_TestCase +{ + public function testCreateRequest() + { + $request = new Request( + new Url('http://localhost.com/foo?bar=foo'), + 'GET', + ['User-Agent' => 'foo'], + new StringStream('foobar') + ); + + $psrRequest = Psr7Factory::createRequest($request); + + $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $psrRequest); + $this->assertEquals('GET', $psrRequest->getMethod()); + $this->assertEquals(['foo'], $psrRequest->getHeader('User-Agent')); + $this->assertEquals('foo', $psrRequest->getHeaderLine('User-Agent')); + $this->assertInstanceOf('Psr\Http\Message\UriInterface', $psrRequest->getUri()); + $this->assertEquals('localhost.com', $psrRequest->getUri()->getHost()); + $this->assertEquals('/foo', $psrRequest->getUri()->getPath()); + $this->assertEquals('bar=foo', $psrRequest->getUri()->getQuery()); + $this->assertInstanceOf('PSX\Http\StreamInterface', $psrRequest->getBody()); + $this->assertEquals('foobar', (string) $psrRequest->getBody()); + $this->assertTrue(is_array($psrRequest->getParsedBody())); + $this->assertTrue(is_array($psrRequest->getCookieParams())); + $this->assertTrue(is_array($psrRequest->getUploadedFiles())); + $this->assertTrue(is_array($psrRequest->getQueryParams())); + $this->assertTrue(is_array($psrRequest->getServerParams())); + } + + public function testCreateResponse() + { + $response = new Response( + 200, + array('Content-Type' => 'text/plain'), + new StringStream('foobar') + ); + + $psrResponse = Psr7Factory::createResponse($response); + + $this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $psrResponse); + $this->assertEquals(200, $psrResponse->getStatusCode()); + $this->assertEquals(['text/plain'], $psrResponse->getHeader('Content-Type')); + $this->assertEquals('text/plain', $psrResponse->getHeaderLine('Content-Type')); + $this->assertInstanceOf('PSX\Http\StreamInterface', $psrResponse->getBody()); + $this->assertEquals('foobar', (string) $psrResponse->getBody()); + } +} diff --git a/tests/PSX/Http/Psr/FactoryTest.php b/tests/PSX/Http/Psr/FactoryTest.php deleted file mode 100644 index 3ef40a75..00000000 --- a/tests/PSX/Http/Psr/FactoryTest.php +++ /dev/null @@ -1,280 +0,0 @@ - - * - * Copyright 2010-2015 Christoph Kappestein - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace PSX\Http\Psr; - -use PSX\Http\Request; -use PSX\Http\RequestInterface; -use PSX\Http\Response; -use PSX\Http\Stream\StringStream; -use PSX\Url; - -/** - * RequestFactoryTest - * - * @author Christoph Kappestein - * @license http://www.apache.org/licenses/LICENSE-2.0 - * @link http://phpsx.org - */ -class RequestFactoryTest extends \PHPUnit_Framework_TestCase -{ - public function testGetPsrRequest() - { - $request = new Request( - new Url('http://localhost.com/foo?bar=foo'), - 'GET', - array('User-Agent' => 'foo'), - new StringStream('foobar') - ); - - $factory = new Factory(); - $psrRequest = $factory->getPsrRequest($request); - - $this->assertEmpty($psrRequest); - - /* - $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $psrRequest); - $this->assertEquals('GET', $psrRequest->getMethod()); - $this->assertEquals('foo', $psrRequest->getHeader('User-Agent')); - $this->assertInstanceOf('Psr\Http\Message\UriInterface', $psrRequest->getUri()); - $this->assertEquals('localhost.com', $psrRequest->getUri()->getHost()); - $this->assertEquals('/foo', $psrRequest->getUri()->getPath()); - $this->assertEquals('bar=foo', $psrRequest->getUri()->getQuery()); - $this->assertInstanceOf('PSX\Http\StreamInterface', $psrRequest->getBody()); - $this->assertEquals('foobar', (string) $psrRequest->getBody()); - */ - } - - public function testGetPsrServerRequest() - { - $request = new Request( - new Url('http://localhost.com/foo?bar=foo'), - 'GET', - ['User-Agent' => 'foo'], - new StringStream('foobar') - ); - - $factory = new Factory(); - $psrRequest = $factory->getPsrServerRequest($request); - - $this->assertEmpty($psrRequest); - - /* - $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $psrRequest); - $this->assertEquals('GET', $psrRequest->getMethod()); - $this->assertEquals('foo', $psrRequest->getHeader('User-Agent')); - $this->assertInstanceOf('Psr\Http\Message\UriInterface', $psrRequest->getUri()); - $this->assertEquals('localhost.com', $psrRequest->getUri()->getHost()); - $this->assertEquals('/foo', $psrRequest->getUri()->getPath()); - $this->assertEquals('bar=foo', $psrRequest->getUri()->getQuery()); - $this->assertInstanceOf('PSX\Http\StreamInterface', $psrRequest->getBody()); - $this->assertEquals('foobar', (string) $psrRequest->getBody()); - $this->assertEquals(['body' => 'foo'], $psrRequest->getParsedBody()); - $this->assertEquals(['cookie' => 'foo'], $psrRequest->getCookieParams()); - $this->assertEquals(['file' => 'foo'], $psrRequest->getFileParams()); - $this->assertEquals(['query' => 'foo'], $psrRequest->getQueryParams()); - $this->assertEquals(['server' => 'foo'], $psrRequest->getServerParams()); - */ - } - - public function testGetPsrResponse() - { - $response = new Response( - 200, - array('Content-Type' => 'text/plain'), - new StringStream('foobar') - ); - - $factory = new Factory(); - $psrResponse = $factory->getPsrResponse($response); - - $this->assertEmpty($psrResponse); - - /* - $this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $psrResponse); - $this->assertEquals(200, $psrResponse->getStatusCode()); - $this->assertEquals('text/plain', $psrResponse->getHeader('Content-Type')); - $this->assertInstanceOf('PSX\Http\StreamInterface', $psrResponse->getBody()); - $this->assertEquals('foobar', (string) $psrResponse->getBody()); - */ - } - - public function testGetNativeRequest() - { - $psrRequest = $this->getMock('Psr\Http\Message\RequestInterface', array( - 'getProtocolVersion', - 'withProtocolVersion', - 'getHeaders', - 'hasHeader', - 'getHeader', - 'getHeaderLines', - 'withHeader', - 'withAddedHeader', - 'withoutHeader', - 'getBody', - 'withBody', - 'getRequestTarget', - 'withRequestTarget', - 'getMethod', - 'withMethod', - 'getUri', - 'withUri' - )); - - $psrRequest->method('getHeaders') - ->willReturn(array('User-Agent' => 'foo')); - - $psrRequest->method('getMethod') - ->willReturn('GET'); - - $psrRequest->method('getUri') - ->willReturn('http://localhost.com/foo?bar=foo'); - - $psrRequest->method('getBody') - ->willReturn(new StringStream('foobar')); - - $factory = new Factory(); - $request = $factory->getNativeRequest($psrRequest); - - $this->assertInstanceOf('PSX\Http\RequestInterface', $request); - $this->assertEquals('GET', $request->getMethod()); - $this->assertEquals('foo', $request->getHeader('User-Agent')); - $this->assertInstanceOf('PSX\Uri', $request->getUri()); - $this->assertEquals('localhost.com', $request->getUri()->getHost()); - $this->assertEquals('/foo', $request->getUri()->getPath()); - $this->assertEquals('bar=foo', $request->getUri()->getQuery()); - $this->assertInstanceOf('PSX\Http\StreamInterface', $request->getBody()); - $this->assertEquals('foobar', (string) $request->getBody()); - } - - public function testGetNativeServerRequest() - { - $psrRequest = $this->getMock('Psr\Http\Message\ServerRequestInterface', array( - 'getProtocolVersion', - 'withProtocolVersion', - 'getHeaders', - 'hasHeader', - 'getHeader', - 'getHeaderLines', - 'withHeader', - 'withAddedHeader', - 'withoutHeader', - 'getBody', - 'withBody', - 'getRequestTarget', - 'withRequestTarget', - 'getMethod', - 'withMethod', - 'getUri', - 'withUri', - 'getServerParams', - 'getCookieParams', - 'withCookieParams', - 'getQueryParams', - 'withQueryParams', - 'getFileParams', - 'getParsedBody', - 'withParsedBody', - 'getAttributes', - 'getAttribute', - 'withAttribute', - 'withoutAttribute', - )); - - $psrRequest->method('getHeaders') - ->willReturn(array('User-Agent' => 'foo')); - - $psrRequest->method('getMethod') - ->willReturn('GET'); - - $psrRequest->method('getUri') - ->willReturn('http://localhost.com/foo?bar=foo'); - - $psrRequest->method('getBody') - ->willReturn(new StringStream('foobar')); - - $psrRequest->method('getParsedBody') - ->willReturn(['body' => 'foo']); - - $psrRequest->method('getCookieParams') - ->willReturn(['cookie' => 'foo']); - - $psrRequest->method('getFileParams') - ->willReturn(['file' => 'foo']); - - $psrRequest->method('getQueryParams') - ->willReturn(['query' => 'foo']); - - $psrRequest->method('getServerParams') - ->willReturn(['server' => 'foo']); - - $factory = new Factory(); - $request = $factory->getNativeServerRequest($psrRequest); - - $this->assertInstanceOf('PSX\Http\RequestInterface', $request); - $this->assertEquals('GET', $request->getMethod()); - $this->assertEquals('foo', $request->getHeader('User-Agent')); - $this->assertInstanceOf('PSX\Uri', $request->getUri()); - $this->assertEquals('localhost.com', $request->getUri()->getHost()); - $this->assertEquals('/foo', $request->getUri()->getPath()); - $this->assertEquals('bar=foo', $request->getUri()->getQuery()); - $this->assertEquals(['bar' => 'foo'], $request->getUri()->getParameters()); - $this->assertInstanceOf('PSX\Http\StreamInterface', $request->getBody()); - $this->assertEquals('foobar', (string) $request->getBody()); - } - - public function testGetNativeResponse() - { - $psrResponse = $this->getMock('Psr\Http\Message\ResponseInterface', array( - 'getProtocolVersion', - 'withProtocolVersion', - 'getHeaders', - 'hasHeader', - 'getHeader', - 'getHeaderLines', - 'withHeader', - 'withAddedHeader', - 'withoutHeader', - 'getBody', - 'withBody', - 'getStatusCode', - 'withStatus', - 'getReasonPhrase', - )); - - $psrResponse->method('getHeaders') - ->willReturn(array('Content-Type' => 'text/plain')); - - $psrResponse->method('getStatusCode') - ->willReturn(200); - - $psrResponse->method('getBody') - ->willReturn(new StringStream('foobar')); - - $factory = new Factory(); - $response = $factory->getNativeResponse($psrResponse); - - $this->assertInstanceOf('PSX\Http\ResponseInterface', $response); - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals('text/plain', $response->getHeader('Content-Type')); - $this->assertInstanceOf('PSX\Http\StreamInterface', $response->getBody()); - $this->assertEquals('foobar', (string) $response->getBody()); - } -} diff --git a/tests/PSX/UriTest.php b/tests/PSX/UriTest.php index 11c0ea12..3f1d286f 100644 --- a/tests/PSX/UriTest.php +++ b/tests/PSX/UriTest.php @@ -424,6 +424,10 @@ public function testWithScheme() $uri = new Uri('http://www.yahoo.com'); $this->assertEquals('https://www.yahoo.com', $uri->withScheme('https')->toString()); + + $uri = new Uri('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose'); + + $this->assertEquals('https://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose', $uri->withScheme('https')->toString()); } public function testWithAuthority() @@ -431,6 +435,10 @@ public function testWithAuthority() $uri = new Uri('http://www.yahoo.com'); $this->assertEquals('http://google.com', $uri->withAuthority('google.com')->toString()); + + $uri = new Uri('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose'); + + $this->assertEquals('http://google.com/over/there?name=ferret&foo=bar#nose', $uri->withAuthority('google.com')->toString()); } public function testWithPath() @@ -438,6 +446,10 @@ public function testWithPath() $uri = new Uri('http://www.yahoo.com/foo/bar'); $this->assertEquals('http://www.yahoo.com/bar', $uri->withPath('/bar')->toString()); + + $uri = new Uri('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose'); + + $this->assertEquals('http://user:password@example.com:8042/bar?name=ferret&foo=bar#nose', $uri->withPath('/bar')->toString()); } public function testWithQuery() @@ -445,6 +457,10 @@ public function testWithQuery() $uri = new Uri('http://www.yahoo.com/?foo=bar'); $this->assertEquals('http://www.yahoo.com/?bar=foo', $uri->withQuery('bar=foo')->toString()); + + $uri = new Uri('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose'); + + $this->assertEquals('http://user:password@example.com:8042/over/there?bar=foo#nose', $uri->withQuery('bar=foo')->toString()); } public function testWithFragment() @@ -452,6 +468,10 @@ public function testWithFragment() $uri = new Uri('http://www.yahoo.com/#foo'); $this->assertEquals('http://www.yahoo.com/#bar', $uri->withFragment('bar')->toString()); + + $uri = new Uri('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose'); + + $this->assertEquals('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#bar', $uri->withFragment('bar')->toString()); } public function testWithParameters() @@ -459,5 +479,45 @@ public function testWithParameters() $uri = new Uri('http://www.yahoo.com/?foo=bar'); $this->assertEquals('http://www.yahoo.com/?bar=foo', $uri->withParameters(['bar' => 'foo'])->toString()); + + $uri = new Uri('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose'); + + $this->assertEquals('http://user:password@example.com:8042/over/there?bar=foo#nose', $uri->withParameters(['bar' => 'foo'])->toString()); + } + + public function testWithUserInfo() + { + $uri = new Uri('http://www.yahoo.com/'); + + $this->assertEquals('http://foo@www.yahoo.com/', $uri->withUserInfo('foo')->toString()); + $this->assertEquals('http://foo:bar@www.yahoo.com/', $uri->withUserInfo('foo', 'bar')->toString()); + + $uri = new Uri('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose'); + + $this->assertEquals('http://example.com:8042/over/there?name=ferret&foo=bar#nose', $uri->withUserInfo('')->toString()); + $this->assertEquals('http://foo@example.com:8042/over/there?name=ferret&foo=bar#nose', $uri->withUserInfo('foo')->toString()); + $this->assertEquals('http://bar:foo@example.com:8042/over/there?name=ferret&foo=bar#nose', $uri->withUserInfo('bar', 'foo')->toString()); + } + + public function testWithHost() + { + $uri = new Uri('http://www.yahoo.com/'); + + $this->assertEquals('http://google.com/', $uri->withHost('google.com')->toString()); + + $uri = new Uri('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose'); + + $this->assertEquals('http://user:password@google.com:8042/over/there?name=ferret&foo=bar#nose', $uri->withHost('google.com')->toString()); + } + + public function testWithPort() + { + $uri = new Uri('http://www.yahoo.com/'); + + $this->assertEquals('http://www.yahoo.com:8080/', $uri->withPort(8080)->toString()); + + $uri = new Uri('http://user:password@example.com:8042/over/there?name=ferret&foo=bar#nose'); + + $this->assertEquals('http://user:password@example.com:8080/over/there?name=ferret&foo=bar#nose', $uri->withPort(8080)->toString()); } }