Passed
Pull Request — master (#11)
by
unknown
02:34
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 1
Bugs 0 Features 1
Metric Value
cc 9
eloc 39
c 1
b 0
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)
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