Passed
Push — master ( 44ac55...cf05d8 )
by Teye
02:01 queued 35s
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\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