Passed
Pull Request — master (#11)
by
unknown
02:34
created

Client::getDomainInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 5
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Level23\Dynadot;
4
5
use GuzzleHttp\Client as GuzzleClient;
6
use GuzzleHttp\Exception\RequestException;
7
use GuzzleHttp\Psr7\Request;
8
use InvalidArgumentException;
9
use Level23\Dynadot\Dto\BulkSearchResult;
10
use Level23\Dynadot\Dto\Contact;
11
use Level23\Dynadot\Dto\ContactListResult;
12
use Level23\Dynadot\Dto\DomainListResult;
13
use Level23\Dynadot\Dto\DomainRegistrationRequest;
14
use Level23\Dynadot\Dto\DomainRegistrationResult;
15
use Level23\Dynadot\Dto\DtoInterface;
16
use Level23\Dynadot\Dto\NameserverUpdateResult;
17
use Level23\Dynadot\Dto\RenewOptionResult;
18
use Level23\Dynadot\Dto\SearchResult;
19
use Level23\Dynadot\Exception\ApiException;
20
use Level23\Dynadot\Exception\NetworkException;
21
use Ramsey\Uuid\Uuid;
22
23
class Client
24
{
25
    /** @var GuzzleClient */
26
    private GuzzleClient $http;
27
28
    /** @var string */
29
    private string $apiKey;
30
31
    /** @var string */
32
    private string $apiSecret;
33
34
    /** @var string */
35
    private string $apiVersion = 'v1';
36
37 84
    public function __construct(string $apiKey, string $apiSecret)
38
    {
39 84
        $this->apiKey    = $apiKey;
40 84
        $this->apiSecret = $apiSecret;
41 84
        $this->http      = new GuzzleClient([
42 84
            'base_uri' => 'https://api.dynadot.com/restful/' . $this->apiVersion . '/',
43 84
            'headers'  => [
44 84
                'Accept'       => 'application/json',
45 84
                'Content-Type' => 'application/json',
46 84
            ],
47 84
        ]);
48
    }
49
50
    /**
51
     * Retrieve detailed information about a single domain.
52
     *
53
     * @param string $domainName
54
     * @return DomainListResult
55
     * @throws ApiException
56
     * @throws NetworkException
57
     */
58 7
    public function getDomainInfo(string $domainName): DomainListResult
59
    {
60
        /** @var DomainListResult $result */
61 7
        $result = $this->request(
62 7
            'GET',
63 7
            "domains/{$domainName}",
64 7
            [],
65 7
            DomainListResult::class
66 7
        );
67
68 4
        return $result;
69
    }
70
71
    /**
72
     * Set nameservers for a domain.
73
     *
74
     * @param string $domainName
75
     * @param array<string> $nameservers
76
     * @throws ApiException
77
     * @throws NetworkException
78
     */
79 10
    public function setNameservers(string $domainName, array $nameservers): NameserverUpdateResult
80
    {
81
        /** @var NameserverUpdateResult $result */
82 10
        $result = $this->request(
83 10
            'PUT',
84 10
            "domains/{$domainName}/nameservers",
85 10
            [
86 10
                'nameservers_list' => $nameservers,
87 10
            ],
88 10
            NameserverUpdateResult::class
89 10
        );
90
91 5
        return $result;
92
    }
93
94
    /**
95
     * Retrieve contact information for a given contact ID.
96
     *
97
     * @param int $contactId
98
     * @return Contact
99
     * @throws ApiException
100
     * @throws NetworkException
101
     */
102 10
    public function getContactInfo(int $contactId): Contact
103
    {
104
        /** @var Contact $result */
105 10
        $result = $this->request(
106 10
            'GET',
107 10
            "contacts/{$contactId}",
108 10
            [],
109 10
            Contact::class
110 10
        );
111
112 6
        return $result;
113
    }
114
115
    /**
116
     * Retrieve a list of all contacts in your Dynadot account.
117
     *
118
     * @return ContactListResult
119
     * @throws ApiException
120
     * @throws NetworkException
121
     */
122 9
    public function getContactList(): ContactListResult
123
    {
124
        /** @var ContactListResult $result */
125 9
        $result = $this->request(
126 9
            'GET',
127 9
            "contacts",
128 9
            [],
129 9
            ContactListResult::class
130 9
        );
131
132 6
        return $result;
133
    }
134
135
    /**
136
     * Retrieve a list of all domains in your Dynadot account.
137
     *
138
     * This method returns detailed information about all domains including
139
     * domain names, expiration dates, registration dates, nameservers,
140
     * status information, and more.
141
     *
142
     * @return DomainListResult Contains an array of DomainInfo objects with detailed domain information
143
     * @throws ApiException When the API returns an error response
144
     * @throws NetworkException When there's a network communication error
145
     */
146
    public function getDomainList(): DomainListResult
147
    {
148
        /** @var DomainListResult $result */
149
        $result = $this->request(
150
            'GET',
151
            "domains",
152
            [],
153
            DomainListResult::class
154
        );
155
156
        return $result;
157
    }
158
159
    /**
160
     * Set the renew option for a domain.
161
     *
162
     * @param string $domain
163
     * @param string $renewOption
164
     * @return RenewOptionResult
165
     * @throws ApiException
166
     * @throws NetworkException
167
     */
168 11
    public function setRenewOption(string $domain, string $renewOption): RenewOptionResult
169
    {
170
        /** @var RenewOptionResult $result */
171 11
        $result = $this->request(
172 11
            'PUT',
173 11
            "domains/{$domain}/renew_option",
174 11
            ['renew_option' => $renewOption],
175 11
            RenewOptionResult::class
176 11
        );
177
178 6
        return $result;
179
    }
180
181
    /**
182
     * Search for multiple domains at once.
183
     *
184
     * @param array<string> $domains
185
     * @return BulkSearchResult
186
     * @throws ApiException
187
     * @throws NetworkException
188
     */
189 11
    public function bulkSearch(array $domains): BulkSearchResult
190
    {
191
        /** @var BulkSearchResult $result */
192 11
        $result = $this->request(
193 11
            'GET',
194 11
            "domains/bulk_search",
195 11
            ['domain_name_list' => implode(',', array_map('trim', $domains))],
196 11
            BulkSearchResult::class
197 11
        );
198
199 8
        return $result;
200
    }
201
202
    /**
203
     * Search for a domain.
204
     *
205
     * @param string $domain
206
     * @return SearchResult
207
     * @throws ApiException
208
     * @throws NetworkException
209
     */
210 13
    public function search(string $domain, bool $showPrice = false, string $currency = 'USD'): SearchResult
211
    {
212
        /** @var SearchResult $result */
213 13
        $result = $this->request(
214 13
            'GET',
215 13
            "domains/{$domain}/search",
216 13
            [
217 13
                'show_price' => $showPrice ? 'true' : 'false',
218 13
                'currency'   => $currency,
219 13
            ],
220 13
            SearchResult::class
221 13
        );
222
223 9
        return $result;
224
    }
225
226
    /**
227
     * Register a new domain.
228
     *
229
     * @param string $domainName
230
     * @param DomainRegistrationRequest $registrationData
231
     * @return DomainRegistrationResult
232
     * @throws ApiException
233
     * @throws NetworkException
234
     */
235 13
    public function registerDomain(string $domainName, DomainRegistrationRequest $registrationData): DomainRegistrationResult
236
    {
237
        /** @var DomainRegistrationResult $result */
238 13
        $result = $this->request(
239 13
            'POST',
240 13
            "domains/{$domainName}/register",
241 13
            $registrationData->jsonSerialize(),
242 13
            DomainRegistrationResult::class
243 13
        );
244
245 8
        return $result;
246
    }
247
248
    /**
249
     * Generic request helper that wraps Guzzle exceptions and hydrates DTOs.
250
     *
251
     * @param string $method
252
     * @param string $path
253
     * @param array<string, mixed>  $params
254
     * @param string $dtoClass
255
     *
256
     * @return DtoInterface
257
     * @throws ApiException
258
     * @throws NetworkException
259
     */
260 84
    private function request(string $method, string $path, array $params, string $dtoClass): DtoInterface
261
    {
262 84
        $requestId = Uuid::uuid4()->toString();
263
264
        // Prepare request options
265 84
        $options = [
266 84
            'headers' => [
267 84
                'Authorization' => 'Bearer ' . $this->apiKey,
268 84
                'X-Request-Id'  => $requestId,
269 84
                'Accept'        => 'application/json',
270 84
                'Content-Type'  => 'application/json',
271 84
            ],
272 84
        ];
273
274
        // For GET requests, use params as query parameters
275 84
        if ($method === 'GET' && ! empty($params)) {
276 24
            $options['query'] = $params;
277 24
            $payloadJson      = '';
278
        } else {
279 60
            $payloadJson = json_encode($params, JSON_UNESCAPED_SLASHES);
280 60
            if ($payloadJson === false) {
281
                throw new InvalidArgumentException('Failed to encode request body as JSON');
282
            }
283 60
            $options['body'] = $payloadJson;
284
        }
285
286 84
        $stringToSign = implode("\n", [
287 84
            $this->apiKey,
288 84
            '/' . trim($path, '/'),
289 84
            $requestId,
290 84
            $payloadJson,
291 84
        ]);
292 84
        $signature                         = hash_hmac('sha256', $stringToSign, $this->apiSecret);
293 84
        $options['headers']['X-Signature'] = $signature;
294
295
        try {
296 84
            $response = $this->http->request($method, $path, $options);
297 32
        } catch (RequestException $e) {
298 32
            if ($e->getHandlerContext()['errno'] ?? null) {
299
                throw new NetworkException('Network error communicating with Dynadot API', 0, $e);
300
            }
301
302 32
            $response = $e->getResponse();
303 32
            if ($response === null) {
304 8
                throw new NetworkException('No response received from Dynadot API', 0, $e);
305
            }
306
307 24
            throw ApiException::fromResponse($e->getRequest(), $response, $e);
308
        }
309
310 52
        $data = json_decode($response->getBody()->getContents(), true);
311
312
        // check the status code in the json response and throw an exception if it's not 200
313 52
        if ($data['code'] >= 400) {
314
            // Create a request object for the exception
315
            $request = new Request($method, $path, $options['headers'], $options['body'] ?? null);
316
317
            throw ApiException::fromResponse($request, $response);
318
        }
319
320 52
        if (! is_a($dtoClass, DtoInterface::class, true)) {
321
            throw new InvalidArgumentException("$dtoClass must implement DtoInterface");
322
        }
323
324
        /** @var DtoInterface $dto */
325 52
        $dto = $dtoClass::fromArray($data['data'] ?? []);
326
327 52
        return $dto;
328
    }
329
}
330