Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b46c3c2

Browse files
Add logger support: Introduced optional logger in the HttpClient constructor to enable request and response logging for better debugging and monitoring.
1 parent 6ff54c7 commit b46c3c2

File tree

4 files changed

+112
-37
lines changed

4 files changed

+112
-37
lines changed

‎src/HttpStatusCode.php renamed to ‎src/Http/HttpStatusCode.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace PhpDevCommunity\HttpClient;
3+
namespace PhpDevCommunity\HttpClient\Http;
44

55
use SplEnum;
66

‎src/HttpClient.php

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use InvalidArgumentException;
66
use LogicException;
7+
use PhpDevCommunity\HttpClient\Http\HttpStatusCode;
78
use PhpDevCommunity\HttpClient\Http\Response;
89

910
final class HttpClient
@@ -21,6 +22,8 @@ final class HttpClient
2122
*/
2223
private array $options;
2324

25+
private $logger;
26+
2427
/**
2528
* HttpClient constructor.
2629
*
@@ -31,7 +34,7 @@ final class HttpClient
3134
* - array headers An associative array of HTTP headers to include in the request.
3235
* - string base_url The base URL to prepend to relative URLs in the request.
3336
*/
34-
public function __construct(array $options = [])
37+
public function __construct(array $options = [], ?callable$logger = null)
3538
{
3639
self::validateOptions($options, ['user_agent', 'timeout', 'headers', 'base_url']);
3740
$this->options = array_replace([
@@ -40,6 +43,7 @@ public function __construct(array $options = [])
4043
'headers' => [],
4144
'base_url' => null,
4245
], $options);
46+
$this->logger = $logger;
4347
}
4448

4549
/**
@@ -95,6 +99,8 @@ public function post(string $url, array $data, bool $json = false, array $header
9599
*/
96100
public function fetch(string $url, array $options = []): Response
97101
{
102+
$options['method'] = strtoupper($options['method'] ?? 'GET');
103+
$options['body'] = $options['body'] ?? '';
98104
self::validateOptions($options, ['user_agent', 'timeout', 'headers', 'body', 'method']);
99105

100106
$options = array_merge_recursive($this->options, $options);
@@ -110,12 +116,28 @@ public function fetch(string $url, array $options = []): Response
110116
throw new InvalidArgumentException(sprintf('Invalid URL: %s', $url));
111117
}
112118

119+
$info = [
120+
'url' => $url,
121+
'request' => [
122+
'user_agent' => $options['user_agent'],
123+
'method' => $options['method'],
124+
'headers' => $options['headers'],
125+
'body' => $options['body'],
126+
],
127+
'response' => [
128+
'body' => '',
129+
'headers' => [],
130+
]
131+
];
132+
113133
$response = '';
114134
$fp = fopen($url, 'rb', false, $context);
115135
$httpResponseHeaders = $http_response_header;
136+
$headers = self::parseHttpResponseHeaders($httpResponseHeaders);
137+
$info['response']['headers'] = $headers;
116138
if ($fp === false) {
117-
$detail = $httpResponseHeaders[0] ?? '';
118-
throw new LogicException(sprintf('Error opening request to %s: %s', $url, $detail));
139+
$this->log($info);
140+
throw new LogicException(sprintf('Error opening request to %s: %s', $url, $httpResponseHeaders[0] ?? ''));
119141
}
120142

121143
while (!feof($fp)) {
@@ -124,9 +146,10 @@ public function fetch(string $url, array $options = []): Response
124146

125147
fclose($fp);
126148

127-
$headers = self::parseHttpResponseHeaders($httpResponseHeaders);
149+
$info['response']['body'] = $response;
150+
$this->log($info);
128151

129-
return new Response($response, $headers['status_code'] ?? HttpStatusCode::HTTP_VERSION_NOT_SUPPORTED, $headers);
152+
return new Response($response, $headers['status_code'], $headers);
130153
}
131154

132155
/**
@@ -137,16 +160,14 @@ public function fetch(string $url, array $options = []): Response
137160
*/
138161
private function createContext(array $options)
139162
{
140-
$method = strtoupper($options['method'] ?? 'GET');
141-
$body = $options['body'] ?? '';
142-
143-
if (in_array($method, ['POST', 'PUT']) && is_array($body)) {
163+
$body = $options['body'];
164+
if (in_array($options['method'], ['POST', 'PUT']) && is_array($body)) {
144165
$body = self::prepareRequestBody($body, $options['headers']);
145166
}
146167

147168
$opts = [
148169
'http' => [
149-
'method' => $method,
170+
'method' => $options['method'],
150171
'header' => self::formatHttpRequestHeaders($options['headers']),
151172
'content' => $body,
152173
'user_agent' => $options['user_agent'],
@@ -158,6 +179,14 @@ private function createContext(array $options)
158179
return stream_context_create($opts);
159180
}
160181

182+
private function log(array $info): void
183+
{
184+
$logger = $this->logger;
185+
if (is_callable($logger)) {
186+
$logger($info);
187+
}
188+
}
189+
161190
/**
162191
* Format HTTP headers from the provided associative array.
163192
*
@@ -212,6 +241,10 @@ private static function parseHttpResponseHeaders(array $responseHeaders): array
212241
}
213242
}
214243
}
244+
245+
if (!isset($headers['status_code'])) {
246+
$headers['status_code'] = HttpStatusCode::HTTP_VERSION_NOT_SUPPORTED;
247+
}
215248
return $headers;
216249
}
217250

‎src/helpers.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
* @param array $options The options to configure the HttpClient
1212
* @return HttpClient The newly created HttpClient instance
1313
*/
14-
function http_client(array $options = []): HttpClient
14+
function http_client(array $options = [], callable$logger = null): HttpClient
1515
{
16-
return new HttpClient($options);
16+
return new HttpClient($options, $logger);
1717
}
1818
}
1919

@@ -28,7 +28,7 @@ function http_client(array $options = []): HttpClient
2828
*/
2929
function http_post(string $url, array $data = [], array $headers = []): Response
3030
{
31-
return http_client()->post($url, $data, false,$headers);
31+
return http_client()->post($url, $data, false,$headers);
3232
}
3333
}
3434

@@ -43,7 +43,7 @@ function http_post(string $url, array $data = [], array $headers = []): Response
4343
*/
4444
function http_post_json(string $url, array $data = [], array $headers = []): Response
4545
{
46-
return http_client()->post($url, $data, true ,$headers);
46+
return http_client()->post($url, $data, true, $headers);
4747
}
4848
}
4949

‎tests/HttpClientTest.php

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace PhpDevCommunity\tests;
44

5+
use Exception;
6+
use LogicException;
57
use PhpDevCommunity\HttpClient\HttpClient;
68
use PHPUnit\Framework\TestCase;
79

@@ -10,38 +12,46 @@ class HttpClientTest extends TestCase
1012
const URL = 'http://localhost:4245';
1113

1214
protected static ?string $serverProcess = null;
15+
1316
public static function setUpBeforeClass(): void
1417
{
15-
$fileToRun = __DIR__.DIRECTORY_SEPARATOR.'test_server.php';
16-
$command = sprintf('php -S %s %s > /dev/null 2>&1 & echo $!;',str_replace('http://', '', self::URL), $fileToRun);
18+
$fileToRun = __DIR__ . DIRECTORY_SEPARATOR . 'test_server.php';
19+
$command = sprintf('php -S %s %s > /dev/null 2>&1 & echo $!;',str_replace('http://', '', self::URL), $fileToRun);
1720
self::$serverProcess = exec($command);
1821
if (empty(self::$serverProcess) || !is_numeric(self::$serverProcess)) {
19-
throw new \Exception('Could not start test server');
22+
throw new Exception('Could not start test server');
2023
}
2124
sleep(1);
2225
}
2326

2427
public function testGetRequest()
2528
{
26-
$response = http_client(['base_url' => self::URL, 'headers' => ['Authorization' => 'Bearer secret_token']])->get('/api/data');
27-
$this->assertEquals( 200, $response->getStatusCode() );
29+
$response = http_client(
30+
['base_url' => self::URL, 'headers' => ['Authorization' => 'Bearer secret_token']],
31+
function ($info) {
32+
$this->assertEquals( 'GET', $info['request']['method']);
33+
$this->assertEquals( 'Bearer secret_token', $info['request']['headers']['Authorization']);
34+
$this->assertEquals( '{"message":"GET request received"}', $info['response']['body']);
35+
}
36+
)->get('/api/data');
37+
$this->assertEquals(200, $response->getStatusCode());
2838
$this->assertNotEmpty($response->getBody());
2939
}
3040

3141
public function testGetWithQueryRequest()
3242
{
33-
$client = new HttpClient(['base_url' => self::URL,'headers' => ['Authorization' => 'Bearer secret_token']]);
43+
$client = new HttpClient(['base_url' => self::URL,'headers' => ['Authorization' => 'Bearer secret_token']]);
3444
$response = $client->get('/api/search', [
3545
'name' => 'foo',
3646
]);
3747

38-
$this->assertEquals(200, $response->getStatusCode());
48+
$this->assertEquals(200, $response->getStatusCode());
3949
$this->assertNotEmpty($response->getBody());
4050

4151
$data = $response->bodyToArray();
42-
$this->assertEquals('foo', $data['name']);
43-
$this->assertEquals(1, $data['page']);
44-
$this->assertEquals(10, $data['limit']);
52+
$this->assertEquals('foo', $data['name']);
53+
$this->assertEquals(1, $data['page']);
54+
$this->assertEquals(10, $data['limit']);
4555

4656

4757
$response = $client->get('/api/search', [
@@ -50,13 +60,13 @@ public function testGetWithQueryRequest()
5060
'limit' => 100
5161
]);
5262

53-
$this->assertEquals(200, $response->getStatusCode());
63+
$this->assertEquals(200, $response->getStatusCode());
5464
$this->assertNotEmpty($response->getBody());
5565

5666
$data = $response->bodyToArray();
57-
$this->assertEquals('foo', $data['name']);
58-
$this->assertEquals(10, $data['page']);
59-
$this->assertEquals(100, $data['limit']);
67+
$this->assertEquals('foo', $data['name']);
68+
$this->assertEquals(10, $data['page']);
69+
$this->assertEquals(100, $data['limit']);
6070
}
6171

6272
public function testPostJsonRequest()
@@ -67,14 +77,14 @@ public function testPostJsonRequest()
6777
'userId' => 1
6878
];
6979
$client = new HttpClient(['headers' => ['Authorization' => 'Bearer secret_token']]);
70-
$response = $client->post(self::URL.'/api/post/data', [
80+
$response = $client->post(self::URL . '/api/post/data', [
7181
'title' => 'foo',
7282
'body' => 'bar',
7383
'userId' => 1
7484
], true);
7585

76-
$this->assertEquals(200, $response->getStatusCode());
77-
$this->assertEquals($dataToPost, $response->bodyToArray());
86+
$this->assertEquals(200, $response->getStatusCode());
87+
$this->assertEquals($dataToPost, $response->bodyToArray());
7888
}
7989

8090
public function testPostFormRequest()
@@ -85,18 +95,50 @@ public function testPostFormRequest()
8595
'userId' => 1
8696
];
8797
$client = new HttpClient(['headers' => ['Authorization' => 'Bearer secret_token']]);
88-
$response = $client->post(self::URL.'/api/post/data/form', $dataToPost);
98+
$response = $client->post(self::URL . '/api/post/data/form', $dataToPost);
8999

90-
$this->assertEquals(200, $response->getStatusCode());
91-
$this->assertEquals($dataToPost, $response->bodyToArray());
100+
$this->assertEquals(200, $response->getStatusCode());
101+
$this->assertEquals($dataToPost, $response->bodyToArray());
92102
}
93103

94104
public function testPostEmptyFormRequest()
95105
{
96106
$client = new HttpClient(['headers' => ['Authorization' => 'Bearer secret_token']]);
97-
$response = $client->post(self::URL.'/api/post/data/form', []);
107+
$response = $client->post(self::URL . '/api/post/data/form', []);
108+
109+
$this->assertEquals(400, $response->getStatusCode());
110+
}
111+
112+
public function testWrongOptions()
113+
{
114+
$this->expectException(LogicException::class);
115+
new HttpClient(['headers' => 'string']);
116+
}
117+
118+
public function testWrongOptions2()
119+
{
120+
$this->expectException(LogicException::class);
121+
new HttpClient(['options_not_supported' => 'value']);
122+
}
123+
124+
public function testWrongOptions3()
125+
{
126+
$this->expectException(LogicException::class);
127+
new HttpClient(['timeout' => 'string']);
128+
}
129+
130+
public function testWrongMethod()
131+
{
132+
$client = new HttpClient(['headers' => ['Authorization' => 'Bearer secret_token']]);
133+
$this->expectException(LogicException::class);
134+
$client->fetch(self::URL . '/api/data', ['method' => 'WRONG']);
135+
}
98136

99-
$this->assertEquals( 400, $response->getStatusCode() );
137+
public function testWrongUrl()
138+
{
139+
$client = new HttpClient(['headers' => ['Authorization' => 'Bearer secret_token']]);
140+
$this->expectException(LogicException::class);
141+
$client->fetch('WRONG_URL', ['method' => 'GET']);
100142
}
101143

102144

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /