Created
January 15, 2025 13:06
-
-
Save vichfs/5834e79d56be11a781d32e2b6762a83a to your computer and use it in GitHub Desktop.
Simple Rest API Client in PHP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class RestApiClient | |
{ | |
private string $baseUrl; | |
private array $headers; | |
private int $timeout; | |
private bool $verifySSL; | |
private ?string $apiKey; | |
private array $allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']; | |
public function __construct( | |
string $baseUrl, | |
?string $apiKey = null, | |
int $timeout = 30, | |
bool $verifySSL = true | |
) | |
{ | |
$this->baseUrl = rtrim($baseUrl, '/'); | |
$this->apiKey = $apiKey; | |
$this->timeout = max(1, $timeout); | |
$this->verifySSL = $verifySSL; | |
$this->headers = [ | |
'Accept' => 'application/json', | |
'Content-Type' => 'application/json', | |
'User-Agent' => 'SecureRestApiClient/1.0' | |
]; | |
if ($this->apiKey) { | |
$this->headers['Authorization'] = 'Bearer ' . $this->apiKey; | |
} | |
} | |
/** | |
* Add or update a custom header | |
*/ | |
public function setHeader(string $name, string $value): self | |
{ | |
$this->headers[$name] = $this->sanitizeHeader($value); | |
return $this; | |
} | |
/** | |
* Make an API request | |
* | |
* @throws InvalidArgumentException | |
* @throws RuntimeException | |
*/ | |
public function request( | |
string $method, | |
string $endpoint, | |
array $data = [], | |
array $queryParams = [] | |
): array | |
{ | |
$method = strtoupper($method); | |
if (!in_array($method, $this->allowedMethods, true)) { | |
throw new InvalidArgumentException('Invalid HTTP method'); | |
} | |
// Sanitize and build the URL | |
$endpoint = ltrim($endpoint, '/'); | |
$url = $this->baseUrl . '/' . $this->sanitizeUrl($endpoint); | |
if (!empty($queryParams)) { | |
$url .= '?' . http_build_query($this->sanitizeData($queryParams)); | |
} | |
// Initialize cURL | |
$ch = curl_init(); | |
// Set common cURL options | |
curl_setopt_array($ch, [ | |
CURLOPT_URL => $url, | |
CURLOPT_RETURNTRANSFER => true, | |
CURLOPT_TIMEOUT => $this->timeout, | |
CURLOPT_SSL_VERIFYPEER => $this->verifySSL, | |
CURLOPT_SSL_VERIFYHOST => $this->verifySSL ? 2 : 0, | |
CURLOPT_CUSTOMREQUEST => $method | |
]); | |
// Set headers | |
$formattedHeaders = []; | |
foreach ($this->headers as $name => $value) { | |
$formattedHeaders[] = "$name: $value"; | |
} | |
curl_setopt($ch, CURLOPT_HTTPHEADER, $formattedHeaders); | |
// Handle request body for POST, PUT, PATCH | |
if (in_array($method, ['POST', 'PUT', 'PATCH'], true) && !empty($data)) { | |
$sanitizedData = $this->sanitizeData($data); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($sanitizedData)); | |
} | |
// Execute request | |
$response = curl_exec($ch); | |
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
$error = curl_error($ch); | |
$errno = curl_errno($ch); | |
curl_close($ch); | |
// Handle errors | |
if ($errno !== 0) { | |
throw new RuntimeException("cURL Error ($errno): $error"); | |
} | |
// Parse response | |
$responseData = json_decode($response, true); | |
if (json_last_error() !== JSON_ERROR_NONE) { | |
throw new RuntimeException('Invalid JSON response: ' . json_last_error_msg()); | |
} | |
// Handle non-200 status codes | |
if ($statusCode < 200 || $statusCode >= 300) { | |
throw new RuntimeException( | |
"HTTP Error $statusCode: " . | |
($responseData['message'] ?? 'Unknown error') | |
); | |
} | |
return $responseData; | |
} | |
/** | |
* Convenience method for GET requests | |
*/ | |
public function get(string $endpoint, array $queryParams = []): array | |
{ | |
return $this->request('GET', $endpoint, [], $queryParams); | |
} | |
/** | |
* Convenience method for POST requests | |
*/ | |
public function post(string $endpoint, array $data = []): array | |
{ | |
return $this->request('POST', $endpoint, $data); | |
} | |
/** | |
* Convenience method for PUT requests | |
*/ | |
public function put(string $endpoint, array $data = []): array | |
{ | |
return $this->request('PUT', $endpoint, $data); | |
} | |
/** | |
* Convenience method for DELETE requests | |
*/ | |
public function delete(string $endpoint): array | |
{ | |
return $this->request('DELETE', $endpoint); | |
} | |
/** | |
* Convenience method for PATCH requests | |
*/ | |
public function patch(string $endpoint, array $data = []): array | |
{ | |
return $this->request('PATCH', $endpoint, $data); | |
} | |
/** | |
* Sanitize URL components to prevent injection attacks | |
*/ | |
private function sanitizeUrl(string $url): string | |
{ | |
return filter_var( | |
$url, | |
FILTER_SANITIZE_URL, | |
FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH | |
); | |
} | |
/** | |
* Sanitize header values | |
*/ | |
private function sanitizeHeader(string $value): string | |
{ | |
return preg_replace('/[^\x20-\x7E]/', '', $value); | |
} | |
/** | |
* Recursively sanitize data arrays | |
*/ | |
private function sanitizeData(array $data): array | |
{ | |
$sanitized = []; | |
foreach ($data as $key => $value) { | |
if (is_array($value)) { | |
$sanitized[$key] = $this->sanitizeData($value); | |
} else { | |
$sanitized[$key] = $this->sanitizeValue($value); | |
} | |
} | |
return $sanitized; | |
} | |
/** | |
* Sanitize individual values | |
*/ | |
private function sanitizeValue($value) | |
{ | |
if (is_string($value)) { | |
// Remove null bytes and other potentially dangerous characters | |
return str_replace( | |
["\0", "\r", "\n"], | |
'', | |
strip_tags($value) | |
); | |
} | |
return $value; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment