Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
composer.phar
/vendor/
.DS_Store
/.idea
composer.lock

# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
Expand Down
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,51 @@
# php-dig
# php-dig
## Introduction

[PHP DNS functions](http://php.net/manual/en/ref.network.php) don't have a timeout while the default timeout for dig is 5 seconds (with several (3) tries)

It should drastically decrease time to get dns records, and lower failure errors like `dns_get_record(): A temporary server error occurred.`

## Installation
Install the latest version with

```console
$ composer require hostinger/php-dig
```

## Usage

```php
$client = new Hostinger\DigClient();
$result = $client->getRecord('hostinger.com', DNS_MX);
```

This is equal to
```php
dns_get_record('hostinger.com', DNS_MX);
```

Package checks if it can run `exec` in server environment, otherwise it will fallback to dns_get_record().

### DigClient implements LoggerAwareInterface
You can set [logger](https://github.com/Seldaek/monolog/) to debug / log package activity
```php
$client = new Hostinger\DigClient();
$logger = new \Monolog\Logger\Logger('App');
$logger->pushHandler(new StreamHandler('path/to/your.log'));
$client->setLogger($logger);
```

## About

### Requirements

- php-dig client works with PHP 5.6 or above.

### Submitting bugs and feature requests

Bugs and feature request are tracked on [GitHub](https://github.com/hostinger/php-dig/issues)


## Sources
- [Stack overflow question What would cause checkdnsrr() or dns_get_record() to take too long?](http://stackoverflow.com/questions/14065946/what-would-cause-checkdnsrr-or-dns-get-record-to-take-too-long)
- [Reddit thread: dns_get_record suddenly running very slowly](https://www.reddit.com/r/PHP/comments/2k3ns7/dns_get_record_suddenly_running_very_slowly/)
33 changes: 33 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "hostinger/php-dig",
"description": "Use servers CLI command dig over php internal dns_get_record() function",
"keywords": [
"dig",
"dns",
"dns_get_record"
],
"license": "MIT",
"require": {
"php": ">=5.6",
"psr/log": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^5.6"
},
"autoload": {
"psr-4": {
"Hostinger\\": "src/Hostinger"
}
},
"autoload-dev": {
"psr-4": {
"Hostinger\\": "src/Hostinger"
},
"classmap": [
"tests/"
]
},
"scripts": {
"test": "./vendor/bin/phpunit"
}
}
21 changes: 21 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite>
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>
84 changes: 84 additions & 0 deletions src/Hostinger/DigClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace Hostinger;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;

class DigClient implements LoggerAwareInterface
{
use LoggerAwareTrait;

public function __construct()
{
$this->logger = new NullLogger();
}

public function getRecord($domain, $type)
{
$recordType = (new RecordTypeFactory())->make($type);
if (is_null($recordType)) {
$this->logger->warning('Unsupported DNS type', ['domain' => $domain, 'type' => $type]);
return $this->fallback($domain, $type);
}

if ($errorCode = $this->execEnabled() !== true) {
$this->logger->warning('EXEC disabled', ['domain' => $domain, 'type' => $type, 'error' => $errorCode]);
return $this->fallback($domain, $type);
}

$this->logger->debug('execute dig', ['domain' => $domain, 'type' => $type]);
return (new ExecuteDigCommand())->execute($domain, $recordType);
}

/**
* Use custom error handler to convert dns_get_record() errors to exceptions.
* It throws a warning when the DNS query fails.
* @see http://stackoverflow.com/a/1241751/2728507
* @see http://php.net/manual/en/function.restore-error-handler.php
* @param $domain
* @param $type
* @return array
*/
public function fallback($domain, $type)
{
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
// error was suppressed with the @-operator
if (0 === error_reporting()) {
return false;
}

throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
});

$output = [];
try {
$output = dns_get_record($domain, $type);
} catch (\ErrorException $errorException) {
$this->logger->critical('dns_get_record() query failed',
['domain' => $domain, 'type' => $type, 'error' => $errorException->getMessage()]);
}

restore_error_handler();
return $output;
}

/**
* Check if system allowed to execute commands. Return true or string with error message
* @return bool|string
*/
public function execEnabled()
{
if (!function_exists('exec')) {
return 'missing_function_exec';
}

$disabled = explode(',', ini_get('disable_functions'));
if (in_array('exec', $disabled)) {
return 'disabled_function_exec';
}

return true;
}
}
27 changes: 27 additions & 0 deletions src/Hostinger/ExecuteDigCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Hostinger;

use Hostinger\RecordType\RecordType;

class ExecuteDigCommand
{
private $timeout = 2;

public function setTimeout($value)
{
$this->timeout = $value;
}

public function execute($domain, RecordType $recordType)
{
$lines = [];
$dnsType = strtoupper($recordType->getType());
$command = 'dig +noall +answer +time=' . escapeshellarg($this->timeout) . ' ' . escapeshellarg($dnsType) . ' ' . escapeshellarg($domain);
exec($command, $lines);
if (empty($lines)) {
return [];
}
return $recordType->transform($lines);
}
}
38 changes: 38 additions & 0 deletions src/Hostinger/RecordType/Cname.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Hostinger\RecordType;

class Cname implements RecordType
{
/**
* Example of dig output
* dig +noall +answer CNAME ghs.google.com
* ghs.google.com. 46441 IN CNAME ghs.l.google.com.
* @param array $lines
* @return array
*/
public function transform(array $lines)
{
$output = [];
foreach ($lines as $line) {
$recordProp = explode(' ', preg_replace('!\s+!', ' ', $line));
if ($recordProp[3] != $this->getType()) {
continue;
}

$output[] = [
'host' => $recordProp[0],
'ttl' => $recordProp[1],
'class' => $recordProp[2],
'type' => $recordProp[3],
];
}
return $output;
}

public function getType()
{
return 'CNAME';
}

}
41 changes: 41 additions & 0 deletions src/Hostinger/RecordType/Mx.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Hostinger\RecordType;

class Mx implements RecordType
{
/**
* Example of dig output
* dig +noall +answer MX hostingermail.com
* hostingermail.com. 81657 IN MX 50 ASPMX3.GOOGLEMAIL.com.
* hostingermail.com. 81657 IN MX 40 ASPMX2.GOOGLEMAIL.com.
* @param array $lines
* @return array
*/
public function transform(array $lines)
{
$output = [];
foreach ($lines as $line) {
$recordProp = explode(' ', preg_replace('!\s+!', ' ', $line));
if ($recordProp[3] != $this->getType()) {
continue;
}

$output[] = [
'host' => $recordProp[0],
'ttl' => $recordProp[1],
'class' => $recordProp[2],
'type' => $recordProp[3],
'pri' => $recordProp[4],
'target' => $recordProp[5],
];
}
return $output;
}

public function getType()
{
return 'MX';
}

}
18 changes: 18 additions & 0 deletions src/Hostinger/RecordType/RecordType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Hostinger\RecordType;


interface RecordType
{
/**
* @return string
*/
public function getType();

/**
* @param array $lines
* @return array
*/
public function transform(array $lines);
}
21 changes: 21 additions & 0 deletions src/Hostinger/RecordTypeFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Hostinger;

class RecordTypeFactory
{
private $dns_types = [
DNS_MX => 'MX',
DNS_CNAME => 'CNAME',
];

public function make($dns_type)
{
$class = '\\Hostinger\\RecordType\\' . ucfirst(strtolower($this->dns_types[$dns_type]));
if (!class_exists($class)) {
return null;
}
return new $class();
}

}
34 changes: 34 additions & 0 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

class ClientTest extends \PHPUnit\Framework\TestCase
{
public function testExecEnabled()
{
$client = new Hostinger\DigClient();
$result = $client->execEnabled();
$this->assertTrue($result, $result);
}

public function domainsAndTypesProvider()
{
return [
['hostingermail.com', DNS_MX],
['ghs.google.com', DNS_CNAME],
];
}

/**
* @dataProvider domainsAndTypesProvider
*/
public function testgetRecords($domain, $type)
{
$client = new Hostinger\DigClient();
$result = $client->getRecord($domain, $type);
$this->assertTrue(is_array($result));
$this->assertArrayHasKey(0, $result);

$expected = dns_get_record($domain, $type);

$this->assertEmpty(array_diff_key($result, $expected));
}
}
Loading