diff --git a/src/Client.php b/src/Client.php index 4de2977..50c6a39 100644 --- a/src/Client.php +++ b/src/Client.php @@ -23,6 +23,72 @@ class Client public const CONTENT_TYPE_MULTIPART_FORM_DATA = 'multipart/form-data'; public const CONTENT_TYPE_GRAPHQL = 'application/graphql'; + /** @var array headers */ + private array $headers = []; + private int $timeout = 15; + private int $connectTimeout = 60; + private int $maxRedirects = 5; + private bool $allowRedirects = true; + + /** + * @param string $key + * @param string $value + * @return self + */ + public function addHeader(string $key, string $value): self + { + $this->headers[$key] = $value; + return $this; + } + + /** + * Set the request timeout. + * + * @param int $timeout + * @return self + */ + public function setTimeout(int $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + /** + * Set whether to allow redirects. + * + * @param bool $allow + * @return self + */ + public function setAllowRedirects(bool $allow): self + { + $this->allowRedirects = $allow; + return $this; + } + + /** + * Set the maximum number of redirects. + * + * @param int $maxRedirects + * @return self + */ + public function setMaxRedirects(int $maxRedirects): self + { + $this->maxRedirects = $maxRedirects; + return $this; + } + + /** + * Set the connection timeout. + * + * @param int $connectTimeout + * @return self + */ + public function setConnectTimeout(int $connectTimeout): self + { + $this->connectTimeout = $connectTimeout; + return $this; + } + /** * Flatten request body array to PHP multiple format * @@ -45,86 +111,89 @@ private static function flatten(array $data, string $prefix = ''): array return $output; } + /** - * This method is used to make a request to the server + * This method is used to make a request to the server. + * * @param string $url - * @param array $headers * @param string $method * @param array|array $body * @param array $query - * @param int $timeout * @return Response */ - public static function fetch( + public function fetch( string $url, - array $headers = [], string $method = self::METHOD_GET, array $body = [], array $query = [], - int $timeout = 15 ): Response { - // Process the data before making the request - if (!in_array($method, [self::METHOD_PATCH, self::METHOD_GET, self::METHOD_CONNECT, self::METHOD_DELETE, self::METHOD_POST, self::METHOD_HEAD, self::METHOD_OPTIONS, self::METHOD_PUT, self::METHOD_TRACE ])) { // If the method is not supported + if (!in_array($method, [self::METHOD_PATCH, self::METHOD_GET, self::METHOD_CONNECT, self::METHOD_DELETE, self::METHOD_POST, self::METHOD_HEAD, self::METHOD_OPTIONS, self::METHOD_PUT, self::METHOD_TRACE])) { throw new FetchException("Unsupported HTTP method"); } - if(isset($headers['content-type'])) { - match ($headers['content-type']) { // Convert the body to the appropriate format - self::CONTENT_TYPE_APPLICATION_JSON => $body = json_encode($body), - self::CONTENT_TYPE_APPLICATION_FORM_URLENCODED, self::CONTENT_TYPE_MULTIPART_FORM_DATA => $body = self::flatten($body), - self::CONTENT_TYPE_GRAPHQL => $body = $body[0], - default => $body = $body, + + if (isset($this->headers['content-type'])) { + $body = match ($this->headers['content-type']) { + self::CONTENT_TYPE_APPLICATION_JSON => json_encode($body), + self::CONTENT_TYPE_APPLICATION_FORM_URLENCODED, self::CONTENT_TYPE_MULTIPART_FORM_DATA => self::flatten($body), + self::CONTENT_TYPE_GRAPHQL => $body[0], + default => $body, }; } - $headers = array_map(function ($i, $header) { // convert headers to appropriate format - return $i . ':' . $header; - }, array_keys($headers), $headers); - if($query) { // if the request has a query string, append it to the request URI - $url = rtrim($url, '?'); - $url .= '?' . http_build_query($query); + + $formattedHeaders = array_map(function ($key, $value) { + return $key . ':' . $value; + }, array_keys($this->headers), $this->headers); + + if ($query) { + $url = rtrim($url, '?') . '?' . http_build_query($query); } + $responseHeaders = []; - // Initialize the curl session $ch = curl_init(); - // Set the request URI - curl_setopt($ch, CURLOPT_URL, $url); - // Set the request headers - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - // Set the request method - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - // Set the request body - curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - // Save the response headers - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { - $len = strlen($header); - $header = explode(':', $header, 2); - - if (count($header) < 2) { // ignore invalid headers + $curlOptions = [ + CURLOPT_URL => $url, + CURLOPT_HTTPHEADER => $formattedHeaders, + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_POSTFIELDS => $body, + CURLOPT_HEADERFUNCTION => function ($curl, $header) use (&$responseHeaders) { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) { // ignore invalid headers + return $len; + } + $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); return $len; - } + }, + CURLOPT_CONNECTTIMEOUT => $this->connectTimeout, + CURLOPT_TIMEOUT => $this->timeout, + CURLOPT_MAXREDIRS => $this->maxRedirects, + CURLOPT_FOLLOWLOCATION => $this->allowRedirects, + CURLOPT_RETURNTRANSFER => true, + ]; - $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); - return $len; - }); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60); - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $responseBody = curl_exec($ch); // Execute the curl session - $responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + // Merge user-defined CURL options with defaults + foreach ($curlOptions as $option => $value) { + curl_setopt($ch, $option, $value); + } + $responseBody = curl_exec($ch); + $responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (curl_errno($ch)) { $errorMsg = curl_error($ch); } + curl_close($ch); if (isset($errorMsg)) { throw new FetchException($errorMsg); } + $response = new Response( statusCode: $responseStatusCode, headers: $responseHeaders, body: $responseBody ); + return $response; } } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 0f1edcd..f235a99 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -26,11 +26,16 @@ public function testFetch( $query = [] ): void { $resp = null; + try { - $resp = Client::fetch( + $client = new Client(); + foreach($headers as $key => $value) { + $client->addHeader($key, $value); + } + + $resp = $client->fetch( url: $url, method: $method, - headers: $headers, body: $body, query: $query ); @@ -38,14 +43,14 @@ public function testFetch( echo $e; return; } - if ($resp->getStatusCode()===200) { // If the response is OK + if ($resp->getStatusCode() === 200) { // If the response is OK $respData = $resp->json(); // Convert body to array $this->assertEquals($respData['method'], $method); // Assert that the method is equal to the response's method if($method != Client::METHOD_GET) { if(empty($body)) { // if body is empty then response body should be an empty string $this->assertEquals($respData['body'], ''); } else { - if($headers['content-type']!="application/x-www-form-urlencoded") { + if($headers['content-type'] != "application/x-www-form-urlencoded") { $this->assertEquals( // Assert that the body is equal to the response's body $respData['body'], json_encode($body) // Converting the body to JSON string @@ -92,12 +97,11 @@ public function testSendFile( ): void { $resp = null; try { - $resp = Client::fetch( + $client = new Client(); + $client->addHeader('Content-type', 'multipart/form-data'); + $resp = $client->fetch( url: 'localhost:8000', method: Client::METHOD_POST, - headers: [ - 'content-type' => 'multipart/form-data' - ], body: [ 'file' => new \CURLFile(strval(realpath($path)), $contentType, $fileName) ], @@ -107,7 +111,7 @@ public function testSendFile( echo $e; return; } - if ($resp->getStatusCode()===200) { // If the response is OK + if ($resp->getStatusCode() === 200) { // If the response is OK $respData = $resp->json(); // Convert body to array if(isset($respData['method'])) { $this->assertEquals($respData['method'], Client::METHOD_POST); @@ -120,9 +124,9 @@ public function testSendFile( $files = [ // Expected files array from response 'file' => [ 'name' => $fileName, - 'full_path'=> $fileName, - 'type'=> $contentType, - 'error'=> 0 + 'full_path' => $fileName, + 'type' => $contentType, + 'error' => 0 ] ]; $resp_files = json_decode($respData['files'], true); @@ -145,10 +149,10 @@ public function testGetFile( ): void { $resp = null; try { - $resp = Client::fetch( + $client = new Client(); + $resp = $client->fetch( url: 'localhost:8000/'.$type, method: Client::METHOD_GET, - headers: [], body: [], query: [] ); @@ -156,11 +160,11 @@ public function testGetFile( echo $e; return; } - if ($resp->getStatusCode()===200) { // If the response is OK + if ($resp->getStatusCode() === 200) { // If the response is OK $data = fopen($path, 'rb'); - $size=filesize($path); + $size = filesize($path); if($data && $size) { - $contents= fread($data, $size); + $contents = fread($data, $size); fclose($data); $this->assertEquals($resp->getBody(), $contents); // Assert that the body is equal to the expected file contents } else { @@ -178,10 +182,10 @@ public function testRedirect(): void { $resp = null; try { - $resp = Client::fetch( + $client = new Client(); + $resp = $client->fetch( url: 'localhost:8000/redirect', method: Client::METHOD_GET, - headers: [], body: [], query: [] ); @@ -189,7 +193,7 @@ public function testRedirect(): void echo $e; return; } - if ($resp->getStatusCode()===200) { // If the response is OK + if ($resp->getStatusCode() === 200) { // If the response is OK $respData = $resp->json(); // Convert body to array $this->assertEquals($respData['page'], "redirectedPage"); // Assert that the page is the redirected page } else { // If the response is not OK diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index dbe61b6..dd60fad 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -62,7 +62,7 @@ public function testClassMethods( public function dataSet() { return [ - 'dummyResponse'=>[ + 'dummyResponse' => [ '{"name":"John Doe","age":30}', [ 'content-type' => 'application/json' diff --git a/tests/router.php b/tests/router.php index 5583fdf..4b89918 100644 --- a/tests/router.php +++ b/tests/router.php @@ -7,20 +7,20 @@ $body = file_get_contents("php://input"); // Get the request body $files = $_FILES; // Get the request files -$curPageName = substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], "/")+1); +$curPageName = substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], "/") + 1); if($curPageName == 'redirect') { header('Location: http://localhost:8000/redirectedPage'); exit; } -if($curPageName=='image') { - $filename=__DIR__."/resources/logo.png"; +if($curPageName == 'image') { + $filename = __DIR__."/resources/logo.png"; header("Content-disposition: attachment;filename=$filename"); header("Content-type: application/octet-stream"); readfile($filename); exit; -} elseif($curPageName=='text') { - $filename=__DIR__."/resources/test.txt"; +} elseif($curPageName == 'text') { + $filename = __DIR__."/resources/test.txt"; header("Content-disposition: attachment;filename=$filename"); header("Content-type: application/octet-stream"); readfile($filename);