athlon1600/php-curl-client
Lightweight, extensible PHP cURL client (PHP 7.3–8.4). Make GET/POST or fully customized requests and always receive a standardized Response with status, body, error, and typed cURL info for IDE autocomplete.
Installation:
composer require athlon1600/php-curl-client
Ensure ext-curl and ext-json are enabled in your PHP environment.
First Request:
use Curl\Client;
$client = new Client();
$response = $client->get('https://api.example.com/data');
Key Response Properties:
$response->status (HTTP status code)$response->body (raw response body)$response->error (curl error or null)$response->info (CurlInfo object with metadata like headers, redirects, etc.)$client = new Client();
$response = $client->get('https://api.github.com/users/octocat');
// Parse JSON response
$data = json_decode($response->body, true);
Leverage convenience methods for common HTTP verbs:
// GET
$client->get('https://api.example.com/users');
// POST
$client->post('https://api.example.com/users', ['name' => 'John']);
// PUT/PATCH/DELETE
$client->put('https://api.example.com/users/1', ['name' => 'Updated']);
$client->delete('https://api.example.com/users/1');
For non-standard requests (e.g., custom headers, proxies):
$client->request(
'GET',
'https://api.example.com/protected',
null, // No body
[
'Authorization' => 'Bearer token123',
'Accept' => 'application/json'
],
[
CURLOPT_PROXY => 'http://proxy.example.com:8080',
CURLOPT_TIMEOUT => 30
]
);
Use BrowserClient for session-like interactions (cookies, user-agent):
use Curl\BrowserClient;
$browser = new BrowserClient();
$browser->setUserAgent('Mozilla/5.0 (Windows NT 10.0)');
$browser->setStorageDirectory(storage_path('cookies')); // Persist cookies
// Subsequent requests share cookies
$browser->get('https://example.com/login');
$browser->post('https://example.com/login', ['email' => 'user@example.com']);
Validate responses in a Laravel service:
public function fetchData()
{
$response = $client->get('https://api.example.com/data');
if ($response->error) {
throw new \RuntimeException("cURL Error: " . $response->error);
}
if ($response->status !== 200) {
throw new \RuntimeException("HTTP Error: " . $response->status);
}
return json_decode($response->body, true);
}
Wrap the client for consistency with Laravel’s Http facade:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton('curl.client', function () {
return new Client();
});
}
// Usage in controllers
$response = app('curl.client')->get('https://api.example.com');
Mock responses in PHPUnit:
public function testApiCall()
{
$client = new Client();
$response = $client->get('https://api.example.com/data');
$this->assertEquals(200, $response->status);
$this->assertNotNull($response->body);
$this->assertNull($response->error);
}
POST Redirects:
CURLOPT_FOLLOWLOCATION converts POST to GET on 3xx redirects. To preserve the method, set:
$client->request('POST', 'url', $data, [], [CURLOPT_POSTREDIR => 'FOLLOW']);
Custom Options Override:
// Correct (from v1.1.2 fix)
$customOptions + $defaultOptions
Cookie Storage:
BrowserClient, ensure the storage directory is writable:
$browser->setStorageDirectory(storage_path('framework/sessions'));
IDE Autocomplete:
$response->info returns a CurlInfo object with IDE-friendly properties (e.g., info->http_code, info->url).HTTPBin Redirects:
httpbin.org/redirect-to in tests (broken endpoint; use workarounds).Inspect cURL Info:
dump($response->info->__toArray()); // Full cURL metadata
Enable Verbose Output:
$client->request('GET', 'url', null, [], [CURLOPT_VERBOSE => true]);
Check for Redirects:
if ($response->info->redirect_count > 0) {
logger()->debug("Redirected to: " . $response->info->url);
}
Custom Response Handling:
Extend the Client class to add middleware:
class CustomClient extends Client
{
public function request($method, $url, $data = null, $headers = [], $options = [])
{
// Pre-process request
$response = parent::request($method, $url, $data, $headers, $options);
// Post-process response
if ($response->status === 401) {
$this->reauthenticate();
}
return $response;
}
}
PSR-18 Compatibility (Future-Proofing): Monitor the TODO in the README for PSR-7/PSR-18 updates. For now, wrap responses:
use Psr\Http\Message\ResponseInterface;
$psrResponse = new class($response) implements ResponseInterface {
public function __construct(private $rawResponse) {}
public function getBody() { return new Stream($this->rawResponse->body); }
// Implement other PSR-7 methods...
};
Retry Logic: Add exponential backoff for transient failures:
public function getWithRetry($url, $maxRetries = 3)
{
$retries = 0;
while ($retries < $maxRetries) {
$response = $client->get($url);
if ($response->status !== 503) break;
sleep(2 ** $retries);
$retries++;
}
return $response;
}
Default Options: Override globally via constructor:
$client = new Client([
CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false // Disable for testing only!
]);
User-Agent Spoofing:
Use BrowserClient or set headers:
$client->request('GET', 'url', null, ['User-Agent' => 'MyApp/1.0']);
SSL Verification: Disable only in development:
$client->request('GET', 'url', null, [], [CURLOPT_SSL_VERIFYPEER => false]);
How can I help you explore Laravel packages today?