Skip to content

Commit 21fb745

Browse files
Merge pull request #2 from utopia-php/override-curl-opts
feat: add overrides for curl parameters
2 parents 2fa214b + 16e213f commit 21fb745

File tree

4 files changed

+144
-71
lines changed

4 files changed

+144
-71
lines changed

src/Client.php

+114-45
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,72 @@ class Client
2323
public const CONTENT_TYPE_MULTIPART_FORM_DATA = 'multipart/form-data';
2424
public const CONTENT_TYPE_GRAPHQL = 'application/graphql';
2525

26+
/** @var array<string, string> headers */
27+
private array $headers = [];
28+
private int $timeout = 15;
29+
private int $connectTimeout = 60;
30+
private int $maxRedirects = 5;
31+
private bool $allowRedirects = true;
32+
33+
/**
34+
* @param string $key
35+
* @param string $value
36+
* @return self
37+
*/
38+
public function addHeader(string $key, string $value): self
39+
{
40+
$this->headers[$key] = $value;
41+
return $this;
42+
}
43+
44+
/**
45+
* Set the request timeout.
46+
*
47+
* @param int $timeout
48+
* @return self
49+
*/
50+
public function setTimeout(int $timeout): self
51+
{
52+
$this->timeout = $timeout;
53+
return $this;
54+
}
55+
56+
/**
57+
* Set whether to allow redirects.
58+
*
59+
* @param bool $allow
60+
* @return self
61+
*/
62+
public function setAllowRedirects(bool $allow): self
63+
{
64+
$this->allowRedirects = $allow;
65+
return $this;
66+
}
67+
68+
/**
69+
* Set the maximum number of redirects.
70+
*
71+
* @param int $maxRedirects
72+
* @return self
73+
*/
74+
public function setMaxRedirects(int $maxRedirects): self
75+
{
76+
$this->maxRedirects = $maxRedirects;
77+
return $this;
78+
}
79+
80+
/**
81+
* Set the connection timeout.
82+
*
83+
* @param int $connectTimeout
84+
* @return self
85+
*/
86+
public function setConnectTimeout(int $connectTimeout): self
87+
{
88+
$this->connectTimeout = $connectTimeout;
89+
return $this;
90+
}
91+
2692
/**
2793
* Flatten request body array to PHP multiple format
2894
*
@@ -45,86 +111,89 @@ private static function flatten(array $data, string $prefix = ''): array
45111

46112
return $output;
47113
}
114+
48115
/**
49-
* This method is used to make a request to the server
116+
* This method is used to make a request to the server.
117+
*
50118
* @param string $url
51-
* @param array<string, string> $headers
52119
* @param string $method
53120
* @param array<string>|array<string, mixed> $body
54121
* @param array<string, mixed> $query
55-
* @param int $timeout
56122
* @return Response
57123
*/
58-
public static function fetch(
124+
public function fetch(
59125
string $url,
60-
array $headers = [],
61126
string $method = self::METHOD_GET,
62127
array $body = [],
63128
array $query = [],
64-
int $timeout = 15
65129
): Response {
66-
// Process the data before making the request
67-
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
130+
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])) {
68131
throw new FetchException("Unsupported HTTP method");
69132
}
70-
if(isset($headers['content-type'])) {
71-
match ($headers['content-type']) { // Convert the body to the appropriate format
72-
self::CONTENT_TYPE_APPLICATION_JSON => $body = json_encode($body),
73-
self::CONTENT_TYPE_APPLICATION_FORM_URLENCODED, self::CONTENT_TYPE_MULTIPART_FORM_DATA => $body = self::flatten($body),
74-
self::CONTENT_TYPE_GRAPHQL => $body = $body[0],
75-
default => $body = $body,
133+
134+
if (isset($this->headers['content-type'])) {
135+
$body = match ($this->headers['content-type']) {
136+
self::CONTENT_TYPE_APPLICATION_JSON => json_encode($body),
137+
self::CONTENT_TYPE_APPLICATION_FORM_URLENCODED, self::CONTENT_TYPE_MULTIPART_FORM_DATA => self::flatten($body),
138+
self::CONTENT_TYPE_GRAPHQL => $body[0],
139+
default => $body,
76140
};
77141
}
78-
$headers = array_map(function ($i, $header) { // convert headers to appropriate format
79-
return $i . ':' . $header;
80-
}, array_keys($headers), $headers);
81-
if($query) { // if the request has a query string, append it to the request URI
82-
$url = rtrim($url, '?');
83-
$url .= '?' . http_build_query($query);
142+
143+
$formattedHeaders = array_map(function ($key, $value) {
144+
return $key . ':' . $value;
145+
}, array_keys($this->headers), $this->headers);
146+
147+
if ($query) {
148+
$url = rtrim($url, '?') . '?' . http_build_query($query);
84149
}
150+
85151
$responseHeaders = [];
86-
// Initialize the curl session
87152
$ch = curl_init();
88-
// Set the request URI
89-
curl_setopt($ch, CURLOPT_URL, $url);
90-
// Set the request headers
91-
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
92-
// Set the request method
93-
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
94-
// Set the request body
95-
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
96-
// Save the response headers
97-
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
98-
$len = strlen($header);
99-
$header = explode(':', $header, 2);
100-
101-
if (count($header) < 2) { // ignore invalid headers
153+
$curlOptions = [
154+
CURLOPT_URL => $url,
155+
CURLOPT_HTTPHEADER => $formattedHeaders,
156+
CURLOPT_CUSTOMREQUEST => $method,
157+
CURLOPT_POSTFIELDS => $body,
158+
CURLOPT_HEADERFUNCTION => function ($curl, $header) use (&$responseHeaders) {
159+
$len = strlen($header);
160+
$header = explode(':', $header, 2);
161+
if (count($header) < 2) { // ignore invalid headers
162+
return $len;
163+
}
164+
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
102165
return $len;
103-
}
166+
},
167+
CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
168+
CURLOPT_TIMEOUT => $this->timeout,
169+
CURLOPT_MAXREDIRS => $this->maxRedirects,
170+
CURLOPT_FOLLOWLOCATION => $this->allowRedirects,
171+
CURLOPT_RETURNTRANSFER => true,
172+
];
104173

105-
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
106-
return $len;
107-
});
108-
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
109-
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
110-
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
111-
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
112-
$responseBody = curl_exec($ch); // Execute the curl session
113-
$responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
174+
// Merge user-defined CURL options with defaults
175+
foreach ($curlOptions as $option => $value) {
176+
curl_setopt($ch, $option, $value);
177+
}
114178

179+
$responseBody = curl_exec($ch);
180+
$responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
115181
if (curl_errno($ch)) {
116182
$errorMsg = curl_error($ch);
117183
}
184+
118185
curl_close($ch);
119186

120187
if (isset($errorMsg)) {
121188
throw new FetchException($errorMsg);
122189
}
190+
123191
$response = new Response(
124192
statusCode: $responseStatusCode,
125193
headers: $responseHeaders,
126194
body: $responseBody
127195
);
196+
128197
return $response;
129198
}
130199
}

tests/ClientTest.php

+24-20
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,31 @@ public function testFetch(
2626
$query = []
2727
): void {
2828
$resp = null;
29+
2930
try {
30-
$resp = Client::fetch(
31+
$client = new Client();
32+
foreach($headers as $key => $value) {
33+
$client->addHeader($key, $value);
34+
}
35+
36+
$resp = $client->fetch(
3137
url: $url,
3238
method: $method,
33-
headers: $headers,
3439
body: $body,
3540
query: $query
3641
);
3742
} catch (FetchException $e) {
3843
echo $e;
3944
return;
4045
}
41-
if ($resp->getStatusCode()===200) { // If the response is OK
46+
if ($resp->getStatusCode() === 200) { // If the response is OK
4247
$respData = $resp->json(); // Convert body to array
4348
$this->assertEquals($respData['method'], $method); // Assert that the method is equal to the response's method
4449
if($method != Client::METHOD_GET) {
4550
if(empty($body)) { // if body is empty then response body should be an empty string
4651
$this->assertEquals($respData['body'], '');
4752
} else {
48-
if($headers['content-type']!="application/x-www-form-urlencoded") {
53+
if($headers['content-type'] != "application/x-www-form-urlencoded") {
4954
$this->assertEquals( // Assert that the body is equal to the response's body
5055
$respData['body'],
5156
json_encode($body) // Converting the body to JSON string
@@ -92,12 +97,11 @@ public function testSendFile(
9297
): void {
9398
$resp = null;
9499
try {
95-
$resp = Client::fetch(
100+
$client = new Client();
101+
$client->addHeader('Content-type', 'multipart/form-data');
102+
$resp = $client->fetch(
96103
url: 'localhost:8000',
97104
method: Client::METHOD_POST,
98-
headers: [
99-
'content-type' => 'multipart/form-data'
100-
],
101105
body: [
102106
'file' => new \CURLFile(strval(realpath($path)), $contentType, $fileName)
103107
],
@@ -107,7 +111,7 @@ public function testSendFile(
107111
echo $e;
108112
return;
109113
}
110-
if ($resp->getStatusCode()===200) { // If the response is OK
114+
if ($resp->getStatusCode() === 200) { // If the response is OK
111115
$respData = $resp->json(); // Convert body to array
112116
if(isset($respData['method'])) {
113117
$this->assertEquals($respData['method'], Client::METHOD_POST);
@@ -120,9 +124,9 @@ public function testSendFile(
120124
$files = [ // Expected files array from response
121125
'file' => [
122126
'name' => $fileName,
123-
'full_path'=> $fileName,
124-
'type'=> $contentType,
125-
'error'=> 0
127+
'full_path' => $fileName,
128+
'type' => $contentType,
129+
'error' => 0
126130
]
127131
];
128132
$resp_files = json_decode($respData['files'], true);
@@ -145,22 +149,22 @@ public function testGetFile(
145149
): void {
146150
$resp = null;
147151
try {
148-
$resp = Client::fetch(
152+
$client = new Client();
153+
$resp = $client->fetch(
149154
url: 'localhost:8000/'.$type,
150155
method: Client::METHOD_GET,
151-
headers: [],
152156
body: [],
153157
query: []
154158
);
155159
} catch (FetchException $e) {
156160
echo $e;
157161
return;
158162
}
159-
if ($resp->getStatusCode()===200) { // If the response is OK
163+
if ($resp->getStatusCode() === 200) { // If the response is OK
160164
$data = fopen($path, 'rb');
161-
$size=filesize($path);
165+
$size = filesize($path);
162166
if($data && $size) {
163-
$contents= fread($data, $size);
167+
$contents = fread($data, $size);
164168
fclose($data);
165169
$this->assertEquals($resp->getBody(), $contents); // Assert that the body is equal to the expected file contents
166170
} else {
@@ -178,18 +182,18 @@ public function testRedirect(): void
178182
{
179183
$resp = null;
180184
try {
181-
$resp = Client::fetch(
185+
$client = new Client();
186+
$resp = $client->fetch(
182187
url: 'localhost:8000/redirect',
183188
method: Client::METHOD_GET,
184-
headers: [],
185189
body: [],
186190
query: []
187191
);
188192
} catch (FetchException $e) {
189193
echo $e;
190194
return;
191195
}
192-
if ($resp->getStatusCode()===200) { // If the response is OK
196+
if ($resp->getStatusCode() === 200) { // If the response is OK
193197
$respData = $resp->json(); // Convert body to array
194198
$this->assertEquals($respData['page'], "redirectedPage"); // Assert that the page is the redirected page
195199
} else { // If the response is not OK

tests/ResponseTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function testClassMethods(
6262
public function dataSet()
6363
{
6464
return [
65-
'dummyResponse'=>[
65+
'dummyResponse' => [
6666
'{"name":"John Doe","age":30}',
6767
[
6868
'content-type' => 'application/json'

tests/router.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@
77
$body = file_get_contents("php://input"); // Get the request body
88
$files = $_FILES; // Get the request files
99

10-
$curPageName = substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], "/")+1);
10+
$curPageName = substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], "/") + 1);
1111

1212
if($curPageName == 'redirect') {
1313
header('Location: http://localhost:8000/redirectedPage');
1414
exit;
1515
}
16-
if($curPageName=='image') {
17-
$filename=__DIR__."/resources/logo.png";
16+
if($curPageName == 'image') {
17+
$filename = __DIR__."/resources/logo.png";
1818
header("Content-disposition: attachment;filename=$filename");
1919
header("Content-type: application/octet-stream");
2020
readfile($filename);
2121
exit;
22-
} elseif($curPageName=='text') {
23-
$filename=__DIR__."/resources/test.txt";
22+
} elseif($curPageName == 'text') {
23+
$filename = __DIR__."/resources/test.txt";
2424
header("Content-disposition: attachment;filename=$filename");
2525
header("Content-type: application/octet-stream");
2626
readfile($filename);

0 commit comments

Comments
 (0)