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

Async Aws Bundle Laravel Package

async-aws/async-aws-bundle

A small Symfony bundle for AsyncAws that handles client configuration and autowiring. It registers AsyncAws clients as Symfony services, loads credentials and endpoint settings from config or environment variables, and lets you inject any AWS client directly into your services.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the honest framing. async-aws/async-aws-bundle is a Symfony bundle: it is a Bundle + DependencyInjection\Extension pair that reads YAML through Symfony's Config component and registers tagged/aliased services in a Symfony container. Laravel's container cannot load it. So for a Laravel developer the day-to-day value is not composer require async-aws/async-aws-bundle — it's treating the bundle as the reference implementation for wiring async-aws/core into a DI container, then reproducing ~120 lines of it in a ServiceProvider. That is a small, self-contained port and it's what this assessment assumes.

What the bundle actually gives you (and what you re-implement):

  1. A config tree per AWS service (type: s3, config: { region, endpoint, profile, ... }) — you re-implement as config/async_aws.php.
  2. One client service per named client, aliased for autowiring by interface — you re-implement with $this->app->singleton() + alias().
  3. A credential provider chain plus a cache decorator — you re-implement as a small wrapper over Laravel's cache.
  4. A Monolog channel (async_aws) — you re-implement as a Laravel log channel.
  5. .env hydration from SSM and secrets — you re-implement as a one-shot artisan command (see below).

Minimal steps

composer require async-aws/async-aws-core async-aws/s3 async-aws/sqs
# PHP >= 8.2, and only add the service packages you actually call

Look at these three sources first, in this order:

  • https://async-aws.com/integration/symfony-bundle.html — the only real docs.
  • src/DependencyInjection/Configuration.php in the bundle repo — the authoritative option tree (region, access_key_id, secret_access_key, session_token, endpoint, profile, role_arn, web_identity_token_file, retries, per-client overrides). Your PHP config array should mirror this key-for-key so the docs stay usable.
  • The Client class of whichever service you use first — its constructor takes Configuration $configuration = null, HttpClientInterface $httpClient = null, CacheInterface $cache = null.

First use case — typed injection, not Aws\S3\S3Client

// config/async_aws.php
return [
    'clients' => [
        's3' => ['type' => \AsyncAws\S3\S3Client::class, 'config' => ['region' => env('AWS_REGION', 'eu-west-1')]],
        'sqs' => ['type' => \AsyncAws\Sqs\SqsClient::class, 'config' => ['region' => env('AWS_REGION', 'eu-west-1')]],
    ],
];
// app/Providers/AsyncAwsServiceProvider.php
public function register(): void
{
    $this->app->singleton(S3Client::class, fn ($app) => new S3Client(
        Configuration::create($app['config']->get('async_aws.clients.s3.config', [])),
    ));
    $this->app->alias(S3Client::class, S3ClientInterface::class);
}

Then public function __construct(private S3ClientInterface $s3) {} works everywhere — controllers, jobs, commands — with zero further wiring. That injection ergonomics is the whole point of the bundle, and it survives the port intact.

Implementation Patterns

Build clients as singletons, resolve credentials once. Client construction is cheap; credential resolution (IMDS, ECS, SSO, web identity) is not. In the bundle this is implicit because services are shared. In Laravel, make sure you use singleton, not bind, and never new S3Client() inside a job.

Mirror the bundle's named-client model, not a "one client per type" model. The bundle supports multiple clients of the same type with different regions/accounts. Do the same with an explicit registry, or you'll be stuck the first time you need S3 in two regions:

foreach ($app['config']->get('async_aws.clients', []) as $name => $def) {
    $app->singleton("async_aws.client.$name", fn ($app) => new $def['type'](
        Configuration::create(array_merge($app['config']->get('async_aws.defaults', []), $def['config'] ?? []))
    ));
    $app->alias("async_aws.client.$name", $def['type']);
}

Bind the interface alias only for the primary client of each type; for secondary clients inject by string async_aws.client.s3-eu in the one or two places that need it. This mirrors how the bundle handles ambiguity, minus the compile-time container.

Credential chain + cache is the highest-value thing to copy. The bundle ships credential_provider (chain: env → ini → ecs → web identity → sso → ssm) and credential_provider_cache, which wraps the chain and logs cache failures on the async_aws channel. In Laravel:

$provider = new ChainProvider([
    new EnvironmentProvider(),
    new IniFileProvider(),
    new EcsProvider(),
    new WebIdentityProvider($tokenFile, $roleArn),
]);
// cache it — SSO/IMDS round-trips are the slow part
$cached = new CachedProvider($provider, $app['cache']->store('aws')->getDriver());

The bundle's 1.12.3 fix ("both credential_provider and credential_provider_cache") is a hint that composing these two is the fragile part — test it explicitly, and wrap cache exceptions so a Redis blip doesn't turn into a hard auth failure.

Endpoint overrides for LocalStack/MinIO are first-class. Every client accepts endpoint in its config. Keep AWS_ENDPOINT in .env per environment and set it in defaults; for S3 with MinIO also set the path-style/force-path behaviour on the client config. Do this on day one so you never have to hit real AWS from a dev machine.

Use result objects deliberately — that's the "async" you actually get. async-aws clients return a Result object; the HTTP call happens when you resolve()/await it or access a field. Batching is real only if you hold two results and resolve them together, and true concurrency only if you inject an HttpClientInterface capable of it (Symfony HttpClient with a concurrency-capable transport). In a Laravel app this pattern is worth adopting for fan-out reads:

$head = $s3->headObject(['Bucket' => $b, 'Key' => $k1]);   // no network yet
$body = $s3->getObject(['Bucket' => $b, 'Key' => $k2]);   // no network yet
$exists = $head->resolve()->getContentLength() > 0;       // now

For genuine parallelism in Laravel the honest answer is still queues: dispatch a job per object and let Horizon workers parallelise. Don't try to build an event loop into an FPM request.

Lazy secret loading belongs in a command, not a provider. The bundle can populate %env()% from SSM/Secrets Manager at container build. In Laravel, doing that in a service provider means an AWS round-trip on cold cache and a hard failure when AWS is down. Put it behind php artisan aws:hydrate-secrets --write in your deploy pipeline, writing to config cache or .env, and use max_results (added in 1.12.1) when paging GetParametersByPath.

Testing. Inject a mock HTTP client into the client constructor instead of mocking the client class — you then exercise real request building and response hydration. async-aws/core ships test doubles for this; combined with a LocalStack endpoint override in phpunit.xml you get both fast unit tests and a thin integration layer. Also note the bundle added VarDumpers (0.2.0): dump() on a Result is genuinely useful when a response shape doesn't match your expectation.

Logging. Give it a dedicated channel (async_aws) so you can silence SDK noise independently, and put your retry/backoff configuration next to it so a spike in retries is visible in the same place. Failures worth alerting on: credential cache misses in prod, throttling from SQS/DynamoDB, and Result::resolve() throwing HttpException subclasses inside queue jobs.

Gotchas and Tips

"Async" is misleading — plan accordingly. async-aws is a promise-shaped, lazily-evaluated HTTP API, not a non-blocking runtime. Under PHP-FPM a resolve() still blocks the worker. Any stakeholder framing like "non-blocking I/O cuts costs by avoiding thread starvation" applies to queue workers and CLI fan-out, not to web requests. Design for queue offload, not for concurrency inside a request.

Results are lazy, so exceptions land far from the call. $s3->putObject([...]) returning successfully means nothing — a 403 from IAM surfaces at resolve(), sometimes in a different function. Adopt a rule: every call site either resolves immediately or returns the Result to a caller that owns resolution. Silent un-awaited results are the single most common bug with this library.

It is Symfony-shaped, and the coupling is real. The DependencyInjection\Extension, Config component definitions, and symfony/cache based credential cache have no Laravel equivalents out of the box. Don't try to boot the bundle inside Laravel via a container shim — you'll import Symfony's DI, Config, and Cache components to get behaviour you can write in a provider. Port, don't bridge.

Version and support constraints are hard. PHP 8.2+ (dropped below that in 1.16.0), Symfony 6–8. If you're on PHP 8.1, pin the older line. With ~26 stars and 0 dependents, you are effectively the QA team: pin exact versions, read CHANGELOG.md on every bump, and treat the service list as "what exists" rather than "what AWS offers" (EC2 and ImageBuilder arrived only in 1.17.0; BedrockAgent in 1.16.0). Check the client package exists before designing around a service.

Config merge semantics have been buggy. 1.1.1 fixed "override config in different environments"; 1.12.3 fixed the credential-provider-plus-cache combination. Your PHP-array port sidesteps merge bugs, but be careful with array_merge recursion — use array_replace_recursive for defaults-vs-client overrides or a nested http/retry block will get clobbered wholesale.

Autowiring only resolves the exact type. If you register two S3Client instances, the interface alias points at whichever registered last. Be explicit: bind S3ClientInterface to the primary and inject secondary clients by name. Also avoid instanceof-style "clever" resolution in a provider — it makes the container non-cacheable and is a nightmare to debug.

Region/endpoint mismatch fails quietly or confusingly. Cross-region S3 access returns redirect/AuthorizationHeaderMalformed rather than a clear error; SQS against the wrong region just 404s the queue URL. Assert region is non-empty at boot and fail fast with a clear exception rather than letting the SDK resolve a default.

Extension points are simpler than the bundle suggests. You rarely need more than: a custom CredentialProvider (for your own secret store), a custom HttpClientInterface (for tracing/retry/logging — wrap Symfony's ScopingHttpClient or write a decorator), and a Result subclass for response hydration. All three are plain classes; no bundle involvement required.

Debugging recipe. Wrap the injected HttpClientInterface in a decorator that logs method/URI/status/duration on the async_aws channel; nine times out of ten the bug is a signing or region problem visible in the request headers. Pair that with a LocalStack endpoint and a mock client in tests, and you'll rarely need an AWS console session.

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.
jinas/dhivatar
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