diff --git a/composer.json b/composer.json index b0b9b38..b6e3f0e 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,6 @@ "require": { "php": "^8.1", "ext-mbstring": "*", - "guzzlehttp/psr7": "^2.0", "phpunit/phpunit": "^9.5 | ^10.0 | ^11.0 | ^12", "symfony/css-selector": ">=4.4.24 <8.0" }, diff --git a/src/Util/Uri.php b/src/Util/Uri.php index ba8f156..0891d30 100644 --- a/src/Util/Uri.php +++ b/src/Util/Uri.php @@ -4,7 +4,6 @@ namespace Codeception\Util; -use GuzzleHttp\Psr7\Uri as Psr7Uri; use InvalidArgumentException; use function dirname; @@ -28,13 +27,13 @@ class Uri */ public static function mergeUrls(string $baseUri, string $uri): string { - $base = new Psr7Uri($baseUri); + $base = self::parseUrl($baseUri); $parts = parse_url($uri); //If the relative URL does not parse, attempt to parse the entire URL. //PHP Known bug ( https://bugs.php.net/bug.php?id=70942 ) if ($parts === false) { - $parts = parse_url($base . $uri); + $parts = parse_url($baseUri . $uri); } if ($parts === false) { @@ -47,14 +46,14 @@ public static function mergeUrls(string $baseUri, string $uri): string } if (isset($parts['host'])) { - $base = $base->withHost($parts['host']); - $base = $base->withPath(''); - $base = $base->withQuery(''); - $base = $base->withFragment(''); + $base['host'] = $parts['host']; + $base['path'] = ''; + $base['query'] = ''; + $base['fragment'] = ''; } if (isset($parts['path'])) { $path = $parts['path']; - $basePath = $base->getPath(); + $basePath = $base['path'] ?? ''; if ((!str_starts_with($path, '/')) && !empty($path)) { if ($basePath !== '') { // if it ends with a slash, relative paths are below it @@ -69,19 +68,19 @@ public static function mergeUrls(string $baseUri, string $uri): string $path = '/' . ltrim($path, '/'); } } - $base = $base->withPath($path); - $base = $base->withQuery(''); - $base = $base->withFragment(''); + $base['path'] = $path; + $base['query'] = ''; + $base['fragment'] = ''; } if (isset($parts['query'])) { - $base = $base->withQuery($parts['query']); - $base = $base->withFragment(''); + $base['query'] = $parts['query']; + $base['fragment'] = ''; } if (isset($parts['fragment'])) { - $base = $base->withFragment($parts['fragment']); + $base['fragment'] = $parts['fragment']; } - return (string)$base; + return self::phpUrlPartsToString($base); } /** @@ -89,16 +88,18 @@ public static function mergeUrls(string $baseUri, string $uri): string */ public static function retrieveUri(string $url): string { - $uri = new Psr7Uri($url); - return (string)(new Psr7Uri()) - ->withPath($uri->getPath()) - ->withQuery($uri->getQuery()) - ->withFragment($uri->getFragment()); + $urlParts = self::parseUrl($url); + + return self::phpUrlPartsToString([ + 'path' => $urlParts['path'], + 'query' => $urlParts['query'] ?? '', + 'fragment' => $urlParts['fragment'] ?? '', + ]); } public static function retrieveHost(string $url): string { - $urlParts = parse_url($url); + $urlParts = self::parseUrl($url); if (!isset($urlParts['host']) || !isset($urlParts['scheme'])) { throw new InvalidArgumentException("Wrong URL passes, host and scheme not set"); } @@ -111,8 +112,12 @@ public static function retrieveHost(string $url): string public static function appendPath(string $url, string $path): string { - $uri = new Psr7Uri($url); - $cutUrl = (string)$uri->withQuery('')->withFragment(''); + $cutUrl = parse_url($url); + unset( + $cutUrl['query'], + $cutUrl['fragment'], + ); + $cutUrl = self::phpUrlPartsToString($cutUrl); if ($path === '' || $path[0] === '#') { return $cutUrl . $path; @@ -120,4 +125,75 @@ public static function appendPath(string $url, string $path): string return rtrim($cutUrl, '/') . '/' . ltrim($path, '/'); } + + /** + * @return array{ + * scheme?: string, + * host?: string, + * port?: int, + * user?: string, + * pass?: string, + * query?: string, + * path?: string, + * fragment?: string, + * } + */ + public static function parseUrl(string $uri): array + { + $parts = parse_url($uri); + + if ($parts === false) { + throw new InvalidArgumentException("Invalid URI {$uri}"); + } + + return $parts; + } + + /** + * @param array{ + * scheme?: string, + * host?: string, + * port?: int, + * user?: string, + * pass?: string, + * query?: string, + * path?: string, + * fragment?: string, + * } $urlParts + */ + public static function phpUrlPartsToString(array $urlParts): string + { + $uri = ''; + $scheme = $urlParts['scheme'] ?? ''; + $host = $urlParts['host'] ?? ''; + $path = $urlParts['path'] ?? ''; + + if ($scheme !== '') { + $uri .= $scheme . ':'; + } + + if ($host !== '' || $scheme === 'file') { + $uri .= '//' . $host; + + if (($urlParts['port'] ?? '') !== '') { + $uri .= ':' . $urlParts['port']; + } + } + + if ($host !== '' && $path !== '' && $path[0] !== '/') { + $path = '/' . $path; + } + + $uri .= $path; + + if (($urlParts['query'] ?? '') !== '') { + $uri .= '?' . $urlParts['query']; + } + + if (($urlParts['fragment'] ?? '') !== '') { + $uri .= '#' . $urlParts['fragment']; + } + + return $uri; + } } diff --git a/tests/Util/UriTest.php b/tests/Util/UriTest.php index 8d71011..7ff4db9 100644 --- a/tests/Util/UriTest.php +++ b/tests/Util/UriTest.php @@ -5,6 +5,7 @@ namespace Tests\Codeception\Util; use Codeception\Util\Uri; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class UriTest extends TestCase @@ -125,4 +126,61 @@ public function testMergeUrlsWhenBaseUriEndsWithSlashButUriPathHasNoLeadingSlash Uri::mergeUrls('https://codeception.com/', 'test') ); } + + #[DataProvider('phpUrlPartsProvider')] + public function testPhpUrlPartsToString(array $parts, string $expected): void + { + $this->assertSame($expected, Uri::phpUrlPartsToString($parts)); + } + + public static function phpUrlPartsProvider(): iterable + { + yield [ + [ + 'path' => '/test', + ], + '/test' + ]; + + yield [ + [ + 'query' => 'test=a', + ], + '?test=a' + ]; + + yield [ + [ + 'scheme' => 'https', + 'host' => 'codeception.com', + 'path' => '/test', + ], + 'https://codeception.com/test' + ]; + + yield [ + [ + 'scheme' => 'https', + 'host' => 'codeception.com', + ], + 'https://codeception.com' + ]; + + yield [ + [ + 'scheme' => 'https', + 'host' => 'codeception.com', + ], + 'https://codeception.com' + ]; + + yield [ + [ + 'scheme' => 'https', + 'host' => 'codeception.com', + 'port' => 8080, + ], + 'https://codeception.com:8080' + ]; + } }