Passed
Pull Request — master (#4)
by Teye
02:45
created

DynadotApi::setNameserversForDomain()   B

Complexity

Conditions 7
Paths 15

Size

Total Lines 59
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 7

Importance

Changes 0
Metric Value
eloc 28
c 0
b 0
f 0
dl 0
loc 59
ccs 25
cts 25
cp 1
rs 8.5386
cc 7
nc 15
nop 2
crap 7

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 Psr\Log\LogLevel;
6
use Sabre\Xml\Reader;
7
use GuzzleHttp\Client;
8
use Sabre\Xml\Service;
9
use Psr\Log\LoggerInterface;
10
use Psr\Http\Message\StreamInterface;
11
use Level23\Dynadot\ResultObjects\SetNsResponse;
12
use Level23\Dynadot\ResultObjects\DomainResponse;
13
use Level23\Dynadot\Exception\DynadotApiException;
14
use Level23\Dynadot\ResultObjects\GeneralResponse;
15
use Level23\Dynadot\ResultObjects\DomainInfoResponse;
16
use Level23\Dynadot\ResultObjects\GetContactResponse;
17
use Level23\Dynadot\Exception\ApiHttpCallFailedException;
18
use Level23\Dynadot\ResultObjects\ListDomainInfoResponse;
19
use Level23\Dynadot\Exception\ApiLimitationExceededException;
20
21
/**
22
 * Class DynadotApi
23
 *
24
 * @package Level23\Dynadot
25
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
26
 */
27
class DynadotApi
28
{
29
    const DYNADOT_API_URL = 'https://api.dynadot.com/api3.xml';
30
31
    /**
32
     * This options array is used by Guzzle.
33
     *
34
     * We currently use it to set the Mock Handler in unit testing.
35
     *
36
     * @var array
37
     */
38
    protected $guzzleOptions = [];
39
40
    /**
41
     * Dynadot's API key we should use for HTTP calls.
42
     *
43
     * @var string
44
     */
45
    protected $apiKey;
46
47
    /**
48
     * Logger for writing debug info
49
     *
50
     * @var LoggerInterface|null
51
     */
52
    protected $logger;
53
54
    /**
55
     * Changes boolean values like "no" and "yes" into false and true.
56
     *
57
     * @return bool
58
     * @throws DynadotApiException
59
     * @var \Closure
60
     */
61
    protected $booleanDeserializer;
62
63
    /**
64
     * Return the contact id
65
     *
66
     * @param Reader $reader
67
     *
68
     * @return int
69
     * @var \Closure
70
     */
71
    protected $contactIdDeserializer;
72
73
    /**
74
     * DynadotApi constructor.
75
     *
76
     * @param string                        $apiKey The API key we should use while communicating with the Dynadot API.
77
     * @param \Psr\Log\LoggerInterface|null $logger
78
     *
79
     * @internal param $Logger
80
     */
81 24
    public function __construct(string $apiKey, LoggerInterface $logger = null)
82
    {
83 24
        $this->setApiKey($apiKey);
84 24
        $this->logger = $logger;
85
86
        /**
87
         * Set the default guzzle options
88
         */
89 24
        $this->setGuzzleOptions([
90
            'max'             => 5,
91
            'referer'         => false,
92
            'protocols'       => ['https'],
93
            'connect_timeout' => 30,
94
        ]);
95
96
        /**
97
         * Changes boolean values like "no" and "yes" into false and true.
98
         *
99
         * @param \Sabre\Xml\Reader $reader
100
         *
101
         * @return bool
102
         * @throws \Level23\Dynadot\Exception\DynadotApiException
103
         * @throws \Sabre\Xml\LibXMLException
104
         * @throws \Sabre\Xml\ParseException
105
         */
106 24
        $this->booleanDeserializer = function (Reader $reader) {
107 3
            $value = strtolower($reader->parseInnerTree());
108
109 3
            if ($value != 'yes' && $value != 'no') {
110
                throw new DynadotApiException('Error, received incorrect boolean value ' . var_export($value, true));
111
            }
112
113 3
            return ($value !== 'no');
114
        };
115
116
        /**
117
         * Return the contact id
118
         *
119
         * @param Reader $reader
120
         *
121
         * @return int
122
         * @throws \Sabre\Xml\LibXMLException
123
         * @throws \Sabre\Xml\ParseException
124
         */
125 24
        $this->contactIdDeserializer = function (Reader $reader) {
126 3
            $children = (array)$reader->parseInnerTree();
127
128 3
            return $children[0]['value'];
129
        };
130
    }
131
132
    /**
133
     * @param array $optionsArray
134
     */
135 24
    public function setGuzzleOptions(array $optionsArray)
136
    {
137 24
        $this->guzzleOptions = $optionsArray;
138
    }
139
140
    /**
141
     * Get info about a domain
142
     *
143
     * @param string $domain
144
     *
145
     * @return DomainResponse\Domain
146
     * @throws \Level23\Dynadot\Exception\ApiHttpCallFailedException
147
     * @throws \Level23\Dynadot\Exception\DynadotApiException
148
     * @throws \Sabre\Xml\ParseException
149
     * @throws \GuzzleHttp\Exception\GuzzleException
150
     */
151 7
    public function getDomainInfo(string $domain): DomainResponse\Domain
152
    {
153 7
        $this->log(LogLevel::INFO, 'Retrieve info for domain: ' . $domain);
154
155
        $requestData = [
156
            'domain'  => $domain,
157
            'command' => 'domain_info',
158
        ];
159
160
        // perform the API call
161 7
        $response = $this->performRawApiCall($requestData);
162
163
        // start parsing XML data using Sabre
164 6
        $sabreService = new Service();
165
166
        // set mapping
167 6
        $sabreService->elementMap = [
168 6
            '{}NameServers'          => function (Reader $reader) {
169
170 2
                $nameservers = [];
171 2
                $id          = '';
172
173 2
                $children = (array)$reader->parseInnerTree();
174
175 2
                foreach ($children as $child) {
176 2
                    if ($child['name'] == '{}ServerId') {
177 2
                        $id = $child['value'];
178 2
                    } elseif ($child['name'] == '{}ServerName') {
179 2
                        if (!empty($id) && !empty($child['value'])) {
180 2
                            $nameserver = new DomainResponse\NameServer();
181
182 2
                            $nameserver->ServerId   = $id;
0 ignored issues
show
Documentation Bug introduced by
The property $ServerId was declared of type integer, but $id is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
183 2
                            $nameserver->ServerName = $child['value'];
184
185 2
                            $nameservers[] = $nameserver;
186
                        }
187 2
                        $id = null;
188
                    }
189
                }
190
191 2
                return $nameservers;
192
            },
193 6
            '{}Registrant'           => $this->contactIdDeserializer,
194 6
            '{}Admin'                => $this->contactIdDeserializer,
195 6
            '{}Technical'            => $this->contactIdDeserializer,
196 6
            '{}Billing'              => $this->contactIdDeserializer,
197 6
            '{}isForSale'            => $this->booleanDeserializer,
198 6
            '{}Hold'                 => $this->booleanDeserializer,
199 6
            '{}RegistrantUnverified' => $this->booleanDeserializer,
200 6
            '{}UdrpLocked'           => $this->booleanDeserializer,
201 6
            '{}Disabled'             => $this->booleanDeserializer,
202 6
            '{}Locked'               => $this->booleanDeserializer,
203 6
            '{}WithAds'              => $this->booleanDeserializer,
204
        ];
205
206
        // map certain values to objects
207 6
        $sabreService->mapValueObject('{}DomainInfoResponse', DomainInfoResponse\DomainInfoResponse::class);
208 6
        $sabreService->mapValueObject('{}DomainInfoResponseHeader', DomainInfoResponse\DomainInfoResponseHeader::class);
209 6
        $sabreService->mapValueObject('{}DomainInfoContent', DomainInfoResponse\DomainInfoContent::class);
210 6
        $sabreService->mapValueObject('{}Domain', DomainResponse\Domain::class);
211 6
        $sabreService->mapValueObject('{}NameServerSettings', DomainResponse\NameServerSettings::class);
212 6
        $sabreService->mapValueObject('{}Whois', DomainResponse\Whois::class);
213 6
        $sabreService->mapValueObject('{}Folder', DomainResponse\Folder::class);
214 6
        $sabreService->mapValueObject('{}Response', GeneralResponse\Response::class);
215 6
        $sabreService->mapValueObject('{}ResponseHeader', GeneralResponse\ResponseHeader::class);
216
217 6
        $this->log(LogLevel::DEBUG, 'Start parsing response XML');
218
219
        // parse the data
220 6
        $resultData = $sabreService->parse($response->getContents());
221
222
        // General error, like incorrect api key
223 5
        if ($resultData instanceof GeneralResponse\Response) {
224 1
            $code = $resultData->ResponseHeader->ResponseCode;
225 1
            if ($code != GeneralResponse\ResponseHeader::RESPONSECODE_OK) {
226 1
                throw new DynadotApiException($resultData->ResponseHeader->Error);
227
            }
228
        }
229
230 4
        if (!$resultData instanceof DomainInfoResponse\DomainInfoResponse) {
231 1
            throw new DynadotApiException('We failed to parse the response');
232
        }
233
234
        /**
235
         * Check if the API call was successful. If not, return the error
236
         */
237 3
        $code = $resultData->DomainInfoResponseHeader->SuccessCode;
238 3
        if ($code != DomainInfoResponse\DomainInfoResponseHeader::SUCCESSCODE_OK) {
239 1
            throw new DynadotApiException($resultData->DomainInfoResponseHeader->Error);
240
        }
241
242 2
        $this->log(LogLevel::DEBUG, 'Returning domain info');
243
244
        // Here we know our API call was succesful, return the domain info.
245 2
        return $resultData->DomainInfoContent->Domain;
246
    }
247
248
    /**
249
     * Log a message to our logger, if we have any.
250
     *
251
     * @param string $level
252
     * @param string $message
253
     */
254 23
    protected function log(string $level, string $message): void
255
    {
256 23
        if ($this->logger instanceof LoggerInterface) {
257 1
            $this->logger->log($level, $message);
258
        }
259
    }
260
261
    /**
262
     * Performs the actual API call (internal method)
263
     *
264
     * @param array $requestData
265
     *
266
     * @return \Psr\Http\Message\StreamInterface
267
     * @throws \GuzzleHttp\Exception\GuzzleException
268
     * @throws \Level23\Dynadot\Exception\ApiHttpCallFailedException
269
     */
270 22
    protected function performRawApiCall(array $requestData): StreamInterface
271
    {
272 22
        $this->log(LogLevel::DEBUG, 'Perform raw call: ' . var_export($requestData, true));
273
274
        // transform the request data into a valid query string
275 22
        $requestDataHttp = http_build_query($requestData);
276
277
        // spawn Guzzle
278 22
        $client = new Client($this->guzzleOptions);
279
280 22
        $url = self::DYNADOT_API_URL .
281 22
            '?key=' . urlencode($this->getApiKey()) .
282 22
            ($requestDataHttp ? '&' . $requestDataHttp : '');
283
284 22
        $this->log(LogLevel::DEBUG, 'Start new guzzle request with URL: ' . $url);
285
286
        // start a request with out API key and optionally our request data
287 22
        $response = $client->request('GET', $url);
288
289 22
        $this->log(LogLevel::DEBUG, 'Received response with status code ' . $response->getStatusCode());
290
291
        // if we did not get a HTTP 200 response, our HTTP call failed (which is different from a failed API call)
292 22
        if ($response->getStatusCode() != 200) {
293 1
            $this->log(LogLevel::ALERT, 'Received wrong HTTP status code: ' . $response->getStatusCode());
294
            // not ok
295 1
            throw new ApiHttpCallFailedException(
296 1
                'HTTP API call failed, expected 200 status, got ' . $response->getStatusCode()
297
            );
298
        }
299
300
        // Return the response body (which is a stream coming from Guzzle).
301
        // Sabre XML semi-handles streams (it will just get the contents of the stream using stream_get_contents) so
302
        // this should work! ;)
303 21
        return $response->getBody();
304
    }
305
306
    /**
307
     * @return string
308
     */
309 23
    public function getApiKey(): string
310
    {
311 23
        return $this->apiKey;
312
    }
313
314
    /**
315
     * @param string $apiKey
316
     */
317 24
    public function setApiKey(string $apiKey)
318
    {
319 24
        $this->apiKey = $apiKey;
320
    }
321
322
    /**
323
     * Set nameservers for a domain (max 13). An exception will be thrown in case of an error.
324
     *
325
     * @param string $domain The domain where to set the nameservers for.
326
     * @param array  $nameservers
327
     *
328
     * @throws \GuzzleHttp\Exception\GuzzleException
329
     * @throws \Level23\Dynadot\Exception\ApiHttpCallFailedException
330
     * @throws \Level23\Dynadot\Exception\ApiLimitationExceededException
331
     * @throws \Level23\Dynadot\Exception\DynadotApiException
332
     * @throws \Sabre\Xml\ParseException
333
     */
334 6
    public function setNameserversForDomain(string $domain, array $nameservers)
335
    {
336 6
        $this->log(LogLevel::DEBUG, 'Set ' . sizeof($nameservers) . ' nameservers for domain ' . $domain);
337
        $requestData = [
338
            'command' => 'set_ns',
339
            'domain'  => $domain,
340
        ];
341
342 6
        if (sizeof($nameservers) > 13) {
343
            // index starts at 0, so we should check if the index is greater than 12 (which is 13 nameservers)
344 1
            throw new ApiLimitationExceededException(
345
                'Can not define more than 13 nameservers through the API'
346
            );
347
        }
348
349 5
        $idx = 0;
350
        // check if there are more than 13 nameservers defined
351 5
        foreach ($nameservers as $nameserver) {
352 5
            $requestData['ns' . $idx++] = $nameserver;
353
        }
354
355
        // perform the API call
356 5
        $response = $this->performRawApiCall($requestData);
357
358 5
        $this->log(LogLevel::DEBUG, 'API call execured, parsing response...');
359
360
        // start parsing XML data using Sabre
361 5
        $sabreService = new Service();
362
363
        // map certain values to objects
364 5
        $sabreService->mapValueObject('{}SetNsResponse', SetNsResponse\SetNsResponse::class);
365 5
        $sabreService->mapValueObject('{}SetNsHeader', SetNsResponse\SetNsHeader::class);
366 5
        $sabreService->mapValueObject('{}Response', GeneralResponse\Response::class);
367 5
        $sabreService->mapValueObject('{}ResponseHeader', GeneralResponse\ResponseHeader::class);
368
369
        // parse the data
370 5
        $resultData = $sabreService->parse($response->getContents());
371
372
        // General error, like incorrect api key
373 4
        if ($resultData instanceof GeneralResponse\Response) {
374 1
            $code = $resultData->ResponseHeader->ResponseCode;
375 1
            if ($code != GeneralResponse\ResponseHeader::RESPONSECODE_OK) {
376 1
                throw new DynadotApiException($resultData->ResponseHeader->Error);
377
            }
378
        }
379
380 3
        if (!$resultData instanceof SetNsResponse\SetNsResponse) {
381 1
            throw new DynadotApiException('We failed to parse the response');
382
        }
383
384
        /**
385
         * Check if the API call was successful. If not, return the error
386
         */
387 2
        $code = $resultData->SetNsHeader->SuccessCode;
388 2
        if ($code != SetNsResponse\SetNsHeader::SUCCESSCODE_OK) {
389 1
            throw new DynadotApiException($resultData->SetNsHeader->Error);
390
        }
391
392 1
        $this->log(LogLevel::DEBUG, 'Received correct response. Everything is ok!');
393
    }
394
395
    /**
396
     * List all domains in the account. We will return an array with Domain objects
397
     *
398
     * @return DomainResponse\Domain[]
399
     * @throws \GuzzleHttp\Exception\GuzzleException
400
     * @throws \Level23\Dynadot\Exception\ApiHttpCallFailedException
401
     * @throws \Level23\Dynadot\Exception\DynadotApiException
402
     * @throws \Sabre\Xml\ParseException
403
     */
404 5
    public function getDomainList(): array
405
    {
406 5
        $this->log(LogLevel::DEBUG, 'Start retrieving all domains');
407
        $requestData = [
408
            'command' => 'list_domain',
409
        ];
410
411
        // perform the API call
412 5
        $response = $this->performRawApiCall($requestData);
413
414 5
        $this->log(LogLevel::DEBUG, 'Start parsing response XML');
415
416
        // start parsing XML data using Sabre
417 5
        $sabreService = new Service();
418
419
        // set mapping
420 5
        $sabreService->elementMap = [
421 5
            '{}NameServers'          => function (Reader $reader) {
422
423
                $nameservers = [];
424
                $id          = '';
425
426
                $children = (array)$reader->parseInnerTree();
427
428
                foreach ($children as $child) {
429
                    if ($child['name'] == '{}ServerId') {
430
                        $id = $child['value'];
431
                    } elseif ($child['name'] == '{}ServerName') {
432
                        if (!empty($id) && !empty($child['value'])) {
433
                            $nameserver             = new DomainResponse\NameServer();
434
                            $nameserver->ServerId   = $id;
0 ignored issues
show
Documentation Bug introduced by
The property $ServerId was declared of type integer, but $id is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
435
                            $nameserver->ServerName = $child['value'];
436
437
                            $nameservers[] = $nameserver;
438
                        }
439
                        $id = null;
440
                    }
441
                }
442
443
                return $nameservers;
444
            },
445 5
            '{}DomainInfoList'       => function (Reader $reader) {
446 1
                $domains = [];
447
448 1
                $tree = (array)$reader->parseInnerTree();
449
450 1
                foreach ($tree as $item) {
451 1
                    foreach ($item['value'] as $domain ) {
452 1
                        $domains[] = $domain['value'];
453
                    }
454
                }
455
456 1
                return $domains;
457
            },
458 5
            '{}Registrant'           => $this->contactIdDeserializer,
459 5
            '{}Admin'                => $this->contactIdDeserializer,
460 5
            '{}Technical'            => $this->contactIdDeserializer,
461 5
            '{}Billing'              => $this->contactIdDeserializer,
462 5
            '{}isForSale'            => $this->booleanDeserializer,
463 5
            '{}Hold'                 => $this->booleanDeserializer,
464 5
            '{}RegistrantUnverified' => $this->booleanDeserializer,
465 5
            '{}UdrpLocked'           => $this->booleanDeserializer,
466 5
            '{}Disabled'             => $this->booleanDeserializer,
467 5
            '{}Locked'               => $this->booleanDeserializer,
468 5
            '{}WithAds'              => $this->booleanDeserializer,
469
        ];
470
471
        // map certain values to objects
472 5
        $sabreService->mapValueObject('{}ListDomainInfoResponse', ListDomainInfoResponse\ListDomainInfoResponse::class);
473 5
        $sabreService->mapValueObject('{}ListDomainInfoHeader', ListDomainInfoResponse\ListDomainInfoHeader::class);
474 5
        $sabreService->mapValueObject('{}ListDomainInfoContent', ListDomainInfoResponse\ListDomainInfoContent::class);
475 5
        $sabreService->mapValueObject('{}Domain', DomainResponse\Domain::class);
476 5
        $sabreService->mapValueObject('{}NameServerSettings', DomainResponse\NameServerSettings::class);
477 5
        $sabreService->mapValueObject('{}Whois', DomainResponse\Whois::class);
478 5
        $sabreService->mapValueObject('{}Folder', DomainResponse\Folder::class);
479 5
        $sabreService->mapValueObject('{}Response', GeneralResponse\Response::class);
480 5
        $sabreService->mapValueObject('{}ResponseHeader', GeneralResponse\ResponseHeader::class);
481
482
        // parse the data
483 5
        $resultData = $sabreService->parse($response->getContents());
484
485
        // General error, like incorrect api key
486 4
        if ($resultData instanceof GeneralResponse\Response) {
487 1
            $code = $resultData->ResponseHeader->ResponseCode;
488 1
            if ($code != GeneralResponse\ResponseHeader::RESPONSECODE_OK) {
489 1
                throw new DynadotApiException($resultData->ResponseHeader->Error);
490
            }
491
        }
492
493 3
        if (!$resultData instanceof ListDomainInfoResponse\ListDomainInfoResponse) {
494 1
            throw new DynadotApiException('We failed to parse the response');
495
        }
496
497
        /**
498
         * Check if the API call was successful. If not, return the error
499
         */
500 2
        $code = $resultData->ListDomainInfoHeader->ResponseCode;
501 2
        if ($code != ListDomainInfoResponse\ListDomainInfoHeader::RESPONSECODE_OK) {
502 1
            throw new DynadotApiException($resultData->ListDomainInfoHeader->Error);
503
        }
504
505 1
        return $resultData->ListDomainInfoContent->DomainInfoList;
506
    }
507
508
    /**
509
     * Get contact information for a specific contact ID
510
     *
511
     * @param int $contactId The contact ID we should request
512
     *
513
     * @return GetContactResponse\Contact
514
     * @throws \GuzzleHttp\Exception\GuzzleException
515
     * @throws \Level23\Dynadot\Exception\ApiHttpCallFailedException
516
     * @throws \Level23\Dynadot\Exception\DynadotApiException
517
     * @throws \Sabre\Xml\ParseException
518
     */
519 5
    public function getContactInfo(int $contactId): GetContactResponse\Contact
520
    {
521 5
        $this->log(LogLevel::DEBUG, 'Fetch contact details for id ' . $contactId);
522
523
        $requestData = [
524
            'command'    => 'get_contact',
525
            'contact_id' => $contactId,
526
        ];
527
528
        // perform the API call
529 5
        $response = $this->performRawApiCall($requestData);
530
531 5
        $this->log(LogLevel::DEBUG, 'Start parsing result');
532
533
        // start parsing XML data using Sabre
534 5
        $sabreService = new Service();
535
536
        // map certain values to objects
537 5
        $sabreService->mapValueObject('{}GetContactResponse', GetContactResponse\GetContactResponse::class);
538 5
        $sabreService->mapValueObject('{}GetContactHeader', GetContactResponse\GetContactHeader::class);
539 5
        $sabreService->mapValueObject('{}GetContactContent', GetContactResponse\GetContactContent::class);
540 5
        $sabreService->mapValueObject('{}Contact', GetContactResponse\Contact::class);
541 5
        $sabreService->mapValueObject('{}Response', GeneralResponse\Response::class);
542 5
        $sabreService->mapValueObject('{}ResponseHeader', GeneralResponse\ResponseHeader::class);
543
544
        // parse the data
545 5
        $resultData = $sabreService->parse($response->getContents());
546
547
        // General error, like incorrect api key
548 4
        if ($resultData instanceof GeneralResponse\Response) {
549 1
            $code = $resultData->ResponseHeader->ResponseCode;
550 1
            if ($code != GeneralResponse\ResponseHeader::RESPONSECODE_OK) {
551 1
                throw new DynadotApiException($resultData->ResponseHeader->Error);
552
            }
553
        }
554
555 3
        if (!$resultData instanceof GetContactResponse\GetContactResponse) {
556 1
            throw new DynadotApiException('We failed to parse the response');
557
        }
558
559
        /**
560
         * Check if the API call was successful. If not, return the error
561
         */
562 2
        $code = $resultData->GetContactHeader->ResponseCode;
563 2
        if ($code != GetContactResponse\GetContactHeader::RESPONSECODE_OK) {
564 1
            throw new DynadotApiException($resultData->GetContactHeader->Error);
565
        }
566
567 1
        return $resultData->GetContactContent->Contact;
568
    }
569
}
570