1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Thruster\Component\Dns; |
4
|
|
|
|
5
|
|
|
use Thruster\Component\Dns\Exception\RecordNotFoundException; |
6
|
|
|
use Thruster\Component\Promise\PromiseInterface; |
7
|
|
|
|
8
|
|
|
class Resolver implements ResolverInterface |
9
|
|
|
{ |
10
|
|
|
/** |
11
|
|
|
* @var string |
12
|
|
|
*/ |
13
|
|
|
private $nameServer; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* @var ExecutorInterface |
17
|
|
|
*/ |
18
|
|
|
private $executor; |
19
|
|
|
|
20
|
|
|
public function __construct(string $nameServer, ExecutorInterface $executor) |
21
|
|
|
{ |
22
|
|
|
$this->nameServer = $nameServer; |
23
|
|
|
$this->executor = $executor; |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
public function resolve(string $domain) : PromiseInterface |
27
|
|
|
{ |
28
|
|
|
$query = new Query($domain, Message::TYPE_A, Message::CLASS_IN, time()); |
29
|
|
|
|
30
|
|
|
return $this->resolveInternal($this->nameServer, $query); |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
protected function resolveInternal(string $nameServer, Query $query) |
34
|
|
|
{ |
35
|
|
|
return $this->executor |
36
|
|
|
->query($nameServer, $query) |
37
|
|
|
->then( |
38
|
|
|
function (Message $response) use ($query) { |
39
|
|
|
return $this->extractAddress($query, $response); |
40
|
|
|
} |
41
|
|
|
); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
private function extractAddress(Query $query, Message $response) |
45
|
|
|
{ |
46
|
|
|
$answers = $response->answers; |
47
|
|
|
|
48
|
|
|
$addresses = $this->resolveAliases($answers, $query->getName()); |
49
|
|
|
|
50
|
|
|
if (0 === count($addresses)) { |
51
|
|
|
throw new RecordNotFoundException('DNS Request did not return valid answer.'); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
$address = $addresses[array_rand($addresses)]; |
55
|
|
|
|
56
|
|
|
return $address; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
private function resolveAliases(array $answers, $name) |
60
|
|
|
{ |
61
|
|
|
$named = $this->filterByName($answers, $name); |
62
|
|
|
$aRecords = $this->filterByType($named, Message::TYPE_A); |
63
|
|
|
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME); |
64
|
|
|
|
65
|
|
|
if ($aRecords) { |
|
|
|
|
66
|
|
|
return $this->mapRecordData($aRecords); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
if ($cnameRecords) { |
|
|
|
|
70
|
|
|
$aRecords = array(); |
71
|
|
|
|
72
|
|
|
$cnames = $this->mapRecordData($cnameRecords); |
73
|
|
|
foreach ($cnames as $cname) { |
74
|
|
|
$targets = $this->filterByName($answers, $cname); |
|
|
|
|
75
|
|
|
$aRecords = array_merge( |
76
|
|
|
$aRecords, |
77
|
|
|
$this->resolveAliases($answers, $cname) |
78
|
|
|
); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
return $aRecords; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return array(); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
private function filterByName(array $answers, $name) |
88
|
|
|
{ |
89
|
|
|
return $this->filterByField($answers, 'name', $name); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
private function filterByType(array $answers, $type) |
93
|
|
|
{ |
94
|
|
|
return $this->filterByField($answers, 'type', $type); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
private function filterByField(array $answers, $field, $value) |
98
|
|
|
{ |
99
|
|
|
return array_filter($answers, function ($answer) use ($field, $value) { |
100
|
|
|
$getter = 'get' . ucfirst($field); |
101
|
|
|
return $value === $answer->$getter(); |
102
|
|
|
}); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
private function mapRecordData(array $records) |
106
|
|
|
{ |
107
|
|
|
return array_map(function ($record) { |
108
|
|
|
return $record->getData(); |
109
|
|
|
}, $records); |
110
|
|
|
} |
111
|
|
|
} |
112
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.