diff --git a/src/Http/Client/HttpClient.php b/src/Http/Client/HttpClient.php index 0c398fd1..d1852598 100644 --- a/src/Http/Client/HttpClient.php +++ b/src/Http/Client/HttpClient.php @@ -112,9 +112,7 @@ public function get(string $url, array $data = []): Response curl_setopt($this->ch, CURLOPT_HTTPGET, true); - $content = $this->execute(); - - return new Response($this->ch, $content); + return $this->execute(); } /** @@ -158,12 +156,12 @@ private function applyCommonOptions(): void } /** - * Execute request + * Execute request and return Response * - * @return string + * @return Response * @throws Exception */ - private function execute(): string + private function execute(): Response { if ($this->headers) { curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->headers); @@ -172,6 +170,9 @@ private function execute(): string $content = curl_exec($this->ch); $errno = curl_errno($this->ch); + // Create response before closing to capture curl info + $response = new Response($this->ch, $content !== false ? $content : null); + $this->close(); if ($content === false) { @@ -181,17 +182,40 @@ private function execute(): string ); } - return $content; + return $response; } /** - * Close connection + * Close connection and reset state * * @return void */ private function close(): void { - curl_close($this->ch); + $this->ch = null; + $this->headers = []; + $this->attach = []; + $this->accept_json = false; + } + + /** + * Send request with custom HTTP method + * + * @param string $method + * @param string $url + * @param array $data + * @return Response + * @throws Exception + */ + private function sendWithMethod(string $method, string $url, array $data = []): Response + { + $this->init($url); + $this->addFields($data); + $this->applyCommonOptions(); + + curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method); + + return $this->execute(); } /** @@ -221,9 +245,7 @@ public function post(string $url, array $data = []): Response curl_setopt($this->ch, CURLOPT_POST, true); - $content = $this->execute(); - - return new Response($this->ch, $content); + return $this->execute(); } /** @@ -257,15 +279,7 @@ private function addFields(array $data): void */ public function put(string $url, array $data = []): Response { - $this->init($url); - $this->addFields($data); - $this->applyCommonOptions(); - - curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, "PUT"); - - $content = $this->execute(); - - return new Response($this->ch, $content); + return $this->sendWithMethod("PUT", $url, $data); } /** @@ -278,15 +292,60 @@ public function put(string $url, array $data = []): Response */ public function delete(string $url, array $data = []): Response { + return $this->sendWithMethod("DELETE", $url, $data); + } + + /** + * Make PATCH request + * + * @param string $url + * @param array $data + * @return Response + * @throws Exception + */ + public function patch(string $url, array $data = []): Response + { + return $this->sendWithMethod("PATCH", $url, $data); + } + + /** + * Make HEAD request (retrieves headers only, no body) + * + * @param string $url + * @param array $data + * @return Response + * @throws Exception + */ + public function head(string $url, array $data = []): Response + { + if (count($data) > 0) { + $url = $url . "?" . http_build_query($data); + } + $this->init($url); - $this->addFields($data); $this->applyCommonOptions(); - curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_setopt($this->ch, CURLOPT_NOBODY, true); + curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, "HEAD"); + + return $this->execute(); + } + + /** + * Make OPTIONS request (retrieves allowed HTTP methods) + * + * @param string $url + * @return Response + * @throws Exception + */ + public function options(string $url): Response + { + $this->init($url); + $this->applyCommonOptions(); - $content = $this->execute(); + curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, "OPTIONS"); - return new Response($this->ch, $content); + return $this->execute(); } /** diff --git a/tests/Support/HttpClientTest.php b/tests/Support/HttpClientTest.php index d6c51a27..683d18e8 100644 --- a/tests/Support/HttpClientTest.php +++ b/tests/Support/HttpClientTest.php @@ -102,6 +102,64 @@ public function test_delete_method() $this->assertEquals(200, $response->statusCode()); } + // ==================== PATCH Method Tests ==================== + + public function test_patch_method_with_data() + { + $http = new HttpClient(); + $response = $http->patch("https://httpbin.org/patch", [ + 'name' => 'patched', + 'value' => 'example' + ]); + + $this->assertEquals(200, $response->statusCode()); + $this->assertStringContainsString('patched', $response->getContent()); + } + + public function test_patch_method_with_json_data() + { + $http = new HttpClient(); + $http->acceptJson(); + + $response = $http->patch("https://httpbin.org/patch", [ + 'name' => 'patched', + 'value' => 'json-example' + ]); + + $this->assertEquals(200, $response->statusCode()); + $this->assertStringContainsString('json-example', $response->getContent()); + } + + // ==================== HEAD Method Tests ==================== + + public function test_head_method() + { + $http = new HttpClient(); + $response = $http->head("https://httpbin.org/get"); + + $this->assertEquals(200, $response->statusCode()); + // HEAD should not return body content + $this->assertEmpty($response->getContent()); + } + + public function test_head_method_with_query_params() + { + $http = new HttpClient(); + $response = $http->head("https://httpbin.org/get", ['key' => 'value']); + + $this->assertEquals(200, $response->statusCode()); + } + + // ==================== OPTIONS Method Tests ==================== + + public function test_options_method() + { + $http = new HttpClient(); + $response = $http->options("https://httpbin.org/get"); + + $this->assertEquals(200, $response->statusCode()); + } + // ==================== Header Tests ==================== public function test_add_multiple_headers()