Skip to content

Commit

Permalink
Add option to disable empty result caching, brush up README
Browse files Browse the repository at this point in the history
  • Loading branch information
chthomas committed Nov 20, 2019
1 parent 811a7f5 commit 74486f3
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 35 deletions.
47 changes: 24 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,12 @@ These resolvers at the least implement the `Resolvers\Interfaces\DNSQuery` inter

```php
$resolver = new Resolvers\GoogleDNS();
$hostname = 'google.com';

// can query via convenience methods
$records = $resolver->getARecords($hostname); // returns a collection of DNS A Records
$records = $resolver->getARecords('google.com'); // returns a collection of DNS A Records

// can also query by any RecordType. Record Types are a proper object that validate you have the right type.
$recordType = DNSRecordType::createAAAA();

// OR

$recordType = DNSRecordType::TYPE_AAAA;

$moreRecords = $resolver->getRecords($hostname, $recordType);
// can also query by any RecordType.
$moreRecords = $resolver->getRecords($hostname, DNSRecordType::TYPE_AAAA);

// can query to see if any resolvers find a record or type.
$resolver->hasRecordType($hostname, $type) // true | false
Expand All @@ -59,16 +52,17 @@ $resolver->hasRecord($record) // true | false
**Chain Resolver**

The Chain Resolver can be used to read through DNS Resolvers until an answer is found.
Whichever you pass in first is the first Resolver it tries in sequence of passing them in.
Whichever you pass in first is the first Resolver it tries in the call sequence.
It implements the same `DNSQuery` interface as the other resolvers but with an additional feature set found in the `Chain` interface.

So something like
So something like:

```php
$chainResolver = new Chain($cloudFlareResolver, $googleDNSResolver, $localDNSResolver);
```

And that will call the GoogleDNS Resolver first, if no answer is found it will continue on to the LocalSystem Resolver
That will call the GoogleDNS Resolver first, if no answer is found it will continue on to the LocalSystem Resolver.
The default call through strategy is First to Find aka `Resolvers\Interfaces\Chain::withFirstResults(): Chain`

You can randomly select which Resolver in the chain it tries first too via `Resolvers\Interfaces\Chain::randomly(): Chain`
Example:
Expand All @@ -79,39 +73,47 @@ $foundRecord = $chainResolver->randomly()->getARecords('facebook.com')->pickFirs

The above code calls through the resolvers randomly until it finds any non empty answer or has exhausted order the chain.

There are a few different methods to decide how you want to query through the resolvers. There are a few different strategies.
Check them out here:
Lastly, and most expensively, there is `Resolvers\Interfaces\Chain::withAllResults(): Chain` and `Resolvers\Interfaces\Chain::withConsensusResults(): Chain`
All results will be a merge from all the different sources, useful if you want to see what all is out there.
Consensus results will be only the results in common from source to source.

[src/Resolvers/Interfaces](https://github.com/remotelyliving/php-dns/tree/master/src/Resolvers/Interfaces/Chain.php)

```php
// returns only common results between resolvers
$chainResolver->withConsensusResults()->getARecords('facebook.com');

// returns the first non empty result set
$chainResolver->withFirstResults()->getARecords('facebook.com');

// returns the first non empty result set from a randomly selected resolver
$chainResolver->randomly()->getARecords('facebook.com');

// returns only common results between resolvers
$chainResolver->withConsensusResults()->getARecords('facebook.com');

// returns all collective responses with duplicates filtered out
$chainResolver->withAllResults()->getARecords('facebook.com');
```

**Cached Resolver**

If you use a PSR6 cache implementation, feel free to wrap whatever Resolver you want to use in the Cached Resolver.
It will take in the TTL of the record(s) average them and use that as the cache TTL.
It will take in the the lowest TTL of the record(s) and use that as the cache TTL.
You may override that behavior by setting a cache TTL in the constructor.

```php
$cachedResolver = new Resolvers\Cached($cache, $resolverOfChoice, $TTL);
$cachedResolver = new Resolvers\Cached($cache, $resolverOfChoice);
$cachedResolver->getRecords('facebook.com'); // get from cache if possible or falls back to the wrapped resolver and caches the returned records
```

`Entities\DNSRecordCollection` and `Entities\DNSRecord` are serializable for just such an occasion.
If you do not wish to cache empty result answers, you may call through with this additional option:
```php
$cachedResolver->withEmptyResultCachingDisabled()->getARecords('facebook.com');
```

**Entities**

Take a look in the `src/Entities` to see what's available for you to query by and receive.

For records with extra type data, like SOA, TXT, MX, CNAME, and NS there is a data attribute on `Entities\DNSRecord` that will be set with the proper type
For records with extra type data, like SOA, TXT, MX, CNAME, and NS there is a data attribute on `Entities\DNSRecord` that will be set with the proper type.

**Reverse Lookup**

Expand All @@ -133,7 +135,6 @@ If you want to see how easy it is to wire all this up, check out [the repl boots
### Logging

All provided resolvers implement `Psr\Log\LoggerAwareInterface` and have a default `NullLogger` set at runtime.
If you want a differen format, I would recommend implementing a logger subscriber.

### Tinkering

Expand Down
23 changes: 18 additions & 5 deletions src/Resolvers/Cached.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class Cached extends ResolverAbstract
*/
private $ttlSeconds;

/**
* @var bool
*/
private $shouldCacheEmptyResults = true;

public function __construct(CacheItemPoolInterface $cache, Resolver $resolver, int $ttlSeconds = null)
{
$this->cache = $cache;
Expand All @@ -47,6 +52,14 @@ public function flush(): void
$this->cache->clear();
}

public function withEmptyResultCachingDisabled() : self
{
$emptyCachingDisabled = new static($this->cache, $this->resolver, $this->ttlSeconds);
$emptyCachingDisabled->shouldCacheEmptyResults = false;

return $emptyCachingDisabled;
}

protected function doQuery(Hostname $hostname, DNSRecordType $recordType): DNSRecordCollection
{
$cachedResult = $this->cache->getItem($this->buildCacheKey($hostname, $recordType));
Expand All @@ -56,6 +69,10 @@ protected function doQuery(Hostname $hostname, DNSRecordType $recordType): DNSRe
}

$dnsRecords = $this->resolver->getRecords((string)$hostname, (string)$recordType);
if ($dnsRecords->isEmpty() && $this->shouldCacheEmptyResults === false) {
return $dnsRecords;
}

$ttlSeconds = $this->ttlSeconds ?? $this->extractLowestTTL($dnsRecords);
$cachedResult->expiresAfter($ttlSeconds);
$cachedResult->set(['recordCollection' => $dnsRecords, 'timestamp' => $this->getTimeStamp()]);
Expand All @@ -73,17 +90,13 @@ private function extractLowestTTL(DNSRecordCollection $recordCollection): int
{
$ttls = [];

if ($recordCollection->isEmpty()) {
return self::DEFAULT_CACHE_TTL;
}

/** @var \RemotelyLiving\PHPDNS\Entities\DNSRecord $record */
foreach ($recordCollection as $record) {
/** @scrutinizer ignore-call */
$ttls[] = $record->getTTL();
}

return min($ttls);
return count($ttls) ? min($ttls) : self::DEFAULT_CACHE_TTL;
}

/**
Expand Down
23 changes: 16 additions & 7 deletions tests/Unit/Resolvers/CachedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,14 @@ protected function setUp() : void

$this->resolver->method('getRecords')
->with('example.com.', 'ANY')
->willReturn($this->DNSRecordCollection);
->willReturnCallback(function () : DNSRecordCollection {
return $this->DNSRecordCollection;
});

$this->cachedResolver = new Cached($this->cache, $this->resolver);
$this->cachedResolver->setDateTimeImmutable($this->dateTimeImmutable);
}

/**
* @test
*/
public function testCachesUsingLowestTTLOnReturnedRecordSet() : void
{
$this->cacheItem->method('isHit')
Expand All @@ -122,9 +121,19 @@ public function testCachesUsingLowestTTLOnReturnedRecordSet() : void
$this->assertEquals($this->DNSRecordCollection, $this->cachedResolver->getRecords('example.com', 'ANY'));
}

/**
* @test
*/
public function testDoesNotCacheEmptyResultsIfOptionIsSet() : void
{
$this->cache->expects($this->never())
->method('save');

$this->DNSRecordCollection = new DNSRecordCollection();

$results = $this->cachedResolver->withEmptyResultCachingDisabled()
->getRecords('example.com', 'ANY');

$this->assertEquals($this->DNSRecordCollection, $results);
}

public function testOnHitReturnsCachedValuesAndAdjustsTTLBasedOnTimeElapsedSinceStorage() : void
{
$this->cacheItem->method('isHit')
Expand Down

0 comments on commit 74486f3

Please sign in to comment.