exeu/amazon-ecs-php
PHP library for the Amazon Product Advertising API (ECS). Search Amazon catalogs, look up items by ASIN, browse nodes, and parse XML responses through a simple object-oriented client. Useful for affiliate product feeds, price checks, and availability lookups.
Read this first: this library targets Amazon ECS (the old Product Advertising API 4.0), which Amazon retired in May 2021, and the repo is archived and GPL-3.0. It is not a viable dependency for new Laravel work. Everything below is written for the two legitimate scenarios: you inherited a codebase that already uses it, or you are evaluating it for replacement.
src/Exeu/AmazonECS/Service.php and src/Exeu/AmazonECS/Service/*.php. Service is the entry point; each class under Service/ maps 1:1 to a PAAPI operation (ItemLookup, ItemSearch, SimilarityLookup, BrowseNodeLookup, plus the old cart operations).composer why-not exeu/amazon-ecs-php is your first debugging step).Minimal wiring (v2.x style; method names shift between tags — verify against the version you install):
use Exeu\AmazonECS\Service;
$service = Service::factory(
config('amazon.access_key'),
config('amazon.secret_key'),
config('amazon.associate_tag'),
'de' // locale / endpoint
);
$lookup = $service->itemLookup();
$lookup->setItemIds(['B00EXAMPLE']);
$lookup->setResponseGroups(['ItemAttributes', 'Images', 'Offers']);
$response = $lookup->execute();
First use case: a product detail page. Resolve an ASIN to title, price, image and availability, cache the normalized result, and never call the library directly from a controller — put it behind a repository class so the swap to PAAPI 5.0 later is a one-file change.
Config + container binding. Keep one source of truth in config/amazon.php and bind a singleton in AppServiceProvider:
$this->app->singleton(\Exeu\AmazonECS\Service::class, fn () => \Exeu\AmazonECS\Service::factory(
config('amazon.access_key'),
config('amazon.secret_key'),
config('amazon.associate_tag'),
config('amazon.locale'),
));
Repository boundary (the pattern that matters). Wrap the vendor classes in an interface returning your own DTOs. This is the single highest-leverage decision, because the vendor package is dead and the repository lets you reimplement against PAAPI 5.0 without touching call sites.
interface ProductCatalog
{
public function findByAsin(string $asin): ?ProductData;
public function search(string $keywords, int $page = 1): Collection;
}
Cache aggressively, key by ASIN. Historically the quota was ~1 request/second and a daily cap tied to prior-month revenue. Cache::remember("amazon:item:{$asin}", now()->addHours(12), fn () => ...) is not optional. Cache negative/"not found" results too, briefly, so a bad ASIN doesn't burn quota.
Queue anything that isn't a single lookup. Batch ItemLookup accepts up to 10 ASINs; chunk array_chunk($asins, 10) inside a job and use Laravel's rate limiter:
RateLimiter::for('amazon', fn () => Limit::perSecond(1));
Persist search results rather than proxying. Run ItemSearch in a job, normalize into an amazon_products table, and serve pages from the database. This survives the quota and lets you filter/sort locally — PAAPI never supported more than 10 pages of 10 results anyway.
Error handling wrapper. Amazon returns business errors with HTTP 200, so try/catch alone is insufficient:
try {
$response = $lookup->execute();
if ($response->isError()) { /* inspect error code */ }
} catch (\Exception $e) {
report($e);
return null;
}
Map RequestThrottled to a retry with exponential backoff; map AWS.InvalidParameterValue and AWS.ECommerceService.NoExactMatches to a domain exception or null. Retry only throttle-type errors.
Testing. Never hit the network in tests. Bind a fake ProductCatalog in the container; if you must test the vendor path, serialize a recorded response via the response object's raw XML/string output into a fixture and feed it to the parser.
.env, never in the config file, and rotate if a committed .env ever leaked.SignatureDoesNotMatch on a working keypair is usually NTP drift on the host, not bad credentials.ItemLookup; more than 10 pages/100 results per ItemSearch is not available.FormattedPrice is locale-localized ("1.234,56 €"); Amount is an integer in 1/100 units — divide by 100 and store the currency code alongside it. Don't parse the formatted string.->children('http://webservices.amazon.com/AWSECommerceService/2011-08-01') or var_dump shows empty nodes. This trips up most debugging sessions.ItemSearch batches.exeu/apai-io, but for new work go directly to aws/aws-sdk-php against PAAPI 5.0 (GetItems, SearchItems, JSON body, SigV4, x-amz-target header). Because everything is behind ProductCatalog, you can ship the new implementation next to the old one and flip the container binding.How can I help you explore Laravel packages today?