Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Http Adapter Laravel Package

toin0u/http-adapter

A lightweight HTTP adapter for PHP that wraps different HTTP clients behind a single consistent interface, letting you swap cURL, Guzzle, or other backends without changing application code. Ideal for flexible, decoupled request handling.

View on GitHub
Deep Wiki
Context7

Getting Started

toin0u/http-adapter is a thin, pre-PSR-7 HTTP client abstraction: one interface, a handful of concrete adapters (cURL being the one you'll actually use), and a plain response value object. It predates Guzzle becoming the default, so treat it as legacy-friendly plumbing, not a modern HTTP stack.

Install

composer require toin0u/http-adapter

There is no service provider, no facade, no auto-discovery. You get a library and nothing else. Confirm ext-curl is present (php -m | grep curl) — the cURL adapter is the only one worth using in production.

Where to look first

  1. src/HttpAdapter/Adapter/ — the concrete transports (CurlAdapter, and typically socket/stream variants). Read CurlAdapter end to end; it's short and it is the package's behaviour.
  2. The adapter interface — the contract you'll bind in the container and mock in tests.
  3. The response/exception classes — they define what you actually get back and what blows up.

First request

use HttpAdapter\Adapter\CurlAdapter;

$http = new CurlAdapter();

$response = $http->get('https://api.example.com/v1/health', [
    'Accept' => 'application/json',
]);

$response->getStatusCode(); // 200
$response->getContent();    // raw body string

First real use case: you've inherited a service class that calls file_get_contents() or hand-rolled curl_* in three places, with no timeout and no way to test it. Swap those calls for an injected adapter, put it behind a Laravel binding, and you immediately gain a seam for stubbing plus a single place to configure timeouts and auth. That's the whole value proposition — don't ask it for more.

Implementation Patterns

Bind the interface, not the class

The single most important pattern. Never let CurlAdapter leak into application code.

// app/Providers/HttpAdapterServiceProvider.php
use HttpAdapter\Adapter\CurlAdapter;
use HttpAdapter\HttpAdapterInterface;

public function register(): void
{
    $this->app->singleton(HttpAdapterInterface::class, function ($app) {
        $adapter = new CurlAdapter();
        $adapter->setTimeout($app['config']->get('services.upstream.timeout', 10));

        return $adapter;
    });
}

Now a future migration to Guzzle is a one-file change instead of a grep-and-replace.

Wrap it in a domain client

Treat the adapter as a transport and give your app a small, opinionated client on top. This is where logging, retries, and JSON decoding belong — not in the library.

final class UpstreamClient
{
    public function __construct(private HttpAdapterInterface $http) {}

    public function getJson(string $path, array $headers = []): array
    {
        $url = rtrim(config('services.upstream.base_url'), '/') . '/' . ltrim($path, '/');

        try {
            $response = $this->http->get($url, $headers + ['Accept' => 'application/json']);
        } catch (HttpAdapterException $e) {
            Log::warning('upstream transport failure', ['url' => $url, 'error' => $e->getMessage()]);
            throw new UpstreamUnavailable($e->getMessage(), 0, $e);
        }

        if ($response->getStatusCode() >= 400) {
            throw new UpstreamError($url, $response->getStatusCode(), $response->getContent());
        }

        return json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
    }
}

Note the two distinct failure modes: the adapter throws on transport failure (DNS, connect timeout, TLS), but a 500 comes back as a perfectly normal response object. You must check the status yourself. Centralising that split in one class is worth the boilerplate.

Config for adapter-specific options

setOption() / setOptions() forward to curl_setopt. That's a leaky abstraction — the cURL constants only mean something to CurlAdapter — but it's how you set TLS verification, proxies, and user agents.

$adapter->setOptions([
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_MAXREDIRS      => 3,
]);

Keep these in config/services.php, keyed by environment, so staging can point at a proxy without a code deploy.

Queued jobs and retries

Because the call is blocking with no async support, everything you'd normally do with concurrency you do with the queue. One job per outbound call, $tries = 3, $backoff = [5, 30, 120], and a timeout comfortably above the adapter timeout so Laravel's worker kills the job rather than leaving a hung socket.

Testing

Http::fake() does not exist here — this package knows nothing about Laravel. Write a stub implementing the interface and bind it in the test container:

final class FakeHttpAdapter implements HttpAdapterInterface
{
    public array $sent = [];
    public function __construct(private array $queue = []) {}
    // ... push a canned response object per call, record the request
}

$this->app->instance(HttpAdapterInterface::class, new FakeHttpAdapter([$okResponse]));

This is cheap to build because the interface is small, and it forces the wrapping pattern above — which is the point. If you find yourself needing to assert on cURL options in a test, your abstraction is in the wrong place.

Multi-step integrations

For paginated or chained APIs, keep loops in a dedicated service, cap iterations explicitly, and log every request at debug level. With no connection pooling you're paying full TLS handshake per call, so batch where the upstream allows it and cache aggressively with Cache::remember() for anything read-mostly.

Gotchas and Tips

Not PSR-7, not PSR-18, not PSR-anything. You cannot drop it into Laravel's HTTP client, Guzzle middleware stacks, or any package that type-hints ClientInterface. If a dependency needs PSR-18, you'll be writing an adapter around the adapter. For greenfield work, Guzzle is the correct answer; this package earns its place only in PHP 5.3-era codebases where Composer and PHP constraints make Guzzle painful.

Exception semantics will bite you. Only transport errors throw. A 401, a 429, a 503 — all come back as Response objects with a status code you have to inspect. Grep your codebase for ->getStatusCode() after wiring this in and make sure every call site handles it.

Response bodies are fully buffered. No streaming, no sink, no chunked download callback. Pulling a 200MB file through this will exhaust memory_limit. For large payloads, bypass the adapter and use cURL directly.

Header handling is literal. Header arrays are passed through largely as-is; duplicate headers and unusual casing are your problem, and header reading off the response may return differently-cased keys than you sent. Normalise with array_change_key_case() on the way in if you're comparing.

Redirects are not followed by default. Set CURLOPT_FOLLOWLOCATION explicitly. Watch out: on redirect, some cURL configurations re-send POST bodies as GET, and auth headers can leak to a different host. Validate the final URL if that matters.

TLS on old cURL. If you're on an old PHP/cURL, CURLOPT_SSL_VERIFYPEER may fail without an explicit CA bundle. Set CURLOPT_CAINFO in config rather than doing the VERIFYPEER => false thing that always seems to get committed at 5pm on a Friday.

PHP 8 compatibility is unverified. The package targets PHP 5.3+, has few stars, and is effectively unmaintained. Before adopting, run the suite on your actual PHP version and watch for deprecation notices around nullable parameters. Assume you own the maintenance — vendor-patch or fork if needed, and pin the version in composer.json rather than using a floating constraint.

Don't let it spread. The failure mode with thin, cheap-to-use libraries is that $http->get() appears in forty controllers. Enforce the rule: adapters are injected into one client class per upstream, that client is injected into services, and controllers never touch HTTP. Six months from now, when you replace this package, that rule is the difference between an afternoon and a sprint.

Extension point if you need one: subclass CurlAdapter and override the request method to add logging, timing, or a correlation-ID header. That's cheaper than composing, because the class wasn't designed for decoration — its internals are the request lifecycle.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings