asgoodasnu/guzzlecommandclient
A Guzzle-based HTTP client wrapper for Laravel and PHP that organizes API requests as reusable command classes. Encapsulate endpoints, headers, and payloads in clean, testable commands while Guzzle handles transport, giving you a structured, maintainable way to call external services.
Verify before you install. This is a Symfony2-era bundle with zero stars and zero dependents. Before writing any code, open the repo and check three files:
composer.json — look at the require block. If it pins symfony/symfony: ~2.x, guzzle/guzzle: ~3.0, or php: >=5.3, it will not install cleanly on a modern Laravel app (PHP 8+, Guzzle 7).DependencyInjection/*Extension.php and Resources/config/services.yml — this is where the actual wiring lives, and it tells you what the package really is: almost certainly a thin DI wrapper that registers one or more command classes plus a shared Guzzle client.Command/ (or Client/) directory — the meat of the package.If it installs, the minimal path is:
composer require asgoodasnu/guzzlecommandclient
Then register the bundle in app/AppKernel.php (Symfony) or, in Laravel, bind the underlying client yourself — see the Gotchas section, because the bundle registration machinery is Symfony-specific and will not work in Laravel.
First use case: treat it as a reference implementation for the "one command object per API endpoint" pattern, not as a library you depend on. The first thing you'd do day-to-day is open a command class and copy its shape into your own app/Services/YourApi/Commands/ namespace.
Command-per-endpoint. The package's core idea is that each API call is a small object with a predictable contract — typically a constructor taking the shared Guzzle client and any parameters, and a method that builds the request, sends it, and returns a parsed response. In Laravel terms, this maps cleanly onto the service-object pattern:
final class FetchCustomerCommand
{
public function __construct(
private Client $http,
private string $id,
) {}
public function execute(): Customer
{
$response = $this->http->get("customers/{$this->id}");
return Customer::fromArray(
json_decode($response->getBody()->getContents(), true)
);
}
}
One configured client, many commands. The bundle's value is that the base URI, auth headers, and timeout are configured once (in Symfony, via config.yml under the bundle's key and passed into services.yml) and injected into every command. In Laravel you replicate this in a service provider:
$this->app->singleton(Client::class, fn () => new Client([
'base_uri' => config('services.your_api.base_url'),
'headers' => ['Authorization' => 'Bearer '.config('services.your_api.token')],
'timeout' => 10,
]));
Bind commands through the container, not new. If you adopt the pattern wholesale, register each command as a binding that receives the singleton client — this is what makes them swappable in tests and keeps auth config out of business logic.
Testing pattern. The command objects are trivially testable because the client is injected. Use Guzzle's MockHandler + HandlerStack to feed canned responses and assert on the parsed result, not on the HTTP layer:
$mock = new MockHandler([new Response(200, [], json_encode(['id' => 1]))]);
$client = new Client(['handler' => HandlerStack::create($mock)]);
$cmd = new FetchCustomerCommand($client, '1');
$this->assertSame(1, $cmd->execute()->id);
Integration tip: if the package ships a Symfony command-bus bridge (a CommandBus-style dispatcher), do not port that to Laravel. Use Laravel's container or a dedicated bus package instead — the bridge will drag in Symfony components you don't otherwise need.
The Symfony Bundle class is dead weight in Laravel. asgoodasnu/guzzlecommandclient is described as a Symfony2 client. If it ships a *Bundle class plus DependencyInjection/ and Resources/config/, none of that auto-executes in Laravel. You get the PHP classes and nothing else. Budget for writing a service provider by hand, or skip the package entirely and copy the command classes.
Guzzle version drift is the real risk. Symfony2 bundles from this era typically target Guzzle 3 ($client->get(), $request->getBody() returning an object with __toString), while Guzzle 6/7 changed the handler stack (GuzzleHttp\HandlerStack), removed Guzzle\Plugin\* in favour of middleware, and altered exception types. If the package pins guzzle/guzzle (the old package name, not guzzlehttp/guzzle), it will conflict with anything modern. Check for a maintained fork or vendor the code.
Abandonment signals. Zero stars and zero dependents means no issue triage, no security patches, and no one to ask when the API contract changes. Treat every line as code you now own. Fork it into app/Support/ or a private namespace rather than relying on Packagist.
Do not trust the package's error handling. Older wrappers often let RequestException bubble raw or, worse, swallow it and return null. Wrap execute() calls in your own try/catch and map ConnectException, RequestException (4xx vs 5xx via $e->getResponse()->getStatusCode()), and JSON decode failures to your own domain exceptions.
Config quirks to look for. The bundle likely expects keys under asgoodasnu_guzzle_command_client: in config.yml. If it uses %parameter% placeholders or a base_url that is concatenated with a leading slash in each command, you'll get double-slash URLs (//customers/1) that some APIs reject. Sanitize the base URI with rtrim($base, '/') and ltrim($path, '/').
Extension points worth using. The clean seams are (a) the injected ClientInterface — swap it for a decorated client that adds logging, retries, or tracing; (b) Guzzle middleware — add a HandlerStack with Middleware::retry() and Middleware::log() rather than editing command classes; (c) response parsing — if commands hardcode json_decode, factor a ResponseParser out so you can handle XML endpoints too.
Debugging. Set Guzzle's debug => true in the client config to dump the request/response to STDOUT while you're wiring things up, and add a history middleware in tests to assert on the exact URI and headers sent. For auth issues, log $response->getHeaderLine('WWW-Authenticate') before assuming a token problem.
How can I help you explore Laravel packages today?