diff --git a/README.md b/README.md index 006705c..ac6a4ce 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,6 @@ class YourApi extends Api - [HTTP client (PSR-18) and HTTP factories (PSR-17)](#http-client-psr-18-and-http-factories-psr-17) - [Cache (PSR-6)](#cache-psr-6) - [Logger (PSR-3)](#logger-psr-3) -- [Configure options](#configure-options) ### Base URL @@ -142,6 +141,9 @@ By default, this method will return a `string` as it will be the response of the If you want to change how the response is handled in all requests (for example, decode a JSON string into an array), check the [`addResponseContentsListener`](#addresponsecontentslistener) method in the [Event Listeners](#event-listeners) section. +> [!NOTE] +> If the `path` set is a full URL, it will be used as the request URL even if a `baseUrl` is set. + #### `buildPath` The purpose of this method is to have an easy way to build a properly formatted path depending on the inputs or parameters you might have. diff --git a/composer.json b/composer.json index 126c4d9..31cef0b 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ ], "require": { "php": ">=8.1", + "nyholm/append-query-string": "^1.0", "php-http/cache-plugin": "^2.0", "php-http/client-common": "^2.7", "php-http/discovery": "^1.20", diff --git a/src/Api.php b/src/Api.php index 7a680a2..54bbdba 100644 --- a/src/Api.php +++ b/src/Api.php @@ -66,8 +66,8 @@ public function request( $headers = array_merge($this->headerDefaults, $headers); } - $uri = $this->buildUri($path, $query); - $request = $this->createRequest($method, $uri, $headers, $body); + $url = $this->buildUrl($path, $query); + $request = $this->createRequest($method, $url, $headers, $body); // pre request listener $request = $this->eventDispatcher->dispatch(new PreRequestEvent($request))->getRequest(); @@ -276,25 +276,26 @@ public function buildPath(string $path, array $parameters): string return $path; } - private function buildUri(string $path, array $query = []): string + private function buildUrl(string $path, array $query = []): string { - $uri = StringHelper::reduceDuplicateSlashes($this->baseUrl . $path); + $appendQuery = http_build_query($query); - if (!empty($query)) { - $uri = sprintf('%s?%s', $uri, http_build_query($query)); + if (StringHelper::isUrl($path)) { + return append_query_string($path, $appendQuery, APPEND_QUERY_STRING_REPLACE_DUPLICATE); } - return $uri; + $url = StringHelper::reduceDuplicateSlashes($this->baseUrl . $path); + return append_query_string($url, $appendQuery, APPEND_QUERY_STRING_REPLACE_DUPLICATE); } private function createRequest( string $method, - string $uri, + string $url, array $headers = [], string|StreamInterface $body = null ): RequestInterface { - $request = $this->clientBuilder->getRequestFactory()->createRequest($method, $uri); + $request = $this->clientBuilder->getRequestFactory()->createRequest($method, $url); foreach ($headers as $key => $value) { $request = $request->withHeader($key, $value); diff --git a/src/Helper/StringHelper.php b/src/Helper/StringHelper.php index 9b7686b..f15612d 100644 --- a/src/Helper/StringHelper.php +++ b/src/Helper/StringHelper.php @@ -8,4 +8,9 @@ public static function reduceDuplicateSlashes(string $string): string { return preg_replace('#(^|[^:])//+#', '\\1/', $string); } + + public static function isUrl(string $string): bool + { + return filter_var($string, FILTER_VALIDATE_URL) !== false; + } } \ No newline at end of file diff --git a/tests/Integration/ApiTest.php b/tests/Integration/ApiTest.php index cd0f152..5859e85 100644 --- a/tests/Integration/ApiTest.php +++ b/tests/Integration/ApiTest.php @@ -5,10 +5,12 @@ use Http\Message\Authentication; use Http\Mock\Client; use Nyholm\Psr7\Response; +use PHPUnit\Framework\Attributes\DataProvider; use ProgrammatorDev\Api\Api; use ProgrammatorDev\Api\Builder\CacheBuilder; use ProgrammatorDev\Api\Builder\ClientBuilder; use ProgrammatorDev\Api\Builder\LoggerBuilder; +use ProgrammatorDev\Api\Event\PreRequestEvent; use ProgrammatorDev\Api\Event\ResponseContentsEvent; use ProgrammatorDev\Api\Test\AbstractTestCase; use ProgrammatorDev\Api\Test\MockResponse; @@ -192,6 +194,30 @@ public function testResponseContentsListener() $this->assertIsArray($response); } + #[DataProvider('provideBuildUrlData')] + public function testBuildUrl(?string $baseUrl, string $path, array $query, string $expectedUrl) + { + $this->api->addPreRequestListener(function(PreRequestEvent $event) use ($expectedUrl) { + $url = (string) $event->getRequest()->getUri(); + + $this->assertSame($expectedUrl, $url); + }); + + $this->api->setBaseUrl($baseUrl); + $this->api->request(method: 'GET', path: $path, query: $query); + } + + public static function provideBuildUrlData(): \Generator + { + yield 'no base url' => [null, '/path', [], '/path']; + yield 'base url' => [self::BASE_URL, '/path', [], 'https://base.com/url/path']; + yield 'path full url' => [self::BASE_URL, 'https://fullurl.com/path', [], 'https://fullurl.com/path']; + yield 'duplicated slashes' => [self::BASE_URL, '////path', [], 'https://base.com/url/path']; + yield 'query' => [self::BASE_URL, '/path', ['foo' => 'bar'], 'https://base.com/url/path?foo=bar']; + yield 'path query' => [self::BASE_URL, '/path?test=true', ['foo' => 'bar'], 'https://base.com/url/path?test=true&foo=bar']; + yield 'query replace' => [self::BASE_URL, '/path?test=true', ['test' => 'false'], 'https://base.com/url/path?test=false']; + } + public function testBuildPath() { $path = $this->api->buildPath('/path/{parameter1}/multiple/{parameter2}', [