baseUrl = rtrim($baseUrl ?? (string) env('DAPODIK_BASE_URL', ''), '/'); $this->token = $token ?? (string) env('DAPODIK_TOKEN', ''); $this->npsn = $npsn ?? (string) env('DAPODIK_NPSN', ''); } /** * GET getSekolah * * @return array{success: bool, data?: array, error?: string} */ public function getSekolah(): array { $url = $this->baseUrl . '/getSekolah'; if ($this->npsn !== '') { $url .= '?npsn=' . rawurlencode($this->npsn); } return $this->request('GET', $url); } /** * GET getPesertaDidik with pagination * * @param int $start * @param int $limit * @return array{success: bool, data?: array, rows?: array, id?: mixed, start?: int, limit?: int, results?: int, error?: string} */ public function getPesertaDidik(int $start = 0, int $limit = 200): array { $params = []; if ($this->npsn !== '') { $params['npsn'] = $this->npsn; } $params['start'] = $start; $params['limit'] = $limit; $url = $this->baseUrl . '/getPesertaDidik?' . http_build_query($params); return $this->request('GET', $url); } /** * Execute HTTP request. Returns decoded JSON with success flag and error message on failure. */ protected function request(string $method, string $url): array { // Dapodik WebService commonly expects `token` as query param. // We still send Authorization header for compatibility. if ($this->token !== '' && strpos($url, 'token=') === false) { $url = $this->appendQueryParam($url, 'token', $this->token); } $ch = curl_init(); if ($ch === false) { return ['success' => false, 'error' => 'cURL init failed']; } $headers = ['Accept: application/json']; if ($this->token !== '') { $headers[] = 'Authorization: Bearer ' . $this->token; } curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 60, CURLOPT_HTTPHEADER => $headers, CURLOPT_CUSTOMREQUEST => $method, ]); $body = curl_exec($ch); $errno = curl_errno($ch); $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = (string) (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?? ''); curl_close($ch); if ($errno) { return ['success' => false, 'error' => 'Network error: ' . ($errno === CURLE_OPERATION_TIMEDOUT ? 'timeout' : 'curl ' . $errno)]; } $bodyStr = is_string($body) ? $body : ''; if (trim($bodyStr) === '') { return ['success' => false, 'error' => 'Empty response from Dapodik', 'http_code' => $httpCode, 'content_type' => $contentType]; } $decoded = json_decode($bodyStr, true); if (json_last_error() !== JSON_ERROR_NONE) { $snippet = substr(trim(strip_tags($bodyStr)), 0, 200); $extra = $snippet !== '' ? ' — ' . $snippet : ''; $codePart = $httpCode > 0 ? ' (HTTP ' . $httpCode . ')' : ''; return ['success' => false, 'error' => 'Invalid JSON response' . $codePart . $extra, 'http_code' => $httpCode, 'content_type' => $contentType]; } if ($httpCode < 200 || $httpCode >= 300) { $msg = is_array($decoded) && isset($decoded['message']) ? $decoded['message'] : 'HTTP ' . $httpCode; return ['success' => false, 'error' => $msg, 'http_code' => $httpCode, 'data' => $decoded]; } return array_merge(['success' => true], $decoded); } /** * Append a query param to URL, preserving fragment. */ private function appendQueryParam(string $url, string $key, string $value): string { $frag = ''; $base = $url; $hashPos = strpos($url, '#'); if ($hashPos !== false) { $base = substr($url, 0, $hashPos); $frag = substr($url, $hashPos); } $sep = (strpos($base, '?') === false) ? '?' : '&'; return $base . $sep . rawurlencode($key) . '=' . rawurlencode($value) . $frag; } /** * Normalize Dapodik response to a list of rows. * Dapodik returns { results, id, start, limit, rows: [...] } * * @param array $response Response from getPesertaDidik * @return array> */ public static function normalizePesertaDidikRows(array $response): array { if (! isset($response['rows']) || ! is_array($response['rows'])) { return []; } $rows = $response['rows']; $out = []; foreach ($rows as $i => $row) { $out[] = is_array($row) ? $row : (array) $row; } return $out; } }