Skip to content

Instantly share code, notes, and snippets.

@vichfs
Created January 15, 2025 13:06
Show Gist options
  • Save vichfs/5834e79d56be11a781d32e2b6762a83a to your computer and use it in GitHub Desktop.
Save vichfs/5834e79d56be11a781d32e2b6762a83a to your computer and use it in GitHub Desktop.
Simple Rest API Client in PHP
<?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