Skip to content

Commit 3e7a98a

Browse files
committed
🔄 refactor(utils): Refactor REST Client
[x] Refactored REST client to include retry mechanism and custom logging [x] Added support for configurable request options [x] Improved error handling with ApiException BREAKING CHANGE: Removed support for old API endpoint
1 parent aafe5f5 commit 3e7a98a

File tree

6 files changed

+385
-65
lines changed

6 files changed

+385
-65
lines changed

readme.md

Lines changed: 155 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,178 @@
1-
Multipurpose PHP Rest Client
2-
=============================
1+
# PHP REST Client
32

4-
A multipurpose PHP rest client for consuming RESTful web services. This package is designed to be very simple and easy to use.
3+
A flexible and robust PHP REST Client with advanced features for handling API requests.
54

6-
### Installation
5+
## Features
76

8-
You can install the package via composer:
7+
- 🚀 Simple and intuitive API
8+
- 🔄 Automatic retry mechanism for failed requests
9+
- 📝 Comprehensive logging system
10+
- ⚡ Custom exception handling
11+
- 🔒 Configurable request options
12+
- 🛠 Extensible architecture
13+
14+
## Installation
15+
16+
Install via Composer:
917

1018
```bash
1119
composer require ay4t/php-rest-client
1220
```
1321

14-
### Description
15-
Example usage of the `Client` class.
22+
## Basic Usage
1623

17-
### Example Usage
24+
### Using Config Object (Recommended)
1825

1926
```php
27+
use Ay4t\RestClient\Client;
28+
use Ay4t\RestClient\Config\Config;
2029

30+
// Initialize config
2131
$config = new Config();
22-
$config->setBaseUri('https://api.groq.com/openai/v1')
23-
->setApiKey('your-api-key-here')
24-
// optional
25-
->setSecretKey('your-secret-key-here');
32+
$config->setBaseUri('https://api.example.com')
33+
->setApiKey('your-api-key-here');
34+
35+
$client = new Client($config);
36+
37+
// Make a GET request
38+
try {
39+
$response = $client->cmd('GET', 'users');
40+
print_r($response);
41+
} catch (Ay4t\RestClient\Exceptions\ApiException $e) {
42+
echo "Error: " . $e->getMessage();
43+
echo "HTTP Status: " . $e->getHttpStatusCode();
44+
echo "Response Body: " . $e->getResponseBody();
45+
}
46+
```
47+
48+
### Using Array Configuration (Alternative)
49+
50+
```php
51+
use Ay4t\RestClient\Client;
52+
53+
// Initialize with array config
54+
$config = [
55+
'base_uri' => 'https://api.example.com',
56+
'headers' => [
57+
'Authorization' => 'Bearer your-api-key-here'
58+
]
59+
];
2660

2761
$client = new Client($config);
28-
$response = $client->cmd('GET', 'models');
62+
```
63+
64+
## Advanced Features
65+
66+
### Custom Logging
67+
68+
```php
69+
use Ay4t\RestClient\Logger\DefaultLogger;
70+
use Ay4t\RestClient\Config\Config;
71+
72+
// Setup configuration
73+
$config = new Config();
74+
$config->setBaseUri('https://api.example.com')
75+
->setApiKey('your-api-key-here');
76+
77+
// Custom log file location
78+
$logger = new DefaultLogger('/path/to/custom.log');
79+
$client = new Client($config, $logger);
80+
81+
// Logs will include:
82+
// - Request details (method, URL, options)
83+
// - Response status and body
84+
// - Any errors that occur
85+
```
2986

30-
echo '<pre>';
31-
print_r($response);
32-
echo '</pre>';
87+
### Retry Mechanism
88+
89+
```php
90+
// Configure retry settings
91+
$client->setMaxRetries(5) // Maximum number of retry attempts
92+
->setRetryDelay(2000); // Delay between retries in milliseconds
93+
94+
// The client will automatically:
95+
// - Retry failed requests (except 4xx errors)
96+
// - Wait between attempts
97+
// - Throw ApiException after all retries fail
3398
```
3499

35-
or
100+
### Request Options
36101

37102
```php
38-
$cmd = $client->cmd('POST', 'chat/completions', [
39-
'model' => 'llama-3.1-70b-versatile',
40-
'messages' => [
41-
[
42-
'role' => 'user',
43-
'content' => 'hi, why is sea water salty?',
44-
]
103+
// Set global request options
104+
$client->setRequestOptions([
105+
'timeout' => 30,
106+
'verify' => false, // Disable SSL verification
107+
'headers' => [
108+
'User-Agent' => 'My Custom User Agent'
45109
]
46110
]);
111+
```
112+
113+
### Error Handling
114+
115+
```php
116+
use Ay4t\RestClient\Exceptions\ApiException;
117+
118+
try {
119+
$response = $client->cmd('POST', 'users', [
120+
'name' => 'John Doe',
121+
'email' => 'john@example.com'
122+
]);
123+
} catch (ApiException $e) {
124+
// Get detailed error information
125+
$statusCode = $e->getHttpStatusCode();
126+
$responseBody = $e->getResponseBody();
127+
$message = $e->getMessage();
128+
129+
// Handle different status codes
130+
switch ($statusCode) {
131+
case 404:
132+
echo "Resource not found";
133+
break;
134+
case 401:
135+
echo "Unauthorized access";
136+
break;
137+
default:
138+
echo "An error occurred: $message";
139+
}
140+
}
141+
```
142+
143+
## Implementing Custom Logger
144+
145+
You can implement your own logger by implementing the `LoggerInterface`:
146+
147+
```php
148+
use Ay4t\RestClient\Interfaces\LoggerInterface;
149+
150+
class MyCustomLogger implements LoggerInterface
151+
{
152+
public function logRequest(string $method, string $url, array $options): void
153+
{
154+
// Your custom request logging logic
155+
}
156+
157+
public function logResponse(int $statusCode, string $body): void
158+
{
159+
// Your custom response logging logic
160+
}
161+
162+
public function logError(\Throwable $exception): void
163+
{
164+
// Your custom error logging logic
165+
}
166+
}
167+
168+
// Use your custom logger
169+
$client = new Client($config, new MyCustomLogger());
170+
```
171+
172+
## Contributing
173+
174+
Contributions are welcome! Please feel free to submit a Pull Request.
175+
176+
## License
47177

48-
echo '<pre>';
49-
print_r($cmd);
50-
echo '</pre>';
51-
```
178+
This project is licensed under the MIT License - see the LICENSE file for details.

src/Client.php

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,99 @@
77
use GuzzleHttp\Exception\GuzzleException;
88
use Ay4t\RestClient\Abstracts\AbstractClient;
99
use Ay4t\RestClient\Interfaces\ClientInterface;
10+
use Ay4t\RestClient\Interfaces\LoggerInterface;
11+
use Ay4t\RestClient\Logger\DefaultLogger;
12+
use Ay4t\RestClient\Exceptions\ApiException;
13+
use Ay4t\RestClient\Config\Config;
1014

1115
class Client extends AbstractClient implements ClientInterface
1216
{
1317
use RequestTrait;
1418

1519
/**
16-
* $request_options
1720
* @var array
1821
*/
1922
private $request_options = [];
2023

24+
/**
25+
* @var LoggerInterface
26+
*/
27+
private $logger;
28+
29+
/**
30+
* @var int
31+
*/
32+
private $maxRetries = 3;
33+
34+
/**
35+
* @var int
36+
*/
37+
private $retryDelay = 1000; // milliseconds
38+
39+
/**
40+
* Client constructor.
41+
* @param array|Config $config Configuration array or Config object
42+
* @param LoggerInterface|null $logger Optional logger instance
43+
*/
44+
public function __construct($config = [], LoggerInterface $logger = null)
45+
{
46+
if (is_array($config)) {
47+
$configObj = new Config();
48+
if (isset($config['base_uri'])) {
49+
$configObj->setBaseUri($config['base_uri']);
50+
}
51+
if (isset($config['headers']['Authorization'])) {
52+
$apiKey = str_replace('Bearer ', '', $config['headers']['Authorization']);
53+
$configObj->setApiKey($apiKey);
54+
}
55+
$config = $configObj;
56+
}
57+
58+
parent::__construct($config);
59+
$this->logger = $logger ?? new DefaultLogger();
60+
}
61+
62+
/**
63+
* Set the maximum number of retries for failed requests
64+
*/
65+
public function setMaxRetries(int $maxRetries): self
66+
{
67+
$this->maxRetries = $maxRetries;
68+
return $this;
69+
}
70+
71+
/**
72+
* Set the delay between retries in milliseconds
73+
*/
74+
public function setRetryDelay(int $delay): self
75+
{
76+
$this->retryDelay = $delay;
77+
return $this;
78+
}
79+
2180
/**
2281
* setRequestOptions
2382
* @param array $request_options
24-
* @return void
83+
* @return self
2584
*/
26-
public function setRequestOptions(array $request_options) {
85+
public function setRequestOptions(array $request_options): self {
2786
$this->request_options = $request_options;
2887
return $this;
2988
}
30-
3189

3290
/**
33-
* Perform a request to the API server.
91+
* Perform a request to the API server with retry mechanism.
3492
*
3593
* @param string $method The HTTP method to use for the request.
3694
* @param string $command The command to send to the API.
3795
* @param array $params The parameters to send in the request.
3896
* @return mixed The response from the API server.
39-
* @throws \Exception If the request to the API server fails.
97+
* @throws ApiException If the request to the API server fails after all retries.
4098
*/
4199
public function cmd(string $method = 'GET', string $command, array $params = [])
42100
{
43101
$url = $this->config->getBaseUri() . '/' . $command;
44-
$requestOptions = [];
102+
$requestOptions = [];
45103

46104
// Merge default options with user-provided options
47105
$defaultOptions = [
@@ -57,12 +115,43 @@ public function cmd(string $method = 'GET', string $command, array $params = [])
57115

58116
$requestOptions = array_merge($defaultOptions, $this->request_options);
59117

60-
try {
61-
$response = $this->client->request($method, $url, $requestOptions);
62-
return json_decode($response->getBody()->getContents(), $this->response_associative);
63-
} catch (GuzzleException $e) {
64-
// Error handling
65-
throw new \Exception('API request failed: ' . $e->getMessage());
118+
// Log the request
119+
$this->logger->logRequest($method, $url, $requestOptions);
120+
121+
$attempts = 0;
122+
$lastException = null;
123+
124+
while ($attempts < $this->maxRetries) {
125+
try {
126+
$response = $this->client->request($method, $url, $requestOptions);
127+
$body = $response->getBody()->getContents();
128+
129+
// Log successful response
130+
$this->logger->logResponse($response->getStatusCode(), $body);
131+
132+
return json_decode($body, $this->response_associative);
133+
} catch (GuzzleException $e) {
134+
$lastException = $e;
135+
$this->logger->logError($e);
136+
137+
// Don't retry if it's a client error (4xx)
138+
if ($e->getCode() >= 400 && $e->getCode() < 500) {
139+
break;
140+
}
141+
142+
$attempts++;
143+
if ($attempts < $this->maxRetries) {
144+
usleep($this->retryDelay * 1000); // Convert milliseconds to microseconds
145+
}
146+
}
66147
}
148+
149+
// If we get here, all retries failed
150+
throw new ApiException(
151+
'API request failed after ' . $attempts . ' attempts: ' . $lastException->getMessage(),
152+
$lastException->getCode() ?? 0,
153+
$lastException->getResponse() ? $lastException->getResponse()->getBody()->getContents() : null,
154+
$lastException
155+
);
67156
}
68157
}

src/Exceptions/ApiException.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Ay4t\RestClient\Exceptions;
4+
5+
class ApiException extends \Exception
6+
{
7+
private $httpStatusCode;
8+
private $responseBody;
9+
10+
public function __construct(string $message, int $httpStatusCode = 0, $responseBody = null, \Throwable $previous = null)
11+
{
12+
parent::__construct($message, 0, $previous);
13+
$this->httpStatusCode = $httpStatusCode;
14+
$this->responseBody = $responseBody;
15+
}
16+
17+
public function getHttpStatusCode(): int
18+
{
19+
return $this->httpStatusCode;
20+
}
21+
22+
public function getResponseBody()
23+
{
24+
return $this->responseBody;
25+
}
26+
}

0 commit comments

Comments
 (0)