uam/aws-ecs-client
A lightweight PHP client for Amazon Elastic Container Service (ECS), offering convenient access to ECS APIs for managing clusters, task definitions, and services from Laravel or PHP applications. Simplifies common ECS operations with a clean interface.
Install:
composer require uam/aws-ecs-client
Before writing any code, do three things:
composer.json. Check require (PHP constraint, Guzzle/Symfony versions) and the autoload.psr-4 prefix. On a modern Laravel app (PHP 8.2/8.3) an older client will often try to pull Guzzle 5/6 and conflict with Laravel's guzzlehttp/guzzle:^7.src/ — not the README. With 3 stars and 0 dependents the README is probably a stub. The client class, the request builder, and the response normalizer are the documentation.webservices.amazon.com/onca/xml, XML over signed GET). PA-API 5.0 (JSON POST, SigV4, x-amz-target: com.amazon.paapi5.v1.ProductAdvertisingAPIv1.*) is the only version Amazon serves today. If the client only signs 4.x requests, no amount of configuration will make it work — verify before you build on it.Credentials come from the Amazon Associates PA-API dashboard: access key, secret, and a PartnerTag (associate tag). The account must have qualifying sales before Amazon enables API access, so a 403 on every single request usually means "not eligible yet", not "bad code".
Wiring it into Laravel. Expect no service provider and no auto-discovery (run php artisan package:discover to confirm). Keep secrets in .env, expose them through config/services.php, and bind a singleton:
// config/services.php
'amazon' => [
'access_key' => env('AMAZON_ACCESS_KEY'),
'secret_key' => env('AMAZON_SECRET_KEY'),
'partner_tag' => env('AMAZON_PARTNER_TAG'),
'region' => env('AMAZON_REGION', 'us-east-1'),
'host' => env('AMAZON_HOST', 'webservices.amazon.com'),
],
// app/Providers/AppServiceProvider.php
$this->app->singleton(AmazonClient::class, fn () => new AmazonClient([
'access_key' => config('services.amazon.access_key'),
'secret_key' => config('services.amazon.secret_key'),
'partner_tag' => config('services.amazon.partner_tag'),
'region' => config('services.amazon.region'),
'host' => config('services.amazon.host'),
]));
Use the class name declared in src/ — don't copy the alias above blindly.
First use case: resolve one ASIN for a product page (GetItem/GetItems), cache it, render title/price/image. That single operation covers most storefront needs; keyword search can wait until the plumbing is proven.
Wrap, don't inject. Put your own AmazonCatalog / ProductRepository service in front of the client, returning your own DTOs. Never let vendor types leak into controllers or Blade views — it makes swapping the library (likely, see Gotchas) a one-file change.
Cache first, always. PA-API is TPS-limited (you start around 1 request/second and earn more with shipped revenue) and Amazon's terms require you to refresh cached data — 24 hours is the usual ceiling. Cache::remember("amazon:item:{$asin}", now()->addHours(24), fn () => ...).
Batch by ten. GetItems accepts up to 10 ASINs per call. Chunk category pages: collect($asins)->chunk(10) instead of 40 sequential lookups.
Queue anything off the request path. Catalog syncs, price refreshes, sitemap enrichment belong in jobs with backoff and a rate limiter:
RateLimiter::for('paapi', fn () => Limit::perSecond(1));
Design search around the hard limit. Search returns 10 results per page, 10 pages max — 100 items per query, full stop. Build filters/sorts into the query rather than promising deep pagination.
Test at your boundary. Bind a fake implementation of your wrapper in the container so tests assert "we asked for these ASINs in these locales", not the vendor's XML. If the client accepts an injected Guzzle handler, MockHandler works too; otherwise define a one-method-per-operation interface.
Drive host and region from one key. They are paired (webservices.amazon.co.uk + eu-west-1, webservices.amazon.de + eu-west-1, webservices.amazon.co.jp + us-west-2, webservices.amazon.com + us-east-1). Two separate env vars invites the half-changed config.
SignatureDoesNotMatch appears on one container only, check NTP before touching code.TooManyRequests (retry with backoff), AssociateNotEligible, ItemNotAccessible, InvalidParameterValue (permanent). Map them explicitly; a generic try/catch will hide the one that actually matters.composer require proposes downgrading Guzzle, don't. Fork the package into packages/ via a path/VCS repository, pin Guzzle 7, and carry it.AWSAccessKeyId / X-Amz-Credential and the signature itself. Redact before it reaches Telescope, Sentry, or Log::debug.SimpleXMLElement. Normalize at your wrapper boundary ((array), explicit is_array checks) or you'll ship a bug that only appears for products with exactly one image.aws/aws-sdk-php for SigV4 signing replaces this dependency entirely. This package earns its place only if its response objects already match the shapes you need — otherwise the unmaintained dependency is pure liability.How can I help you explore Laravel packages today?