SuperSeller3000/src/Infrastructure/Channel/Ebay/EbayTaxonomyService.php
Simon Kuehn 7f2ec21c64 feat: expose eBay aspect usage tier (RECOMMENDED vs OPTIONAL)
EbayTaxonomyService.getCategoryAspects() now returns 'usage' alongside
'required'. eBay has three effective tiers for category 177/Notebooks:
  required=true  + usage=RECOMMENDED → hard gate (3 aspects)
  required=false + usage=RECOMMENDED → search ranking signal (17 aspects)
  required=false + usage=OPTIONAL    → truly optional (11 aspects)

Integration test covers all three tiers explicitly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 18:21:31 +00:00

78 lines
2.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Infrastructure\Channel\Ebay;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class EbayTaxonomyService
{
public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly EbayOAuthClient $oauthClient,
private readonly CacheInterface $cache,
private readonly string $apiBaseUrl,
private readonly string $marketplaceId,
) {
}
/**
* Returns item aspects for a given eBay category, including eBay's usage tier.
*
* usage values:
* 'RECOMMENDED' — eBay search-ranking signal; include whenever possible
* 'OPTIONAL' — truly optional, low impact
*
* @return list<array{name: string, required: bool, usage: string, values: list<string>}>
*/
public function getCategoryAspects(string $categoryId): array
{
$cacheKey = 'ebay_aspects_'.md5($this->marketplaceId.$categoryId);
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($categoryId): array {
$item->expiresAfter(86400 * 7);
$token = $this->oauthClient->getAccessToken();
$response = $this->httpClient->request(
'GET',
$this->apiBaseUrl.'/commerce/taxonomy/v1/category_tree/'.$this->getTreeId().'/get_item_aspects_for_category',
[
'headers' => [
'Authorization' => 'Bearer '.$token,
'X-EBAY-C-MARKETPLACE-ID' => $this->marketplaceId,
],
'query' => ['category_id' => $categoryId],
],
);
/** @var array{aspects?: list<array{localizedAspectName: string, aspectConstraint?: array{aspectRequired?: bool, aspectUsage?: string}, aspectValues?: list<array{localizedValue: string}>}>} $data */
$data = $response->toArray();
$aspects = [];
foreach ($data['aspects'] ?? [] as $aspect) {
$aspects[] = [
'name' => $aspect['localizedAspectName'],
'required' => (bool) ($aspect['aspectConstraint']['aspectRequired'] ?? false),
'usage' => $aspect['aspectConstraint']['aspectUsage'] ?? 'OPTIONAL',
'values' => array_column($aspect['aspectValues'] ?? [], 'localizedValue'),
];
}
return $aspects;
});
}
private function getTreeId(): string
{
return match ($this->marketplaceId) {
'EBAY_DE' => '77',
'EBAY_US' => '0',
'EBAY_UK' => '3',
default => '77',
};
}
}