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

Guzzle Bundle Laravel Package

orkestra/guzzle-bundle

Orkestra Guzzle Bundle integrates the Guzzle HTTP client into Symfony applications, providing service configuration, dependency injection support, and convenient access to configured clients for making API requests, handling responses, and managing HTTP options with sensible defaults.

View on GitHub
Deep Wiki
Context7

Getting Started

Step 0 — verify you're in the one environment where this works. Open the bundle's composer.json and check three constraints before installing: the Symfony major (~2.x expected), the Guzzle major (3/5-era means the Guzzle\Service\Client namespace and no PSR-7), and the PHP floor. If you're on Symfony 4+ or a modern Laravel app, stop here — you want symfony/http-client or guzzlehttp/guzzle bound in a service provider, not this bundle. The rest of this section assumes an existing Symfony 2.8 app that already depends on it.

Step 1 — install pinned. Because the repo is archived, pin the commit, not the branch:

"require": {
    "orkestra/guzzle-bundle": "dev-master#<full-sha>"
}

Then composer update orkestra/guzzle-bundle --with-dependencies and confirm the Guzzle version that landed. Run composer why guzzlehttp/guzzle / composer show | grep -i guzzle and write the version down — everything downstream depends on whether you got the pre-PSR-7 or PSR-7 API.

Step 2 — register the bundle (no Flex here). Add it to app/AppKernel.php:

$bundles = [
    // ...
    new Orkestra\Bundle\GuzzleBundle\OrkestraGuzzleBundle(),
];

Step 3 — read the config schema before you write any YAML. This is the "where to look first" step and it is not optional, because the bundle is undocumented beyond a README. Read in this order:

  1. DependencyInjection/Configuration.php — the actual config tree: root key name, whether clients are a named map, which keys are required, what defaults exist. The defaults are the gotcha; they're implicit.
  2. DependencyInjection/*Extension.php — how config maps to service definitions, and what service IDs get registered. That mapping is the API you'll actually call.
  3. Resources/config/services.xml (or .yml) — constructor args, tags, whether there's a compiler pass.
  4. Any DataCollector/ or EventListener/ directory — the profiler and event wiring, if present.

For a Laravel developer this is the equivalent of reading a package's ServiceProvider::register() plus its config/*.php before touching config/app.php.

Step 4 — define your first client. Roughly (verify key names against Configuration.php):

# app/config/config.yml
orkestra_guzzle:            # root key — confirm in Configuration.php
    clients:
        acme_api:
            base_url: "https://api.acme.test/v1/"
            timeout: 5
            connect_timeout: 2
            headers:
                Accept: application/json

Then inject it by the service ID the extension registers (typically orkestra_guzzle.client.acme_api — again, confirm from services.xml). Clear the cache and confirm it resolves:

app/console cache:clear --env=dev
app/console debug:container acme_api

First real use case: one named client per upstream API, injected into the service that talks to it, with base_url and timeouts set in config rather than hard-coded in PHP. That's the entire value proposition — everything else (retries, logging, auth) is Guzzle middleware you configure underneath.

Implementation Patterns

One named client per upstream. Don't share a single client across APIs and override the base URL per call. Configure acme_api, billing_api, shipping_api as separate entries with their own base URLs, timeouts, and headers. This keeps auth and timeout policy attached to the upstream it belongs to, and makes it obvious in config review which service talks to what.

Inject by type, not by string ID. If the bundle registers clients under a common interface or a Guzzle\Http\ClientInterface / GuzzleHttp\ClientInterface alias, type-hint the constructor and let the container resolve it. String service IDs in constructors are the thing that breaks silently when the extension's ID scheme changes on a Guzzle bump.

class AcmeGateway
{
    private $client;

    public function __construct(\Guzzle\Http\ClientInterface $client) // or GuzzleHttp\ClientInterface
    {
        $this->client = $client;
    }

    public function findUser($id)
    {
        $response = $this->client->get('users/' . $id)->send(); // Guzzle 3/5 style
        return json_decode($response->getBody(), true);
    }
}

Model the handler stack explicitly, in config, not in code. Most of the day-to-day wins live here: put the logging handler and the retry subscriber in the client's handler configuration so every call to that upstream gets them, and nobody has to remember to add them at the call site. Middleware added ad hoc in a service method is how you end up with one endpoint that retries and nine that don't.

Wrap the client behind a small gateway class per upstream. The bundle gives you a configured HTTP client, not a domain API. Put a thin AcmeGateway around it that returns arrays or DTOs, handles status codes, and is the only place Guzzle types appear. This is the single pattern that makes the eventual migration (to symfony/http-client, PSR-18, or a Laravel Http:: binding) mechanical: you replace the gateway's internals and nothing else moves. It also gives you a seam to fake in tests.

Test with a mock handler, not with the network. In Symfony 2, override the client service in the test container or construct the gateway with a client whose handler is a Mock/MockHandler. Assert on the request that was built — URL, method, headers, JSON body — rather than on a live response. If the bundle's service is hard to replace, that's a signal to lean harder on the gateway seam above.

Keep environment differences in parameters.yml, not in the bundle config. Timeouts, base URLs, and API credentials belong in parameters so dev/staging/prod diverge without forking the bundle config block. Never commit credentials into the bundle section — reference %acme_api_key%.

Disable any profiler collector outside dev/test. If the bundle wires a DataCollector, it captures request and response bodies. That is fine in dev, and memory/log-storage noise in staging or prod. Make sure it's off in config_prod.yml.

Treat the bundle config as a contract you own, and document it. Because there's no upstream docs and no community, the effective documentation is a short internal page: client names, upstreams, timeouts, retry policy, auth mechanism, and where the Guzzle version is pinned. Write it while the person who wired it up still remembers.

If you're modernizing rather than maintaining, the extraction is small: enumerate what each client entry configures (base URL, timeouts, headers, handlers, subscribers), then re-express it as a plain service definition — a services.yaml factory for Symfony, or in Laravel a Http::macro/singleton binding in a service provider. The bundle is a config loader; you're replacing a config loader with a config loader.

Gotchas and Tips

The Guzzle major is the whole ballgame. Guzzle 3/5 (Guzzle\Http\Client, ->get()->send(), no PSR-7) and Guzzle 6/7 (GuzzleHttp\Client, ->request() returning a PSR-7 response) are not source-compatible. A "harmless" constraint loosening or a --with-dependencies update that swaps the major will break every call site with confusing method-not-found errors. Pin both the bundle commit and the Guzzle version, and read the diff on any Guzzle change.

Config key names are not documented — verify against Configuration.php every time. The README describes capabilities, not a schema. base_url vs base_uri, whether headers live under headers or defaults, whether handlers are a map or a list — all of it is defined only in the PHP. Guessing produces a config block that Symfony silently ignores or rejects at cache warmup, and a client that behaves with defaults you didn't choose.

Silent defaults are the trap. A client entry that omits timeout gets Guzzle's default (often no timeout / infinite), which means one slow upstream hangs your request thread until PHP's max_execution_time. Set timeout and connect_timeout explicitly on every client. Treat a missing timeout as a code-review rejection.

base_uri requires a trailing slash if you pass relative paths. With base_uri: https://api.acme.test/v1 (no slash), a relative request users/1 resolves to /users/1, dropping v1. Always end base URIs with /. Related: if the request path starts with /, it replaces the whole path — including your version prefix.

Middleware order changes behaviour. A retry handler placed before the logger logs one attempt; after the logger it logs every attempt. A logging handler placed before the auth subscriber logs the request without the auth header (good) — after, it logs your token into the log file (bad). Decide the order deliberately and write it down next to the config.

Guzzle throws on 4xx/5xx by default (http_errors in 6/7; exceptions from send() in 3/5). If callers expect arrays, catch RequestException/BadResponseException in the gateway and translate status codes into your domain errors. Don't let a third-party 500 become an uncaught exception halfway through your controller.

Service IDs are internal implementation, not API. The extension chooses them and they can change with a Guzzle bump or a bundle patch. Prefer type-hinted injection or an alias you define yourself in services.yml, so if IDs shift you fix it in one place.

Archived means you own every bug. No security patches, no PHP-version compatibility, no maintainer to escalate to. Concretely: fork or vendor the source, keep the pin, add it to a security-exception register with an owner and a removal date, and re-audit the Guzzle/Symfony transitive tree each dependency cycle. The bundle is thin, which is the one thing in your favour — the exit is cheap if you decide to take it.

Clear the cache after any config change. Symfony 2 caches the compiled container aggressively; a config edit that "did nothing" is almost always a stale cache. app/console cache:clear --env=dev before debugging behaviour.

Debug by dumping the resolved client, not the config. debug:container <client_service_id> shows the actual class and constructor arguments after compilation, which tells you instantly which Guzzle major and which handler stack you really got — far faster than reading the YAML back.

The extension points worth knowing: the bundle's DI extension (for adding your own config-driven services alongside it), any compiler pass it ships (for decorating or tagging clients), and Guzzle's own subscriber/event system for auth and logging. Custom Guzzle middleware is the right place for cross-cutting request logic — don't reach into the bundle internals to do it.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package