runAndWait('H1', 'SIRKET'); * foreach ($stok['rows'] as $r) { * echo $r['stock_code'], ' ', $r['quantity'], PHP_EOL; * } */ final class ReaderpClient { private string $base; private string $keyId; private string $secret; private int $timeout; public function __construct(string $keyId, string $secret, string $base = 'https://panel.readerp.com/api/v1', int $timeout = 60) { $this->keyId = $keyId; $this->secret = $secret; $this->base = rtrim($base, '/'); $this->timeout = $timeout; } /** Bağlantı ve anahtar kontrolü. */ public function ping(): array { return $this->request('GET', '/ping'); } /** Bu anahtarın çalıştırabileceği raporlar ve her birinin şirketleri. */ public function catalog(): array { return $this->request('GET', '/catalog'); } /** * Raporu kuyruğa alır, iş numarasını döner. * * @param array $arguments */ public function run(string $reportId, ?string $entity = null, array $arguments = []): array { $body = ['id' => $reportId]; if ($entity !== null) { $body['entity'] = $entity; } if ($arguments !== []) { $body['arguments'] = $arguments; } return $this->request('POST', '/run', $body); } /** Bir çalıştırmanın durumu; bittiyse sonucu da içinde. */ public function job(int $jobId): array { return $this->request('GET', '/job/' . $jobId); } /** * Çalıştırır ve sonucu bekler. * * Dönen dizi: ['columns' => [...], 'rows' => [...], 'row_count' => int, * 'duration_ms' => int, 'truncated' => bool, 'job_id' => int] * * @param array $arguments * @throws ReaderpError süre dolarsa ya da çalıştırma başarısız olursa */ public function runAndWait(string $reportId, ?string $entity = null, array $arguments = [], int $waitSeconds = 120): array { $queued = $this->run($reportId, $entity, $arguments); $jobId = (int)$queued['job_id']; $until = time() + $waitSeconds; // İlk yanıt genelde birkaç saniyede gelir; sık yoklamak ajanı // yormaz ama gereksizdir. Bir saniyeyle başlayıp beşe çıkıyoruz. $sleep = 1; while (time() < $until) { $job = $this->job($jobId); $status = $job['status'] ?? ''; if ($status === 'done') { if (!isset($job['result'])) { // Sonuç saklama süresi dolmuş: iş bitti ama satırlar silinmiş. throw new ReaderpError('Çalıştırma bitti ama sonuç saklama süresi dolmuş. ' . 'Raporu yeniden çalıştırın ve sonucu hemen okuyun.', 410); } return $job['result'] + [ 'job_id' => $jobId, 'row_count' => $job['row_count'] ?? count($job['result']['rows'] ?? []), 'duration_ms' => $job['duration_ms'] ?? null, 'truncated' => (bool)($job['truncated'] ?? false), ]; } if ($status === 'failed' || $status === 'cancelled') { throw new ReaderpError('Çalıştırma başarısız: ' . $reportId, 500); } sleep($sleep); $sleep = min(5, $sleep + 1); } throw new ReaderpError('Çalıştırma ' . $waitSeconds . ' saniyede bitmedi: ' . $reportId, 504); } /** * Bu rapor için tamamlanmış son çalıştırmanın sonucu. * * Sonuç saklama süresi dolmuşsa 404 döner; o yüzden latest() bir hız * kolaylığıdır, garanti değil. Kesin sonuç isteyen runAndWait kullanır. */ public function latest(string $reportId, ?string $entity = null): array { $q = $entity === null ? '' : '?entity=' . rawurlencode($entity); return $this->request('GET', '/report/' . rawurlencode($reportId) . '/latest' . $q); } /** Panodaki özet rakamlar (anahtarda dashboard:read yetkisi gerekir). */ public function dashboard(): array { return $this->request('GET', '/dashboard'); } // ---------------------------------------------------------------- iç private function request(string $method, string $path, ?array $body = null): array { $ch = curl_init($this->base . $path); $headers = [ 'X-Readerp-Api-Key: ' . $this->keyId, 'X-Readerp-Api-Secret: ' . $this->secret, 'Accept: application/json', ]; if ($body !== null) { $headers[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_UNESCAPED_UNICODE)); } curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $this->timeout, ]); $raw = curl_exec($ch); $code = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $err = curl_error($ch); curl_close($ch); if ($raw === false) { // Ağ hatası: READERP'e ulaşılamıyor. Çağıran taraf son iyi veriyle // devam etmeli; boş liste dönmek stoğu sıfır göstermek demektir. throw new ReaderpError('READERP\'e ulaşılamadı: ' . $err, 0); } $data = json_decode((string)$raw, true); if (!is_array($data)) { throw new ReaderpError('Yanıt JSON değil (HTTP ' . $code . ')', $code); } if ($code >= 400) { throw new ReaderpError((string)($data['error'] ?? 'Bilinmeyen hata'), $code); } return $data; } } final class ReaderpError extends RuntimeException { public function __construct(string $message, public readonly int $status = 0) { parent::__construct($message, $status); } /** 401/403 yetki, 400 istek, 404 yok, 5xx sunucu, 0 ağ. */ public function isAuth(): bool { return $this->status === 401 || $this->status === 403; } public function isRequest(): bool { return $this->status === 400; } public function isNetwork(): bool { return $this->status === 0; } }