diff --git a/src/Uri.php b/src/Uri.php index 0d486515..c9a81b7c 100644 --- a/src/Uri.php +++ b/src/Uri.php @@ -46,7 +46,7 @@ class Uri implements UriInterface */ public function __construct($uri = '') { - if ($uri != null) { + if ($uri != '') { $parts = parse_url($uri); if ($parts === false) { throw new \InvalidArgumentException("Unable to parse URI: $uri"); @@ -119,7 +119,7 @@ public static function removeDotSegments($path) */ public static function resolve(UriInterface $base, $rel) { - if ($rel === null || $rel === '') { + if ($rel == '') { return $base; } @@ -128,8 +128,8 @@ public static function resolve(UriInterface $base, $rel) } // Return the relative uri as-is if it has a scheme. - if ($rel->getScheme()) { - return $rel->withPath(static::removeDotSegments($rel->getPath())); + if ($rel->getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); } $relParts = [ @@ -148,18 +148,18 @@ public static function resolve(UriInterface $base, $rel) 'fragment' => $base->getFragment() ]; - if (!empty($relParts['authority'])) { + if ($relParts['authority'] != '') { $parts['authority'] = $relParts['authority']; $parts['path'] = self::removeDotSegments($relParts['path']); $parts['query'] = $relParts['query']; $parts['fragment'] = $relParts['fragment']; - } elseif (!empty($relParts['path'])) { + } elseif ($relParts['path'] != '') { if (substr($relParts['path'], 0, 1) == '/') { $parts['path'] = self::removeDotSegments($relParts['path']); $parts['query'] = $relParts['query']; $parts['fragment'] = $relParts['fragment']; } else { - if (!empty($parts['authority']) && empty($parts['path'])) { + if ($parts['authority'] != '' && $parts['path'] == '') { $mergedPath = '/'; } else { $mergedPath = substr($parts['path'], 0, strrpos($parts['path'], '/') + 1); @@ -168,9 +168,9 @@ public static function resolve(UriInterface $base, $rel) $parts['query'] = $relParts['query']; $parts['fragment'] = $relParts['fragment']; } - } elseif (!empty($relParts['query'])) { + } elseif ($relParts['query'] != '') { $parts['query'] = $relParts['query']; - } elseif ($relParts['fragment'] != null) { + } elseif ($relParts['fragment'] != '') { $parts['fragment'] = $relParts['fragment']; } @@ -199,7 +199,7 @@ public static function resolve(UriInterface $base, $rel) public static function withoutQueryValue(UriInterface $uri, $key) { $current = $uri->getQuery(); - if (!$current) { + if ($current == '') { return $uri; } @@ -232,7 +232,7 @@ public static function withQueryValue(UriInterface $uri, $key, $value) $current = $uri->getQuery(); $key = strtr($key, self::$replaceQuery); - if (!$current) { + if ($current == '') { $result = []; } else { $result = []; @@ -273,16 +273,16 @@ public function getScheme() public function getAuthority() { - if (empty($this->host)) { + if ($this->host == '') { return ''; } $authority = $this->host; - if (!empty($this->userInfo)) { + if ($this->userInfo != '') { $authority = $this->userInfo . '@' . $authority; } - if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) { + if ($this->port !== null) { $authority .= ':' . $this->port; } @@ -306,7 +306,7 @@ public function getPort() public function getPath() { - return $this->path == null ? '' : $this->path; + return $this->path; } public function getQuery() @@ -329,14 +329,14 @@ public function withScheme($scheme) $new = clone $this; $new->scheme = $scheme; - $new->port = $new->filterPort($new->scheme, $new->host, $new->port); + $new->port = $new->filterPort($new->port); return $new; } public function withUserInfo($user, $password = null) { $info = $user; - if ($password) { + if ($password != '') { $info .= ':' . $password; } @@ -362,7 +362,7 @@ public function withHost($host) public function withPort($port) { - $port = $this->filterPort($this->scheme, $this->host, $port); + $port = $this->filterPort($port); if ($this->port === $port) { return $this; @@ -436,7 +436,7 @@ public function withFragment($fragment) /** * Apply parse_url parts to a URI. * - * @param $parts Array of parse_url parts to apply. + * @param array $parts Array of parse_url parts to apply. */ private function applyParts(array $parts) { @@ -445,8 +445,8 @@ private function applyParts(array $parts) : ''; $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; $this->host = isset($parts['host']) ? $parts['host'] : ''; - $this->port = !empty($parts['port']) - ? $this->filterPort($this->scheme, $this->host, $parts['port']) + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) : null; $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) @@ -476,34 +476,36 @@ private static function createUriString($scheme, $authority, $path, $query, $fra { $uri = ''; - if (!empty($scheme)) { + if ($scheme != '') { $uri .= $scheme . ':'; } - $hierPart = ''; - - if (!empty($authority)) { - if (!empty($scheme)) { - $hierPart .= '//'; - } - $hierPart .= $authority; + if ($authority != '') { + $uri .= '//' . $authority; } - if ($path != null) { - // Add a leading slash if necessary. - if ($hierPart && substr($path, 0, 1) !== '/') { - $hierPart .= '/'; + if ($path != '') { + if ($path[0] !== '/') { + if ($authority != '') { + // If the path is rootless and an authority is present, the path MUST be prefixed by "/" + $path = '/' . $path; + } + } elseif (isset($path[1]) && $path[1] === '/') { + if ($authority == '') { + // If the path is starting with more than one "/" and no authority is present, the + // starting slashes MUST be reduced to one. + $path = '/' . ltrim($path, '/'); + } } - $hierPart .= $path; - } - $uri .= $hierPart; + $uri .= $path; + } - if ($query != null) { + if ($query != '') { $uri .= '?' . $query; } - if ($fragment != null) { + if ($fragment != '') { $uri .= '#' . $fragment; } @@ -514,20 +516,12 @@ private static function createUriString($scheme, $authority, $path, $query, $fra * Is a given port non-standard for the current scheme? * * @param string $scheme - * @param string $host - * @param int $port + * @param int $port + * * @return bool */ - private static function isNonStandardPort($scheme, $host, $port) + private static function isNonStandardPort($scheme, $port) { - if (!$scheme && $port) { - return true; - } - - if (!$host || !$port) { - return false; - } - return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme]; } @@ -545,32 +539,32 @@ private function filterScheme($scheme) } /** - * @param string $scheme - * @param string $host - * @param int $port + * @param int|null $port * * @return int|null * * @throws \InvalidArgumentException If the port is invalid. */ - private function filterPort($scheme, $host, $port) + private function filterPort($port) { - if (null !== $port) { - $port = (int) $port; - if (1 > $port || 0xffff < $port) { - throw new \InvalidArgumentException( - sprintf('Invalid port: %d. Must be between 1 and 65535', $port) - ); - } + if ($port === null) { + return null; } - return $this->isNonStandardPort($scheme, $host, $port) ? $port : null; + $port = (int) $port; + if (1 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 1 and 65535', $port) + ); + } + + return self::isNonStandardPort($this->scheme, $port) ? $port : null; } /** * Filters the path of a URI * - * @param $path + * @param string $path * * @return string */ @@ -586,7 +580,7 @@ private function filterPath($path) /** * Filters the query string or fragment of a URI. * - * @param $str + * @param string $str * * @return string */ diff --git a/src/functions.php b/src/functions.php index 2c86eeb9..bb23ee2b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -446,7 +446,7 @@ function readline(StreamInterface $stream, $maxLength = null) } $buffer .= $byte; // Break when a new line is found or the max length - 1 is reached - if ($byte == PHP_EOL || ++$size == $maxLength - 1) { + if ($byte === "\n" || ++$size === $maxLength - 1) { break; } } diff --git a/tests/StreamWrapperTest.php b/tests/StreamWrapperTest.php index 0156e598..7183aae8 100644 --- a/tests/StreamWrapperTest.php +++ b/tests/StreamWrapperTest.php @@ -21,6 +21,8 @@ public function testResource() $this->assertSame('', fread($handle, 1)); $this->assertTrue(feof($handle)); + $stBlksize = defined('PHP_WINDOWS_VERSION_BUILD') ? -1 : 0; + // This fails on HHVM for some reason if (!defined('HHVM_VERSION')) { $this->assertEquals([ @@ -35,8 +37,8 @@ public function testResource() 'atime' => 0, 'mtime' => 0, 'ctime' => 0, - 'blksize' => 0, - 'blocks' => 0, + 'blksize' => $stBlksize, + 'blocks' => $stBlksize, 0 => 0, 1 => 0, 2 => 33206, @@ -48,8 +50,8 @@ public function testResource() 8 => 0, 9 => 0, 10 => 0, - 11 => 0, - 12 => 0, + 11 => $stBlksize, + 12 => $stBlksize, ], fstat($handle)); } diff --git a/tests/UriTest.php b/tests/UriTest.php index de33986c..66894ae2 100644 --- a/tests/UriTest.php +++ b/tests/UriTest.php @@ -8,64 +8,134 @@ */ class UriTest extends \PHPUnit_Framework_TestCase { - const RFC3986_BASE = "http://a/b/c/d;p?q"; + const RFC3986_BASE = 'http://a/b/c/d;p?q'; - public function testParsesProvidedUrl() + public function testParsesProvidedUri() { - $uri = new Uri('https://michael:test@test.com:443/path/123?q=abc#test'); + $uri = new Uri('https://user:pass@example.com:8080/path/123?q=abc#test'); + + $this->assertSame('https', $uri->getScheme()); + $this->assertSame('user:pass@example.com:8080', $uri->getAuthority()); + $this->assertSame('user:pass', $uri->getUserInfo()); + $this->assertSame('example.com', $uri->getHost()); + $this->assertSame(8080, $uri->getPort()); + $this->assertSame('/path/123', $uri->getPath()); + $this->assertSame('q=abc', $uri->getQuery()); + $this->assertSame('test', $uri->getFragment()); + $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri); + } - // Standard port 443 for https gets ignored. - $this->assertEquals( - 'https://michael:test@test.com/path/123?q=abc#test', - (string) $uri - ); + public function testCanTransformAndRetrievePartsIndividually() + { + $uri = (new Uri()) + ->withScheme('https') + ->withUserInfo('user', 'pass') + ->withHost('example.com') + ->withPort(8080) + ->withPath('/path/123') + ->withQuery('q=abc') + ->withFragment('test'); + + $this->assertSame('https', $uri->getScheme()); + $this->assertSame('user:pass@example.com:8080', $uri->getAuthority()); + $this->assertSame('user:pass', $uri->getUserInfo()); + $this->assertSame('example.com', $uri->getHost()); + $this->assertSame(8080, $uri->getPort()); + $this->assertSame('/path/123', $uri->getPath()); + $this->assertSame('q=abc', $uri->getQuery()); + $this->assertSame('test', $uri->getFragment()); + $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri); + } + + /** + * @dataProvider getValidUris + */ + public function testValidUrisStayValid($input) + { + $uri = new Uri($input); - $this->assertEquals('test', $uri->getFragment()); - $this->assertEquals('test.com', $uri->getHost()); - $this->assertEquals('/path/123', $uri->getPath()); - $this->assertEquals(null, $uri->getPort()); - $this->assertEquals('q=abc', $uri->getQuery()); - $this->assertEquals('https', $uri->getScheme()); - $this->assertEquals('michael:test', $uri->getUserInfo()); + $this->assertSame($input, (string) $uri); + } + + /** + * @dataProvider getValidUris + */ + public function testFromParts($input) + { + $uri = Uri::fromParts(parse_url($input)); + + $this->assertSame($input, (string) $uri); + } + + public function getValidUris() + { + return [ + ['urn:path-rootless'], + ['urn:/path-absolute'], + ['urn:/'], + // only scheme with empty path + ['urn:'], + // only path + ['/'], + ['relative/'], + ['0'], + // same document reference + [''], + // network path without scheme + ['//example.org'], + ['//example.org/'], + ['//example.org?q#h'], + // only query + ['?q'], + ['?q=abc&foo=bar'], + // only fragment + ['#fragment'], + ]; } /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Unable to parse URI + * @dataProvider getInvalidUris */ - public function testValidatesUriCanBeParsed() + public function testInvalidUrisThrowException($invalidUri) { - new Uri('///'); + new Uri($invalidUri); } - public function testCanTransformAndRetrievePartsIndividually() + public function getInvalidUris() { - $uri = (new Uri('')) - ->withFragment('#test') - ->withHost('example.com') - ->withPath('path/123') - ->withPort(8080) - ->withQuery('?q=abc') - ->withScheme('http') - ->withUserInfo('user', 'pass'); - - // Test getters. - $this->assertEquals('user:pass@example.com:8080', $uri->getAuthority()); - $this->assertEquals('test', $uri->getFragment()); - $this->assertEquals('example.com', $uri->getHost()); - $this->assertEquals('path/123', $uri->getPath()); - $this->assertEquals(8080, $uri->getPort()); - $this->assertEquals('q=abc', $uri->getQuery()); - $this->assertEquals('http', $uri->getScheme()); - $this->assertEquals('user:pass', $uri->getUserInfo()); + return [ + ['///'], + ['urn://example:animal:ferret:nose'], + ]; } /** * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid port: 100000. Must be between 1 and 65535 */ public function testPortMustBeValid() { - (new Uri(''))->withPort(100000); + (new Uri())->withPort(100000); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid port: 0. Must be between 1 and 65535 + */ + public function testWithPortCannotBeZero() + { + (new Uri())->withPort(0); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unable to parse URI + */ + public function testParseUriPortCannotBeZero() + { + new Uri('//example.com:0'); } /** @@ -73,7 +143,7 @@ public function testPortMustBeValid() */ public function testPathMustBeValid() { - (new Uri(''))->withPath([]); + (new Uri())->withPath([]); } /** @@ -81,24 +151,41 @@ public function testPathMustBeValid() */ public function testQueryMustBeValid() { - (new Uri(''))->withQuery(new \stdClass); + (new Uri())->withQuery(new \stdClass); + } + + public function testCanParseFalseyUriParts() + { + $uri = new Uri('0://0:0@0/0?0#0'); + + $this->assertSame('0', $uri->getScheme()); + $this->assertSame('0:0@0', $uri->getAuthority()); + $this->assertSame('0:0', $uri->getUserInfo()); + $this->assertSame('0', $uri->getHost()); + $this->assertSame('/0', $uri->getPath()); + $this->assertSame('0', $uri->getQuery()); + $this->assertSame('0', $uri->getFragment()); + $this->assertSame('0://0:0@0/0?0#0', (string) $uri); } - public function testAllowsFalseyUrlParts() + public function testCanConstructFalseyUriParts() { - $url = new Uri('http://a:1/0?0#0'); - $this->assertSame('a', $url->getHost()); - $this->assertEquals(1, $url->getPort()); - $this->assertSame('/0', $url->getPath()); - $this->assertEquals('0', (string) $url->getQuery()); - $this->assertSame('0', $url->getFragment()); - $this->assertEquals('http://a:1/0?0#0', (string) $url); - $url = new Uri(''); - $this->assertSame('', (string) $url); - $url = new Uri('0'); - $this->assertSame('0', (string) $url); - $url = new Uri('/'); - $this->assertSame('/', (string) $url); + $uri = (new Uri()) + ->withScheme('0') + ->withUserInfo('0', '0') + ->withHost('0') + ->withPath('/0') + ->withQuery('0') + ->withFragment('0'); + + $this->assertSame('0', $uri->getScheme()); + $this->assertSame('0:0@0', $uri->getAuthority()); + $this->assertSame('0:0', $uri->getUserInfo()); + $this->assertSame('0', $uri->getHost()); + $this->assertSame('/0', $uri->getPath()); + $this->assertSame('0', $uri->getQuery()); + $this->assertSame('0', $uri->getFragment()); + $this->assertSame('0://0:0@0/0?0#0', (string) $uri); } /** @@ -108,13 +195,13 @@ public function testResolvesUris($base, $rel, $expected) { $uri = new Uri($base); $actual = Uri::resolve($uri, $rel); - $this->assertEquals($expected, (string) $actual); + $this->assertSame($expected, (string) $actual); } public function getResolveTestCases() { return [ - //[self::RFC3986_BASE, 'g:h', 'g:h'], + [self::RFC3986_BASE, 'g:h', 'g:h'], [self::RFC3986_BASE, 'g', 'http://a/b/c/g'], [self::RFC3986_BASE, './g', 'http://a/b/c/g'], [self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'], @@ -155,128 +242,174 @@ public function getResolveTestCases() ['http://u@a/b/c/d;p?q', '.', 'http://u@a/b/c/'], ['http://u:p@a/b/c/d;p?q', '.', 'http://u:p@a/b/c/'], ['http://a/b/c/d/', 'e', 'http://a/b/c/d/e'], + // falsey relative parts + [self::RFC3986_BASE, '//0', 'http://0'], + [self::RFC3986_BASE, '0', 'http://a/b/c/0'], + [self::RFC3986_BASE, '?0', 'http://a/b/c/d;p?0'], + [self::RFC3986_BASE, '#0', 'http://a/b/c/d;p?q#0'], ]; } public function testAddAndRemoveQueryValues() { - $uri = new Uri('http://foo.com/bar'); + $uri = new Uri(); $uri = Uri::withQueryValue($uri, 'a', 'b'); $uri = Uri::withQueryValue($uri, 'c', 'd'); $uri = Uri::withQueryValue($uri, 'e', null); - $this->assertEquals('a=b&c=d&e', $uri->getQuery()); + $this->assertSame('a=b&c=d&e', $uri->getQuery()); $uri = Uri::withoutQueryValue($uri, 'c'); $uri = Uri::withoutQueryValue($uri, 'e'); - $this->assertEquals('a=b', $uri->getQuery()); + $this->assertSame('a=b', $uri->getQuery()); $uri = Uri::withoutQueryValue($uri, 'a'); $uri = Uri::withoutQueryValue($uri, 'a'); - $this->assertEquals('', $uri->getQuery()); + $this->assertSame('', $uri->getQuery()); + } + + public function testPortIsNullIfStandardPortForScheme() + { + // HTTPS standard port + $uri = new Uri('https://example.com:443'); + $this->assertNull($uri->getPort()); + $this->assertSame('example.com', $uri->getAuthority()); + + $uri = (new Uri('https://example.com'))->withPort(443); + $this->assertNull($uri->getPort()); + $this->assertSame('example.com', $uri->getAuthority()); + + // HTTP standard port + $uri = new Uri('http://example.com:80'); + $this->assertNull($uri->getPort()); + $this->assertSame('example.com', $uri->getAuthority()); + + $uri = (new Uri('http://example.com'))->withPort(80); + $this->assertNull($uri->getPort()); + $this->assertSame('example.com', $uri->getAuthority()); } - public function testGetAuthorityReturnsCorrectPort() + public function testPortIsReturnedIfSchemeUnknown() { - // HTTPS non-standard port - $uri = new Uri('https://foo.co:99'); - $this->assertEquals('foo.co:99', $uri->getAuthority()); + $uri = (new Uri('//example.com'))->withPort(80); - // HTTP non-standard port - $uri = new Uri('http://foo.co:99'); - $this->assertEquals('foo.co:99', $uri->getAuthority()); + $this->assertSame(80, $uri->getPort()); + $this->assertSame('example.com:80', $uri->getAuthority()); + } + + public function testStandardPortIsNullIfSchemeChanges() + { + $uri = new Uri('http://example.com:443'); + $this->assertSame('http', $uri->getScheme()); + $this->assertSame(443, $uri->getPort()); + + $uri = $uri->withScheme('https'); + $this->assertNull($uri->getPort()); + } - // No scheme - $uri = new Uri('foo.co:99'); - $this->assertEquals('foo.co:99', $uri->getAuthority()); + public function testPortPassedAsStringIsCastedToInt() + { + $uri = (new Uri('//example.com'))->withPort('8080'); - // No host or port - $uri = new Uri('http:'); - $this->assertEquals('', $uri->getAuthority()); + $this->assertSame(8080, $uri->getPort(), 'Port is returned as integer'); + $this->assertSame('example.com:8080', $uri->getAuthority()); + } - // No host or port - $uri = new Uri('http://foo.co'); - $this->assertEquals('foo.co', $uri->getAuthority()); + public function testPortCanBeRemoved() + { + $uri = (new Uri('http://example.com:8080'))->withPort(null); + + $this->assertNull($uri->getPort()); + $this->assertSame('http://example.com', (string) $uri); } - public function pathTestProvider() + public function testAuthorityWithUserInfoButWithoutHost() + { + $uri = (new Uri())->withUserInfo('user', 'pass'); + + $this->assertSame('user:pass', $uri->getUserInfo()); + $this->assertSame('', $uri->getAuthority()); + } + + public function pathEncodingProvider() { return [ // Percent encode spaces. - ['http://foo.com/baz bar', 'http://foo.com/baz%20bar'], + ['/baz bar', '/baz%20bar'], // Don't encoding something that's already encoded. - ['http://foo.com/baz%20bar', 'http://foo.com/baz%20bar'], + ['/baz%20bar', '/baz%20bar'], // Percent encode invalid percent encodings - ['http://foo.com/baz%2-bar', 'http://foo.com/baz%252-bar'], + ['/baz%2-bar', '/baz%252-bar'], // Don't encode path segments - ['http://foo.com/baz/bar/bam?a', 'http://foo.com/baz/bar/bam?a'], - ['http://foo.com/baz+bar', 'http://foo.com/baz+bar'], - ['http://foo.com/baz:bar', 'http://foo.com/baz:bar'], - ['http://foo.com/baz@bar', 'http://foo.com/baz@bar'], - ['http://foo.com/baz(bar);bam/', 'http://foo.com/baz(bar);bam/'], - ['http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@', 'http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@'], + ['/baz/bar/bam', '/baz/bar/bam'], + ['/baz+bar', '/baz+bar'], + ['/baz:bar', '/baz:bar'], + ['/baz@bar', '/baz@bar'], + ['/baz(bar);bam/', '/baz(bar);bam/'], + ['/a-zA-Z0-9.-_~!$&\'()*+,;=:@', '/a-zA-Z0-9.-_~!$&\'()*+,;=:@'], ]; } /** - * @dataProvider pathTestProvider + * @dataProvider pathEncodingProvider */ public function testUriEncodesPathProperly($input, $output) { $uri = new Uri($input); - $this->assertEquals((string) $uri, $output); - } - - public function testDoesNotAddPortWhenNoPort() - { - $this->assertEquals('bar', new Uri('//bar')); - $this->assertEquals('bar', (new Uri('//bar'))->getHost()); + $this->assertSame($output, $uri->getPath()); + $this->assertSame($output, (string) $uri); } public function testAllowsForRelativeUri() { $uri = (new Uri)->withPath('foo'); - $this->assertEquals('foo', $uri->getPath()); - $this->assertEquals('foo', (string) $uri); + $this->assertSame('foo', $uri->getPath()); + $this->assertSame('foo', (string) $uri); } public function testAddsSlashForRelativeUriStringWithHost() { - $uri = (new Uri)->withPath('foo')->withHost('bar.com'); - $this->assertEquals('foo', $uri->getPath()); - $this->assertEquals('bar.com/foo', (string) $uri); + // If the path is rootless and an authority is present, the path MUST + // be prefixed by "/". + $uri = (new Uri)->withPath('foo')->withHost('example.com'); + $this->assertSame('foo', $uri->getPath()); + // concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong + $this->assertSame('//example.com/foo', (string) $uri); } - /** - * @dataProvider pathTestNoAuthority - */ - public function testNoAuthority($input) + public function testRemoveExtraSlashesWihoutHost() { - $uri = new Uri($input); - - $this->assertEquals($input, (string) $uri); + // If the path is starting with more than one "/" and no authority is + // present, the starting slashes MUST be reduced to one. + $uri = (new Uri)->withPath('//foo'); + $this->assertSame('//foo', $uri->getPath()); + // URI "//foo" would be interpreted as network reference and thus change the original path to the host + $this->assertSame('/foo', (string) $uri); } - public function pathTestNoAuthority() + public function testDefaultReturnValuesOfGetters() { - return [ - // path-rootless - ['urn:example:animal:ferret:nose'], - // path-absolute - ['urn:/example:animal:ferret:nose'], - ['urn:/'], - // path-empty - ['urn:'], - ['urn'], - ]; + $uri = new Uri(); + + $this->assertSame('', $uri->getScheme()); + $this->assertSame('', $uri->getAuthority()); + $this->assertSame('', $uri->getUserInfo()); + $this->assertSame('', $uri->getHost()); + $this->assertNull($uri->getPort()); + $this->assertSame('', $uri->getPath()); + $this->assertSame('', $uri->getQuery()); + $this->assertSame('', $uri->getFragment()); } - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Unable to parse URI - */ - public function testNoAuthorityWithInvalidPath() + public function testImmutability() { - $input = 'urn://example:animal:ferret:nose'; - $uri = new Uri($input); + $uri = new Uri(); + + $this->assertNotSame($uri, $uri->withScheme('https')); + $this->assertNotSame($uri, $uri->withUserInfo('user', 'pass')); + $this->assertNotSame($uri, $uri->withHost('example.com')); + $this->assertNotSame($uri, $uri->withPort(8080)); + $this->assertNotSame($uri, $uri->withPath('/path/123')); + $this->assertNotSame($uri, $uri->withQuery('q=abc')); + $this->assertNotSame($uri, $uri->withFragment('test')); } public function testExtendingClassesInstantiates()