Cached::doQuery()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 11
nc 3
nop 2
dl 0
loc 19
rs 9.9
c 1
b 0
f 0
1
<?php
2
3
namespace RemotelyLiving\PHPDNS\Resolvers;
4
5
use Psr\Cache\CacheItemPoolInterface;
6
use RemotelyLiving\PHPDNS\Entities\DNSRecordCollection;
7
use RemotelyLiving\PHPDNS\Entities\DNSRecordType;
8
use RemotelyLiving\PHPDNS\Entities\Hostname;
9
use RemotelyLiving\PHPDNS\Resolvers\Traits\Time;
10
use RemotelyLiving\PHPDNS\Resolvers\Interfaces\Resolver;
11
12
use function count;
13
use function max;
14
use function md5;
15
use function min;
16
use function sprintf;
17
18
final class Cached extends ResolverAbstract
19
{
20
    use Time;
21
22
    protected const DEFAULT_CACHE_TTL = 300;
23
    private const CACHE_KEY_TEMPLATE = '%s:%s:%s';
24
25
    /**
26
     * Bump this number on breaking changes to invalidate cache
27
     */
28
    private const NAMESPACE = 'php-dns-v4.0.1';
29
30
    private bool $shouldCacheEmptyResults = true;
31
32
    public function __construct(
33
        private CacheItemPoolInterface $cache,
34
        private Resolver $resolver,
35
        private ?int $ttlSeconds = null
36
    ) {
37
    }
38
39
    public function flush(): void
40
    {
41
        $this->cache->clear();
42
    }
43
44
    public function withEmptyResultCachingDisabled(): self
45
    {
46
        $emptyCachingDisabled = new self($this->cache, $this->resolver, $this->ttlSeconds);
47
        $emptyCachingDisabled->shouldCacheEmptyResults = false;
48
49
        return $emptyCachingDisabled;
50
    }
51
52
    protected function doQuery(Hostname $hostname, DNSRecordType $recordType): DNSRecordCollection
53
    {
54
        $cachedResult = $this->cache->getItem($this->buildCacheKey($hostname, $recordType));
55
56
        if ($cachedResult->isHit()) {
57
            return $this->unwrapResults($cachedResult->get());
58
        }
59
60
        $dnsRecords = $this->resolver->getRecords((string)$hostname, (string)$recordType);
61
        if ($dnsRecords->isEmpty() && !$this->shouldCacheEmptyResults) {
62
            return $dnsRecords;
63
        }
64
65
        $ttlSeconds = $this->ttlSeconds ?? $this->extractLowestTTL($dnsRecords);
66
        $cachedResult->expiresAfter($ttlSeconds);
67
        $cachedResult->set(['recordCollection' => $dnsRecords, 'timestamp' => $this->getTimeStamp()]);
68
        $this->cache->save($cachedResult);
69
70
        return $dnsRecords;
71
    }
72
73
    private function buildCacheKey(Hostname $hostname, DNSRecordType $recordType): string
74
    {
75
        return md5(sprintf(self::CACHE_KEY_TEMPLATE, self::NAMESPACE, (string)$hostname, (string)$recordType));
76
    }
77
78
    private function extractLowestTTL(DNSRecordCollection $recordCollection): int
79
    {
80
        $ttls = [];
81
82
        /** @var \RemotelyLiving\PHPDNS\Entities\DNSRecord $record */
83
        foreach ($recordCollection as $record) {
84
            /** @scrutinizer ignore-call */
85
            if ($record->getTTL() <= 0) {
86
                continue;
87
            }
88
89
            $ttls[] = $record->getTTL();
0 ignored issues
show
Bug introduced by
The method getTTL() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

89
            /** @scrutinizer ignore-call */ 
90
            $ttls[] = $record->getTTL();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
90
        }
91
92
        return count($ttls) ? min($ttls) : self::DEFAULT_CACHE_TTL;
93
    }
94
95
    /**
96
     * @param array $results ['recordCollection' => $recordCollection, 'timestamp' => $timeStamp]
97
     */
98
    private function unwrapResults(array $results): DNSRecordCollection
99
    {
100
        /** @var DNSRecordCollection $records */
101
        $records = $results['recordCollection'];
102
        /**
103
         * @var int $key
104
         * @var \RemotelyLiving\PHPDNS\Entities\DNSRecord $record
105
         */
106
        foreach ($records as $key => $record) {
107
            $records[$key] = $record
108
                ->setTTL(max($record->getTTL() - ($this->getTimeStamp() - (int)$results['timestamp']), 0));
109
        }
110
111
        return $records;
112
    }
113
}
114