petrepatrasc/http-adapter-bundle
A Symfony bundle offering an HTTP adapter abstraction, letting applications wrap or swap HTTP client implementations behind a shared interface. Useful for decoupling code from specific HTTP libraries, though the project is low-activity and sparsely documented.
First, a reality check. This is a Symfony 2–era bundle (WidopHttpAdapterBundle), not a Laravel package. It wires widop/http-adapter into Symfony's DI container via bundle configuration. As a Laravel developer, your first move is not composer require — it's deciding which of two situations you're in:
Illuminate\Support\Facades\Http or Guzzle. The adapter idea is worth stealing; the bundle is not.If you are in a Symfony2 app, the onboarding loop is short:
composer require petrepatrasc/http-adapter-bundle
// app/AppKernel.php
$bundles = array(
// ...
new Widop\HttpAdapterBundle\WidopHttpAdapterBundle(),
);
# app/config/config.yml
widop_http_adapter:
adapters:
curl:
class: Widop\HttpAdapterBundle\Adapter\CurlAdapter
stream:
class: Widop\HttpAdapterBundle\Adapter\StreamAdapter
Where to look first. Boot the app and inspect the container before writing any code:
app/console debug:container | grep -i adapter
The bundle's only real deliverable is the service layer it registers — a registry (widop_http_adapter) plus one service per configured adapter. The exact service ids/aliases are what you'll reference every day, so confirm them in your app rather than trusting any README. Then read the underlying widop/http-adapter docs: the HttpAdapterInterface methods (get, post, put, delete, head, options, patch) and the HttpResponse object are ~90% of daily usage.
First use case. Inject the registry, select a named adapter, call it, assert on the response object (status code + body), not the raw string:
$adapter = $this->http->getAdapter('curl');
$response = $adapter->get('https://api.example.com/prices');
if (200 !== $response->getStatusCode()) {
throw new \RuntimeException('Upstream failed: '.$response->getStatusCode());
}
$data = json_decode($response->getBody(), true);
That's the loop: register bundle → configure one adapter → inject registry → getAdapter() → read HttpResponse.
Treat the registry as a factory, not a client. The bundle exists to let you pick a backend by name. Don't scatter getAdapter('curl') across controllers. Resolve once at the service-definition layer and inject the concrete adapter:
services:
acme.api_adapter:
factory: ['@widop_http_adapter', 'getAdapter']
arguments: ['curl']
acme.api_client:
class: AcmeBundle\Http\ApiClient
arguments: ['@acme.api_adapter']
class ApiClient
{
public function __construct(HttpAdapterInterface $adapter) { $this->adapter = $adapter; }
public function fetchPrice($sku)
{
return $this->adapter->get(sprintf('https://api.example.com/prices/%s', $sku));
}
}
This is the single most important habit here: inject the adapter, never the registry, into business code. It keeps services testable and makes swapping curl→stream a one-line YAML change.
Choose adapters deliberately. Configure several side by side and route by call site:
| Alias | Use when |
|---|---|
curl |
Default: fast, timeouts, parallel handles |
stream |
No ext-curl, or you want allow_url_fopen semantics |
guzzle |
You already depend on Guzzle and want its middleware |
socket |
Low-level control, unusual ports/protocols |
Wrap it in a decorator for cross-cutting concerns. The adapters are thin and offer no retry, backoff, or logging. Add those yourself:
class RetryingAdapter implements HttpAdapterInterface
{
public function __construct(private HttpAdapterInterface $inner, private int $attempts = 3) {}
public function get($url, array $headers = array()) {
$tries = 0;
do {
$res = $this->inner->get($url, $headers);
if ($res->getStatusCode() < 500) return $res;
} while (++$tries < $this->attempts);
return $res;
}
// delegate remaining methods to $this->inner
}
Map HttpResponse to a domain DTO at the boundary. Don't let a legacy HTTP response object leak into controllers or domain services. Decode once, validate, return your own value object — this is also what makes the eventual replacement migration mechanical.
Environment-specific configuration. Point dev at stream (easy to inspect) and prod at curl, keeping the same adapter alias and interface so no application code changes.
Laravel translation of the same pattern. If you're deliberately rebuilding this idiom, bind an interface in a service provider and back it with the framework client:
// app/Providers/HttpServiceProvider.php
$this->app->bind(PricingClient::class, fn () => new PricingClient(
Http::baseUrl(config('services.pricing.url'))->timeout(5)
));
Constructor-injected, swappable, testable with Http::fake() — the same architectural payoff without the Symfony2 baggage.
debug:container | grep -i adapter is your ground truth; a wrong alias produces a confusing container compile error rather than a clear "adapter not found."class: key expects an FQCN. Typos there surface at container compile time, not at request time — read the full compile error, the useful line is usually not the last one.widop/http-adapter mix getBody()/getContent() across versions. Check the installed interface rather than copying snippets — and write one adapter integration test that pins the behavior.HttpAdapterInterface (or decorate an existing adapter) rather than forking. It keeps your call sites portable and makes the swap to Guzzle/Symfony HttpClient a single new class.How can I help you explore Laravel packages today?