avris/http
Lightweight HTTP client for PHP/Laravel with a clean, fluent API for making requests and working with responses. Designed for simple integrations, sane defaults, and easy customization of headers, query params, timeouts, and body formats.
Installation:
composer require avris/http
No service provider registration is required—this is a standalone package.
First Use Case: Create a request and send it via a client:
use Avris\Http\Request;
use Avris\Http\Client;
$request = new Request('GET', 'https://api.example.com/users');
$client = new Client();
$response = $client->send($request);
// Extract data
$data = $response->json();
Where to Look First:
src/Request.php: Core request builder with methods like withHeader(), withBody().src/Response.php: Response handling (e.g., json(), status()).src/Client.php: Low-level transport (can be swapped, e.g., for Guzzle/Symfony).src/Middleware/: Built-in middleware (e.g., RetryMiddleware, JsonMiddleware).Immutable Request Building:
$request = (new Request('POST', '/users'))
->withHeader('Accept', 'application/json')
->withHeader('Authorization', 'Bearer token')
->withBody(json_encode(['name' => 'John']));
Middleware Stack (Laravel-Inspired):
$client = new Client();
$client->pushMiddleware(new RetryMiddleware(3)); // Retry failed requests
$client->pushMiddleware(new JsonMiddleware()); // Auto-encode/decode JSON
Response Transformation:
$response = $client->send($request);
$data = $response->json(); // Parse JSON body
$status = $response->status(); // Get HTTP status code
Integration with Laravel Controllers:
use Avris\Http\Client;
use Illuminate\Support\Facades\Http as LaravelHttp;
// Option 1: Standalone usage
$client = new Client();
$response = $client->get('https://api.example.com/data');
// Option 2: Wrap Laravel's HTTP client (if needed)
$laravelClient = new class extends Client {
protected function createTransport() {
return new LaravelHttp();
}
};
Testing with Mocks:
// Mock a response for unit tests
$mockResponse = new Response(200, [], '{"id": 1}');
$client->setMockResponse($mockResponse);
$response = $client->get('/test');
$this->assertEquals(1, $response->json()['id']);
API Client Layer: Create a dedicated service class to encapsulate API logic:
class StripeService {
protected $client;
public function __construct(Client $client) {
$this->client = $client;
}
public function createCustomer(array $data) {
$request = (new Request('POST', '/customers'))
->withHeader('Authorization', 'Bearer ' . config('stripe.key'))
->withBody(json_encode($data));
return $this->client->send($request)->json();
}
}
Request/Response Middleware: Use middleware to add cross-cutting concerns (e.g., logging, auth):
$client->pushMiddleware(function ($request, callable $next) {
$start = microtime(true);
$response = $next($request);
$duration = microtime(true) - $start;
// Log request/response
logger()->info("HTTP call took {$duration}s", [
'method' => $request->method(),
'url' => $request->url(),
'status' => $response->status(),
]);
return $response;
});
Transport Abstraction: Swap the underlying HTTP client (e.g., Guzzle, Symfony, or cURL):
// Use Guzzle as the transport
$client = new Client(new GuzzleTransport());
Immutable Requests:
// ❌ Won't work (requests are immutable)
$request->withHeader('X-Test', '123');
$request->withHeader('X-Test', '456'); // Overwrites previous header
// ✅ Correct (creates a new request)
$request = $request->withHeader('X-Test', '123');
$request = $request->withHeader('X-Test', '456');
Middleware Order:
$client->pushMiddleware($middleware1); // Executes second
$client->pushMiddleware($middleware2); // Executes first
Response Parsing:
Response::json() throws an exception if the body isn’t valid JSON. Handle it gracefully:
try {
$data = $response->json();
} catch (JsonException $e) {
$data = $response->text(); // Fallback to raw text
}
Transport Lock-In:
Laravel Integration Quirks:
Illuminate\Http\Request or Response. Use it alongside Laravel’s native classes or create adapters.avris/http middleware with Laravel’s middleware pipeline unless explicitly designed for it.Log Requests/Responses: Add a middleware to log raw request/response data:
$client->pushMiddleware(function ($request, callable $next) {
logger()->debug('Request:', [
'method' => $request->method(),
'url' => $request->url(),
'headers' => $request->headers()->all(),
'body' => $request->body(),
]);
$response = $next($request);
logger()->debug('Response:', [
'status' => $response->status(),
'headers' => $response->headers()->all(),
'body' => $response->body(),
]);
return $response;
});
Check Status Codes:
Use Response::isSuccessful() or Response::isClientError() to handle errors:
if (!$response->isSuccessful()) {
throw new RuntimeException("HTTP error: {$response->status()}");
}
Handle Redirects: The default transport does not follow redirects. Use a middleware or custom transport if needed:
$client->pushMiddleware(new FollowRedirectsMiddleware());
Custom Transports:
Implement the TransportInterface to use other HTTP clients:
class GuzzleTransport implements TransportInterface {
public function send(Request $request): Response {
$guzzleResponse = $this->client->request(
$request->method(),
$request->url(),
[
'headers' => $request->headers()->all(),
'body' => $request->body(),
]
);
return new Response(
$guzzleResponse->getStatusCode(),
$guzzleResponse->getHeaders(),
$guzzleResponse->getBody()->getContents()
);
}
}
Request/Response Factories:
Extend the Request or Response classes to add custom methods:
class CustomRequest extends Request {
public function withAuthToken(string $token): self {
return $this->withHeader('Authorization', "Bearer {$token}");
}
}
Middleware Utilities: Create reusable middleware for common tasks (e.g., rate limiting, retries):
class RateLimitMiddleware implements MiddlewareInterface {
public function __invoke(Request $request, callable $next): Response {
// Check rate limits
if ($this->isOverLimit($request)) {
return new Response(429, [], 'Too Many Requests');
}
return $next($request);
}
}
Default Headers: Set global default headers via middleware:
$client->pushMiddleware(function ($request) {
return $request->withHeader('User-Agent', 'MyApp/1.0');
});
Timeouts: Configure timeouts in the transport layer (e.g., cURL options or Guzzle config).
SSL Verification: Disable SSL verification only for testing (never in production):
$client = new Client(new CurlTransport([
CURLOPT_SSL_VERIFYPEER
How can I help you explore Laravel packages today?