Files
michaelschiemer/src/Infrastructure/Api/ShopifyClient.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

380 lines
10 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Infrastructure\Api;
use App\Framework\Api\ApiRequestTrait;
use App\Framework\Http\Method;
use App\Framework\HttpClient\AuthConfig;
use App\Framework\HttpClient\ClientOptions;
use App\Framework\HttpClient\ClientResponse;
use App\Framework\HttpClient\CurlHttpClient;
use App\Framework\HttpClient\HttpClient;
final class ShopifyClient
{
use ApiRequestTrait;
private string $apiVersion;
/**
* Erstellt einen neuen Shopify API-Client
*
* @param string $shopDomain Die Shopify-Domain (z.B. 'my-store.myshopify.com')
* @param string $accessToken Das Zugriffstoken für die API
* @param string $apiVersion Die API-Version (z.B. '2023-10')
* @param HttpClient|null $httpClient Ein optionaler HTTP-Client
*/
public function __construct(
string $shopDomain,
string $accessToken,
string $apiVersion = '2024-04',
?HttpClient $httpClient = null
) {
$this->baseUrl = "https://{$shopDomain}/admin/api/{$apiVersion}";
$this->apiVersion = $apiVersion;
$this->defaultOptions = new ClientOptions(
auth: AuthConfig::custom([
'headers' => ['X-Shopify-Access-Token' => $accessToken],
])
);
$this->httpClient = $httpClient ?? new CurlHttpClient();
}
/**
* Ruft eine Liste aller Produkte ab
*
* @param array $options Optionale Parameter (limit, since_id, usw.)
* @return array Die Produktliste
*/
public function getProducts(array $options = []): array
{
$queryParams = $this->buildQueryParams($options);
$endpoint = 'products.json' . $queryParams;
$response = $this->sendRequest(
method: Method::GET,
endpoint: $endpoint
);
return $this->decodeJson($response)['products'] ?? [];
}
/**
* Ruft ein einzelnes Produkt ab
*
* @param int $productId Die Produkt-ID
* @return array Die Produktdaten
*/
public function getProduct(int $productId): array
{
$response = $this->sendRequest(
method: Method::GET,
endpoint: "products/{$productId}.json"
);
return $this->decodeJson($response)['product'] ?? [];
}
/**
* Erstellt ein neues Produkt
*
* @param array $productData Die Produktdaten
* @return array Das erstellte Produkt
*/
public function createProduct(array $productData): array
{
$response = $this->sendRequest(
method: Method::POST,
endpoint: 'products.json',
data: ['product' => $productData]
);
return $this->decodeJson($response)['product'] ?? [];
}
/**
* Aktualisiert ein bestehendes Produkt
*
* @param int $productId Die Produkt-ID
* @param array $productData Die zu aktualisierenden Produktdaten
* @return array Das aktualisierte Produkt
*/
public function updateProduct(int $productId, array $productData): array
{
$response = $this->sendRequest(
method: Method::PUT,
endpoint: "products/{$productId}.json",
data: ['product' => $productData]
);
return $this->decodeJson($response)['product'] ?? [];
}
/**
* Löscht ein Produkt
*
* @param int $productId Die Produkt-ID
* @return bool Erfolg oder Misserfolg
*/
public function deleteProduct(int $productId): bool
{
$response = $this->sendRequest(
method: Method::DELETE,
endpoint: "products/{$productId}.json"
);
return $response->status->value === 200;
}
/**
* Ruft eine Liste aller Bestellungen ab
*
* @param array $options Optionale Parameter (limit, status, usw.)
* @return array Die Bestellungsliste
*/
public function getOrders(array $options = []): array
{
$queryParams = $this->buildQueryParams($options);
$endpoint = 'orders.json' . $queryParams;
$response = $this->sendRequest(
method: Method::GET,
endpoint: $endpoint
);
return $this->decodeJson($response)['orders'] ?? [];
}
/**
* Ruft eine einzelne Bestellung ab
*
* @param int $orderId Die Bestellungs-ID
* @return array Die Bestellungsdaten
*/
public function getOrder(int $orderId): array
{
$response = $this->sendRequest(
method: Method::GET,
endpoint: "orders/{$orderId}.json"
);
return $this->decodeJson($response)['order'] ?? [];
}
/**
* Erstellt eine neue Bestellung
*
* @param array $orderData Die Bestellungsdaten
* @return array Die erstellte Bestellung
*/
public function createOrder(array $orderData): array
{
$response = $this->sendRequest(
method: Method::POST,
endpoint: 'orders.json',
data: ['order' => $orderData]
);
return $this->decodeJson($response)['order'] ?? [];
}
/**
* Ruft Informationen über den Shop ab
*
* @return array Die Shop-Informationen
*/
public function getShopInfo(): array
{
$response = $this->sendRequest(
method: Method::GET,
endpoint: 'shop.json'
);
return $this->decodeJson($response)['shop'] ?? [];
}
/**
* Ruft eine Liste aller Kunden ab
*
* @param array $options Optionale Parameter (limit, since_id, usw.)
* @return array Die Kundenliste
*/
public function getCustomers(array $options = []): array
{
$queryParams = $this->buildQueryParams($options);
$endpoint = 'customers.json' . $queryParams;
$response = $this->sendRequest(
method: Method::GET,
endpoint: $endpoint
);
return $this->decodeJson($response)['customers'] ?? [];
}
/**
* Erstellt einen neuen Kunden
*
* @param array $customerData Die Kundendaten
* @return array Der erstellte Kunde
*/
public function createCustomer(array $customerData): array
{
$response = $this->sendRequest(
method: Method::POST,
endpoint: 'customers.json',
data: ['customer' => $customerData]
);
return $this->decodeJson($response)['customer'] ?? [];
}
/**
* Ruft einen einzelnen Kunden ab
*
* @param int $customerId Die Kunden-ID
* @return array Die Kundendaten
*/
public function getCustomer(int $customerId): array
{
$response = $this->sendRequest(
method: Method::GET,
endpoint: "customers/{$customerId}.json"
);
return $this->decodeJson($response)['customer'] ?? [];
}
/**
* Erstellt einen neuen Webhook
*
* @param string $topic Das Webhook-Thema (z.B. 'orders/create')
* @param string $address Die URL, die aufgerufen werden soll
* @param string $format Das Format (JSON oder XML)
* @return array Die Webhook-Daten
*/
public function createWebhook(string $topic, string $address, string $format = 'json'): array
{
$response = $this->sendRequest(
method: Method::POST,
endpoint: 'webhooks.json',
data: [
'webhook' => [
'topic' => $topic,
'address' => $address,
'format' => $format,
],
]
);
return $this->decodeJson($response)['webhook'] ?? [];
}
/**
* Ruft alle Webhooks ab
*
* @return array Die Liste der Webhooks
*/
public function getWebhooks(): array
{
$response = $this->sendRequest(
method: Method::GET,
endpoint: 'webhooks.json'
);
return $this->decodeJson($response)['webhooks'] ?? [];
}
/**
* Sucht Produkte anhand von Suchkriterien
*
* @param string $query Die Suchanfrage
* @param array $options Zusätzliche Optionen
* @return array Die gefundenen Produkte
*/
public function searchProducts(string $query, array $options = []): array
{
$options = array_merge($options, ['query' => $query]);
$queryParams = $this->buildQueryParams($options);
$response = $this->sendRequest(
method: Method::GET,
endpoint: 'products/search.json' . $queryParams
);
return $this->decodeJson($response)['products'] ?? [];
}
/**
* Ruft Metafields für eine Ressource ab
*
* @param string $resourceType Der Ressourcentyp (product, customer, usw.)
* @param int $resourceId Die Ressourcen-ID
* @return array Die Metafields
*/
public function getMetafields(string $resourceType, int $resourceId): array
{
$response = $this->sendRequest(
method: Method::GET,
endpoint: "{$resourceType}s/{$resourceId}/metafields.json"
);
return $this->decodeJson($response)['metafields'] ?? [];
}
/**
* Verarbeitet Rate-Limiting-Informationen aus den Headern
*
* @param ClientResponse $response Die API-Antwort
* @return array Rate-Limiting-Informationen
*/
public function getRateLimitInfo(ClientResponse $response): array
{
$headers = $response->headers->all();
return [
'limit' => (int)($headers['X-Shopify-Shop-Api-Call-Limit'][0] ?? 0),
'remaining' => isset($headers['X-Shopify-Shop-Api-Call-Limit'][0])
? $this->parseRemainingCalls($headers['X-Shopify-Shop-Api-Call-Limit'][0])
: null,
];
}
/**
* Baut Query-Parameter aus einem Options-Array
*
* @param array $options Die Optionen
* @return string Die Query-Parameter als String
*/
private function buildQueryParams(array $options): string
{
if (empty($options)) {
return '';
}
return '?' . http_build_query($options);
}
/**
* Parst die verbleibenden API-Aufrufe aus dem Header
*
* @param string $limitHeader Der Header-Wert
* @return int|null Die verbleibenden Aufrufe
*/
private function parseRemainingCalls(string $limitHeader): ?int
{
if (preg_match('/^(\d+)\/(\d+)$/', $limitHeader, $matches)) {
$current = (int)$matches[1];
$limit = (int)$matches[2];
return $limit - $current;
}
return null;
}
}