Chain   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 127
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 27
eloc 51
c 1
b 0
f 0
dl 0
loc 127
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A addSubscriber() 0 5 3
A withFirstResults() 0 6 1
A setLogger() 0 5 3
A hasRecord() 0 9 3
A pushResolver() 0 4 2
A withAllResults() 0 6 1
A withConsensusResults() 0 6 1
A addListener() 0 5 3
A randomly() 0 6 1
B doQuery() 0 30 7
A __construct() 0 4 2
1
<?php
2
3
namespace RemotelyLiving\PHPDNS\Resolvers;
4
5
use Psr\Log\LoggerAwareInterface;
6
use Psr\Log\LoggerInterface;
7
use RemotelyLiving\PHPDNS\Entities\DNSRecord;
8
use RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
9
use RemotelyLiving\PHPDNS\Entities\Interfaces\DNSRecordInterface;
10
use RemotelyLiving\PHPDNS\Entities\DNSRecordType;
11
use RemotelyLiving\PHPDNS\Entities\Hostname;
12
use RemotelyLiving\PHPDNS\Exceptions\Exception;
13
use RemotelyLiving\PHPDNS\Observability\Interfaces\Observable;
14
use RemotelyLiving\PHPDNS\Resolvers\Interfaces;
15
use RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver;
16
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
17
18
use function json_encode;
19
use function shuffle;
20
21
final class Chain extends ResolverAbstract implements Interfaces\Chain
22
{
23
    public const STRATEGY_FIRST_TO_FIND = 0;
24
    public const STRATEGY_ALL_RESULTS = 1;
25
    public const STRATEGY_CONSENSUS = 2;
26
27
    /**
28
     * @var \RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver[]
29
     */
30
    private array $resolvers = [];
31
32
    private int $callThroughStrategy = self::STRATEGY_FIRST_TO_FIND;
33
34
    public function __construct(Resolver ...$resolvers)
35
    {
36
        foreach ($resolvers as $resolver) {
37
            $this->pushResolver($resolver);
38
        }
39
    }
40
41
    public function pushResolver(Resolver ...$resolvers): void
42
    {
43
        foreach ($resolvers as $resolver) {
44
            $this->resolvers[] = $resolver;
45
        }
46
    }
47
48
    public function withAllResults(): Interfaces\Chain
49
    {
50
        $all = new self(...$this->resolvers);
51
        $all->callThroughStrategy = self::STRATEGY_ALL_RESULTS;
52
53
        return $all;
54
    }
55
56
    public function withFirstResults(): Interfaces\Chain
57
    {
58
        $first = new self(...$this->resolvers);
59
        $first->callThroughStrategy = self::STRATEGY_FIRST_TO_FIND;
60
61
        return $first;
62
    }
63
64
    public function withConsensusResults(): Interfaces\Chain
65
    {
66
        $consensus = new self(...$this->resolvers);
67
        $consensus->callThroughStrategy = self::STRATEGY_CONSENSUS;
68
69
        return $consensus;
70
    }
71
72
    public function randomly(): Interfaces\Chain
73
    {
74
        $randomized = clone $this;
75
        shuffle($randomized->resolvers);
76
77
        return $randomized;
78
    }
79
80
    public function addSubscriber(EventSubscriberInterface $subscriber): void
81
    {
82
        foreach ($this->resolvers as $resolver) {
83
            if ($resolver instanceof Observable) {
84
                $resolver->addSubscriber($subscriber);
85
            }
86
        }
87
    }
88
89
    public function addListener(string $eventName, callable $listener, int $priority = 0): void
90
    {
91
        foreach ($this->resolvers as $resolver) {
92
            if ($resolver instanceof Observable) {
93
                $resolver->addListener($eventName, $listener, $priority);
94
            }
95
        }
96
    }
97
98
    public function setLogger(LoggerInterface $logger): void
99
    {
100
        foreach ($this->resolvers as $resolver) {
101
            if ($resolver instanceof LoggerAwareInterface) {
102
                $resolver->setLogger($logger);
103
            }
104
        }
105
    }
106
107
    public function hasRecord(DNSRecordInterface $record): bool
108
    {
109
        foreach ($this->resolvers as $resolver) {
110
            if ($resolver->hasRecord($record)) {
111
                return true;
112
            }
113
        }
114
115
        return false;
116
    }
117
118
    protected function doQuery(Hostname $hostname, DNSRecordType $recordType): DNSRecordCollection
119
    {
120
        $merged = [];
121
122
        foreach ($this->resolvers as $resolver) {
123
            try {
124
                $records = $resolver->getRecords((string)$hostname, (string)$recordType);
125
            } catch (Exception $e) {
126
                $this->getLogger()->error(
127
                    'Something went wrong in the chain of resolvers',
128
                    ['exception' => json_encode($e, JSON_THROW_ON_ERROR), 'resolver' => $resolver->getName()]
129
                );
130
                continue;
131
            }
132
133
            if ($this->callThroughStrategy === self::STRATEGY_FIRST_TO_FIND && !$records->isEmpty()) {
134
                return $records;
135
            }
136
137
            /** @var DNSRecord $record */
138
            foreach ($records as $record) {
139
                $merged[] = $record;
140
            }
141
        }
142
143
        $collection = new DNSRecordCollection(...$merged);
144
145
        return ($this->callThroughStrategy === self::STRATEGY_CONSENSUS)
146
            ? $collection->withUniqueValuesExcluded()
147
            : $collection->withUniqueValues();
148
    }
149
}
150