CloudFlare::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 7
rs 10
1
<?php
2
3
namespace RemotelyLiving\PHPDNS\Resolvers;
4
5
use Generator;
6
use GuzzleHttp\Client;
7
use GuzzleHttp\ClientInterface;
8
use GuzzleHttp\Promise\EachPromise;
9
use Psr\Http\Message\ResponseInterface;
10
use RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
11
use RemotelyLiving\PHPDNS\Entities\DNSRecordType;
12
use RemotelyLiving\PHPDNS\Entities\Hostname;
13
use RemotelyLiving\PHPDNS\Mappers\CloudFlare as CloudFlareMapper;
14
use RemotelyLiving\PHPDNS\Resolvers\Exceptions\QueryFailure;
15
use Throwable;
16
17
use function array_merge;
18
use function http_build_query;
19
use function json_decode;
20
21
final class CloudFlare extends ResolverAbstract
22
{
23
    protected const BASE_URI = 'https://cloudflare-dns.com';
24
    protected const DEFAULT_TIMEOUT = 5.0;
25
    public const DEFAULT_OPTIONS = [
26
        'base_uri' => self::BASE_URI,
27
        'connect_timeout' => self::DEFAULT_TIMEOUT,
28
        'strict' => true,
29
        'allow_redirects' => false,
30
        'protocols' => ['https'],
31
        'headers' => [
32
            'Accept' => 'application/dns-json',
33
        ],
34
    ];
35
36
    private ClientInterface $http;
37
38
    private CloudFlareMapper $mapper;
39
40
    /**
41
     * @param array<string, mixed> $options
42
     */
43
    public function __construct(
44
        ClientInterface $http = null,
45
        CloudFlareMapper $mapper = null,
46
        private array $options = self::DEFAULT_OPTIONS
47
    ) {
48
        $this->http = $http ?? new Client();
49
        $this->mapper = $mapper ?? new CloudFlareMapper();
50
    }
51
52
    protected function doQuery(Hostname $hostname, DNSRecordType $recordType): DNSRecordCollection
53
    {
54
        try {
55
            return ($recordType->isA(DNSRecordType::TYPE_ANY))
56
                ? $this->doAnyApiQuery($hostname)
57
                : $this->doApiQuery($hostname, $recordType);
58
        } catch (Throwable $e) {
59
            throw new QueryFailure("Unable to query CloudFlare API", 0, $e);
60
        }
61
    }
62
63
    /**
64
     * Cloudflare does not support ANY queries, so we must ask for all record types individually
65
     */
66
    private function doAnyApiQuery(Hostname $hostname): DNSRecordCollection
67
    {
68
        $results = [];
69
        $eachPromise = new EachPromise($this->generateEachTypeQuery($hostname), [
70
            'concurrency' => 4,
71
            'fulfilled' => function (ResponseInterface $response) use (&$results) {
72
                $results = array_merge(
73
                    $results,
74
                    $this->parseResult((array) json_decode((string)$response->getBody(), true))
75
                );
76
            },
77
            'rejected' => function (Throwable $e): void {
78
                throw $e;
79
            },
80
        ]);
81
82
        $eachPromise->promise()->wait(true);
83
84
        return $this->mapResults($this->mapper, $results);
85
    }
86
87
    private function generateEachTypeQuery(Hostname $hostname): Generator
88
    {
89
        foreach (DNSRecordType::VALID_TYPES as $type) {
90
            if ($type === DNSRecordType::TYPE_ANY) {
91
                continue 1;
92
            }
93
94
            yield $this->http->requestAsync(
95
                'GET',
96
                '/dns-query?' . http_build_query(['name' => (string)$hostname, 'type' => $type]),
97
                $this->options
98
            );
99
        }
100
    }
101
102
    private function doApiQuery(Hostname $hostname, DNSRecordType $type): DNSRecordCollection
103
    {
104
        $url = '/dns-query?' . http_build_query(['name' => (string)$hostname, 'type' => (string)$type]);
105
        $decoded = (array)json_decode(
106
            (string)$this->http->requestAsync('GET', $url, $this->options)->wait(true)->getBody(),
107
            true,
108
            512,
109
            JSON_THROW_ON_ERROR
110
        );
111
112
        return $this->mapResults($this->mapper, $this->parseResult($decoded));
113
    }
114
115
    private function parseResult(array $result): array
116
    {
117
        if (isset($result['Answer'])) {
118
            return $result['Answer'];
119
        }
120
121
        if (isset($result['Authority'])) {
122
            return $result['Authority'];
123
        }
124
125
        return [];
126
    }
127
}
128