Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiClient
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 5
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getInstance
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 authenticate
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 getAllPages
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 request
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace AmgGroup;
4
5use AmgGroup\Logger;
6use GuzzleHttp\Client;
7use GuzzleHttp\Exception\GuzzleException;
8use GuzzleHttp\Exception\RequestException;
9use Psr\Http\Message\ResponseInterface;
10
11/**
12 * Class ApiClient
13 * ----------------
14 * A generic API client with pagination support.
15 */
16class ApiClient
17{
18    private Client $client;
19
20    private static ?ApiClient $instance = null;
21
22    private string $accessToken;
23
24    private Logger $logger;
25
26    private Config $config;
27    public ResponseInterface|null $return;
28    public int $statusCode;
29
30    /**
31     * ApiClient constructor.
32     *
33     * @param array $oauthConfig OAuth2 configuration for the provider.
34     */
35    private function __construct()
36    {
37        $this->config = Config::getInstance();
38        $this->logger = Logger::getInstance();
39
40        $this->authenticate();
41
42        $this->client = new Client([
43            'headers' => [
44                'Authorization' => 'Bearer ' . $this->accessToken,
45                'Accept' => 'application/json',
46            ]
47        ]);
48    }
49
50
51    /**
52     * Provides access to the single instance of Smarty.
53     *
54     * @return MSGraph
55     */
56    public static function getInstance(): ApiClient
57    {
58        if (self::$instance === null) {
59            self::$instance = new self();
60        }
61        return self::$instance;
62    }
63
64    /**
65     * Authenticate and store the access token.
66     */
67    private function authenticate(): string|null
68    {
69        $client = new Client();
70        try {
71            $response = $client->post("https://login.microsoftonline.com/{$this->config->get('graph.tenant_id')}/oauth2/v2.0/token", [
72                'form_params' => [
73                    'client_id' => $this->config->get('graph.client_id'),
74                    'client_secret' => $this->config->get('graph.client_secret'),
75                    'grant_type' => 'client_credentials',
76                    'scope' => 'https://graph.microsoft.com/.default'
77                ]
78            ]);
79
80// Check the response body
81            $body = json_decode($response->getBody()->getContents(), true); // Decode JSON as array
82            $accessToken = $body['access_token']; // Extract the 'access_token'
83            return $this->accessToken = $accessToken;
84        } catch (\GuzzleHttp\Exception\RequestException $e) {
85            echo 'Error: ' . $e->getMessage();
86            if ($e->hasResponse()) {
87                $body = $e->getResponse()->getBody();
88                echo 'Response Body: ' . $body;
89            }
90            return null;
91        }
92    }
93
94    /**
95     * Perform a GET request with pagination support.
96     *
97     * @param string $url Base API endpoint.
98     * @param array $params Optional query parameters.
99     * @param string $nextKey JSON key path to next URL (default: 'next').
100     * @return array All combined response data pages.
101     */
102    public function getAllPages(string $url, array $params = [], string $nextKey = '@odata.nextLink'): array
103    {
104        $results = [];
105
106        try {
107            $fullUrl = $url . '?' . http_build_query($params);
108            //echo "Full URL: " . $fullUrl . PHP_EOL;
109            while ($fullUrl) {
110                $response = $this->client->get($fullUrl);
111                $data = json_decode($response->getBody(), true);
112
113                // Append results from the current page
114                if (isset($data['value']) && is_array($data['value'])) {
115                    $results = array_merge($results, $data['value']);
116                }
117
118                // Determine next URL (if pagination is used)
119                $fullUrl = $data[$nextKey] ?? null;
120            }
121        } catch (RequestException $e) {
122            echo "Request failed: " . $e->getMessage();
123        }
124
125        return $results;
126    }
127
128    /**
129     * Send a generic HTTP request.
130     *
131     * @param string $method HTTP method (GET, POST, etc).
132     * @param string $url Endpoint URL.
133     * @param array $options Guzzle options (headers, json, query, etc).
134     * @return ResponseInterface|null
135     * @throws GuzzleException
136     */
137    public function request(string $method, string $url, array $options = []): ?ResponseInterface
138    {
139        unset($options['json']['id']);
140        try {
141            $this->return = $this->client->request($method, $url, $options);
142        } catch (RequestException $e) {
143            //echo "Request error: " . $e->getMessage();
144            $this->logger->error("Request error : ",[$e->getResponse()->getBody()->getContents()]);
145            $this->logger->error("Request Type : $method");
146            $this->logger->error("Request URL : $url");
147            $this->logger->error("Request Options : ",$options);
148            $this->return = null;
149        }
150        $this->statusCode = $this->return ? $this->return->getStatusCode() : 0;
151        return $this->return;
152    }
153
154}