Skip to content

Commit 59216aa

Browse files
authored
Merge pull request #6 from Thavarshan/refactor/use-guzzle-psr7
Use guzzlehttp/psr7 instead of symfony/http-foundation
2 parents e95b135 + f8bf5d6 commit 59216aa

File tree

5 files changed

+118
-49
lines changed

5 files changed

+118
-49
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ FetchPHP provides three main functions:
3131

3232
### **Custom Guzzle Client Usage**
3333

34-
By default, FetchPHP uses a single instance of the Guzzle client shared across all requests. However, you can provide your own Guzzle client through the `options` parameter of both `fetch` and `fetch_async`. This gives you full control over the client configuration, including base URI, headers, timeouts, and more.
34+
By default, FetchPHP uses a singleton instance of the Guzzle client shared across all requests. However, you can provide your own Guzzle client through the `options` parameter of both `fetch` and `fetch_async`. This gives you full control over the client configuration, including base URI, headers, timeouts, and more.
3535

3636
### **How to Provide a Custom Guzzle Client**
3737

@@ -92,6 +92,8 @@ echo $response->statusText();
9292

9393
#### **Available Response Methods**
9494

95+
The `Response` class, now based on Guzzle’s `Psr7\Response`, provides various methods to interact with the response data:
96+
9597
- **`json(bool $assoc = true)`**: Decodes the response body as JSON. If `$assoc` is `true`, it returns an associative array. If `false`, it returns an object.
9698
- **`text()`**: Returns the response body as plain text.
9799
- **`blob()`**: Returns the response body as a PHP stream resource (like a "blob").
@@ -238,7 +240,9 @@ echo $response->text(); // Outputs error message
238240

239241
$promise = fetch_async('https://nonexistent-url.com');
240242

241-
$promise->then(function ($response) {
243+
$promise->then(function ($
244+
245+
response) {
242246
echo $response->text();
243247
}, function ($exception) {
244248
echo "Request failed: " . $exception->getMessage();
@@ -281,9 +285,7 @@ echo $response->statusText();
281285

282286
---
283287

284-
### **Working
285-
286-
with the Response Object**
288+
### **Working with the Response Object**
287289

288290
The `Response` class provides convenient methods for interacting with the response body, headers, and status codes.
289291

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "jerome/fetch-php",
33
"description": "The JavaScript fetch API for PHP.",
4-
"version": "1.1.1",
4+
"version": "1.2.0",
55
"type": "library",
66
"license": "MIT",
77
"authors": [
@@ -26,8 +26,8 @@
2626
"require": {
2727
"php": "^8.2",
2828
"guzzlehttp/guzzle": "^7.8",
29-
"psr/http-message": "^1.0 || ^2.0",
30-
"symfony/http-foundation": "^6.0 || ^7.1"
29+
"guzzlehttp/psr7": "^2.7",
30+
"psr/http-message": "^1.0 || ^2.0"
3131
},
3232
"require-dev": {
3333
"friendsofphp/php-cs-fixer": "^3.64",

src/Http.php

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
use GuzzleHttp\Exception\RequestException;
88
use GuzzleHttp\Promise\PromiseInterface;
99
use GuzzleHttp\Psr7\MultipartStream;
10-
use GuzzleHttp\Psr7\Response as GuzzleResponse;
1110
use Psr\Http\Message\ResponseInterface;
12-
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
1311

1412
class Http
1513
{
@@ -48,6 +46,16 @@ public static function setClient(Client $client): void
4846
self::$client = $client;
4947
}
5048

49+
/**
50+
* Reset the Guzzle client instance.
51+
*
52+
* @return void
53+
*/
54+
public static function resetClient(): void
55+
{
56+
self::$client = null;
57+
}
58+
5159
/**
5260
* Helper function to perform HTTP requests using Guzzle.
5361
*
@@ -97,15 +105,27 @@ public static function makeRequest(
97105

98106
if ($async) {
99107
return $client->requestAsync($method, $url, $requestOptions)->then(
100-
fn (ResponseInterface $response) => new Response($response),
108+
fn (ResponseInterface $response) => new Response(
109+
$response->getStatusCode(),
110+
$response->getHeaders(),
111+
(string) $response->getBody(),
112+
$response->getProtocolVersion(),
113+
$response->getReasonPhrase()
114+
),
101115
fn (RequestException $e) => self::handleRequestException($e)
102116
);
103117
}
104118

105119
try {
106120
$response = $client->request($method, $url, $requestOptions);
107121

108-
return new Response($response);
122+
return new Response(
123+
$response->getStatusCode(),
124+
$response->getHeaders(),
125+
(string) $response->getBody(),
126+
$response->getProtocolVersion(),
127+
$response->getReasonPhrase()
128+
);
109129
} catch (RequestException $e) {
110130
return self::handleRequestException($e);
111131
}
@@ -123,9 +143,17 @@ protected static function handleRequestException(RequestException $e): Response
123143
$response = $e->getResponse();
124144

125145
if ($response) {
126-
return new Response($response);
146+
return new Response(
147+
$response->getStatusCode(),
148+
$response->getHeaders(),
149+
(string) $response->getBody(),
150+
$response->getProtocolVersion(),
151+
$response->getReasonPhrase()
152+
);
127153
}
128154

155+
error_log('HTTP Error: ' . $e->getMessage());
156+
129157
return self::createErrorResponse($e);
130158
}
131159

@@ -138,12 +166,10 @@ protected static function handleRequestException(RequestException $e): Response
138166
*/
139167
protected static function createErrorResponse(RequestException $e): Response
140168
{
141-
$mockResponse = new GuzzleResponse(
142-
SymfonyResponse::HTTP_INTERNAL_SERVER_ERROR,
169+
return new Response(
170+
500,
143171
[],
144172
$e->getMessage()
145173
);
146-
147-
return new Response($mockResponse);
148174
}
149175
}

src/Response.php

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
namespace Fetch;
44

5-
use Psr\Http\Message\ResponseInterface;
5+
use GuzzleHttp\Psr7\Response as BaseResponse;
66
use RuntimeException;
7-
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
87

9-
class Response extends SymfonyResponse
8+
class Response extends BaseResponse
109
{
1110
/**
1211
* The buffered content of the body.
@@ -18,20 +17,21 @@ class Response extends SymfonyResponse
1817
/**
1918
* Create new response instance.
2019
*
21-
* @param \Psr\Http\Message\ResponseInterface $guzzleResponse
22-
*
23-
* @return void
20+
* @param int $status
21+
* @param array $headers
22+
* @param string $body
23+
* @param string $version
24+
* @param string $reason
2425
*/
25-
public function __construct(protected ResponseInterface $guzzleResponse)
26-
{
27-
// Buffer the body contents to allow multiple reads
28-
$this->bodyContents = (string) $guzzleResponse->getBody();
29-
30-
parent::__construct(
31-
$this->bodyContents,
32-
$guzzleResponse->getStatusCode(),
33-
$guzzleResponse->getHeaders()
34-
);
26+
public function __construct(
27+
int $status = 200,
28+
array $headers = [],
29+
string $body = '',
30+
string $version = '1.1',
31+
string $reason = null
32+
) {
33+
parent::__construct($status, $headers, $body, $version, $reason);
34+
$this->bodyContents = (string) $this->getBody();
3535
}
3636

3737
/**
@@ -41,14 +41,20 @@ public function __construct(protected ResponseInterface $guzzleResponse)
4141
*
4242
* @return mixed
4343
*/
44-
public function json(bool $assoc = true)
44+
public function json(bool $assoc = true, bool $throwOnError = true)
4545
{
4646
$decoded = json_decode($this->bodyContents, $assoc);
47-
if (json_last_error() !== \JSON_ERROR_NONE) {
47+
$jsonError = json_last_error();
48+
49+
if ($jsonError === \JSON_ERROR_NONE) {
50+
return $decoded;
51+
}
52+
53+
if ($throwOnError) {
4854
throw new RuntimeException('Failed to decode JSON: ' . json_last_error_msg());
4955
}
5056

51-
return $decoded;
57+
return null; // or return an empty array/object depending on your needs.
5258
}
5359

5460
/**
@@ -95,8 +101,7 @@ public function arrayBuffer(): string
95101
*/
96102
public function statusText(): string
97103
{
98-
return $this->statusText
99-
?? SymfonyResponse::$statusTexts[$this->getStatusCode()];
104+
return $this->getReasonPhrase() ?: 'No reason phrase available';
100105
}
101106

102107
/**

tests/ResponseTest.php

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,77 @@
55

66
test('Response::json() correctly decodes JSON', function () {
77
$guzzleResponse = new GuzzleResponse(200, ['Content-Type' => 'application/json'], '{"key":"value"}');
8-
$response = new Response($guzzleResponse);
8+
$response = new Response(
9+
$guzzleResponse->getStatusCode(),
10+
$guzzleResponse->getHeaders(),
11+
(string) $guzzleResponse->getBody(),
12+
$guzzleResponse->getProtocolVersion(),
13+
$guzzleResponse->getReasonPhrase()
14+
);
915

1016
$json = $response->json();
1117
expect($json)->toMatchArray(['key' => 'value']);
1218
});
1319

1420
test('Response::text() correctly retrieves plain text', function () {
1521
$guzzleResponse = new GuzzleResponse(200, [], 'Plain text content');
16-
$response = new Response($guzzleResponse);
22+
$response = new Response(
23+
$guzzleResponse->getStatusCode(),
24+
$guzzleResponse->getHeaders(),
25+
(string) $guzzleResponse->getBody(),
26+
$guzzleResponse->getProtocolVersion(),
27+
$guzzleResponse->getReasonPhrase()
28+
);
1729

1830
expect($response->text())->toBe('Plain text content');
1931
});
2032

2133
test('Response::blob() correctly retrieves blob (stream)', function () {
2234
$guzzleResponse = new GuzzleResponse(200, [], 'Binary data');
23-
$response = new Response($guzzleResponse);
35+
$response = new Response(
36+
$guzzleResponse->getStatusCode(),
37+
$guzzleResponse->getHeaders(),
38+
(string) $guzzleResponse->getBody(),
39+
$guzzleResponse->getProtocolVersion(),
40+
$guzzleResponse->getReasonPhrase()
41+
);
2442

2543
$blob = $response->blob();
2644
expect(is_resource($blob))->toBeTrue();
2745
});
2846

2947
test('Response::arrayBuffer() correctly retrieves binary data as string', function () {
3048
$guzzleResponse = new GuzzleResponse(200, [], 'Binary data');
31-
$response = new Response($guzzleResponse);
49+
$response = new Response(
50+
$guzzleResponse->getStatusCode(),
51+
$guzzleResponse->getHeaders(),
52+
(string) $guzzleResponse->getBody(),
53+
$guzzleResponse->getProtocolVersion(),
54+
$guzzleResponse->getReasonPhrase()
55+
);
3256

3357
expect($response->arrayBuffer())->toBe('Binary data');
3458
});
3559

3660
test('Response::statusText() correctly retrieves status text', function () {
3761
$guzzleResponse = new GuzzleResponse(200);
38-
$response = new Response($guzzleResponse);
62+
$response = new Response(
63+
$guzzleResponse->getStatusCode(),
64+
$guzzleResponse->getHeaders(),
65+
(string) $guzzleResponse->getBody(),
66+
$guzzleResponse->getProtocolVersion(),
67+
$guzzleResponse->getReasonPhrase()
68+
);
3969

4070
expect($response->statusText())->toBe('OK');
4171
});
4272

4373
test('Response status helper methods work correctly', function () {
44-
$informationalResponse = new Response(new GuzzleResponse(100));
45-
$successfulResponse = new Response(new GuzzleResponse(200));
46-
$redirectionResponse = new Response(new GuzzleResponse(301));
47-
$clientErrorResponse = new Response(new GuzzleResponse(404));
48-
$serverErrorResponse = new Response(new GuzzleResponse(500));
74+
$informationalResponse = new Response(100);
75+
$successfulResponse = new Response(200);
76+
$redirectionResponse = new Response(301);
77+
$clientErrorResponse = new Response(404);
78+
$serverErrorResponse = new Response(500);
4979

5080
expect($informationalResponse->isInformational())->toBeTrue();
5181
expect($successfulResponse->ok())->toBeTrue();
@@ -57,7 +87,13 @@
5787
test('Response handles error gracefully', function () {
5888
$errorMessage = 'Something went wrong';
5989
$guzzleResponse = new GuzzleResponse(500, [], $errorMessage);
60-
$response = new Response($guzzleResponse);
90+
$response = new Response(
91+
$guzzleResponse->getStatusCode(),
92+
$guzzleResponse->getHeaders(),
93+
(string) $guzzleResponse->getBody(),
94+
$guzzleResponse->getProtocolVersion(),
95+
$guzzleResponse->getReasonPhrase()
96+
);
6197

6298
expect($response->getStatusCode())->toBe(500);
6399
expect($response->text())->toBe($errorMessage);

0 commit comments

Comments
 (0)