Passed
Push — master ( 44ac55...cf05d8 )
by Teye
02:01 queued 35s
created

Client::setRenewOption()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

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