Passed
Push — master ( cf05d8...5fb5a4 )
by Teye
01:45 queued 16s
created

Client::request()   B

Complexity

Conditions 9
Paths 13

Size

Total Lines 68
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 9.147

Importance

Changes 3
Bugs 1 Features 1
Metric Value
cc 9
eloc 39
c 3
b 1
f 1
nc 13
nop 4
dl 0
loc 68
ccs 36
cts 41
cp 0.878
crap 9.147
rs 7.7404

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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