Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add native types to public API
Browse files Browse the repository at this point in the history
This changeset adds native types to the public API as discussed in reactphp#219.

Once merged, I'm planning to add PHPStan in a follow-up PR which would take advantage of these types.

Builds on top of reactphp#222, reactphp#223 and reactphp/cache#60
WyriHaximus committed Jun 13, 2024

Verified

This commit was signed with the committer’s verified signature.
WyriHaximus Cees-Jan Kiewiet
1 parent 30c5e43 commit 6c7b3af
Showing 20 changed files with 101 additions and 117 deletions.
9 changes: 6 additions & 3 deletions src/Config/Config.php
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ final class Config
* @return self
* @codeCoverageIgnore
*/
public static function loadSystemConfigBlocking()
public static function loadSystemConfigBlocking(): self
{
// Use WMIC output on Windows
if (DIRECTORY_SEPARATOR === '\\') {
@@ -71,7 +71,7 @@ public static function loadSystemConfigBlocking()
* @return self
* @throws RuntimeException if the path can not be loaded (does not exist)
*/
public static function loadResolvConfBlocking($path = null)
public static function loadResolvConfBlocking(?string $path = null): self
{
if ($path === null) {
$path = '/etc/resolv.conf';
@@ -122,7 +122,7 @@ public static function loadResolvConfBlocking($path = null)
* @return self
* @link https://ss64.com/nt/wmic.html
*/
public static function loadWmicBlocking($command = null)
public static function loadWmicBlocking(?string $command = null): self
{
$contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
@@ -133,5 +133,8 @@ public static function loadWmicBlocking($command = null)
return $config;
}

/**
* @var array<string>
*/
public $nameservers = [];
}
17 changes: 7 additions & 10 deletions src/Config/HostsFile.php
Original file line number Diff line number Diff line change
@@ -24,10 +24,9 @@ class HostsFile
/**
* Returns the default path for the hosts file on this system
*
* @return string
* @codeCoverageIgnore
*/
public static function getDefaultPath()
public static function getDefaultPath(): string
{
// use static path for all Unix-based systems
if (DIRECTORY_SEPARATOR !== '\\') {
@@ -59,7 +58,7 @@ public static function getDefaultPath()
* @return self
* @throws RuntimeException if the path can not be loaded (does not exist)
*/
public static function loadFromPathBlocking($path = null)
public static function loadFromPathBlocking(?string $path = null): self
{
if ($path === null) {
$path = self::getDefaultPath();
@@ -77,10 +76,8 @@ public static function loadFromPathBlocking($path = null)

/**
* Instantiate new hosts file with the given hosts file contents
*
* @param string $contents
*/
public function __construct($contents)
public function __construct(string $contents)
{
// remove all comments from the contents
$contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents));
@@ -92,9 +89,9 @@ public function __construct($contents)
* Returns all IPs for the given hostname
*
* @param string $name
* @return string[]
* @return array<string>
*/
public function getIpsForHost($name)
public function getIpsForHost(string $name): array
{
$name = strtolower($name);

@@ -121,9 +118,9 @@ public function getIpsForHost($name)
* Returns all hostnames for the given IPv4 or IPv6 address
*
* @param string $ip
* @return string[]
* @return array<string>
*/
public function getHostsForIp($ip)
public function getHostsForIp(string $ip): array
{
// check binary representation of IP to avoid string case and short notation
$ip = @inet_pton($ip);
22 changes: 8 additions & 14 deletions src/Model/Message.php
Original file line number Diff line number Diff line change
@@ -82,11 +82,8 @@ final class Message

/**
* Creates a new request message for the given query
*
* @param Query $query
* @return self
*/
public static function createRequestForQuery(Query $query)
public static function createRequestForQuery(Query $query): self
{
$request = new Message();
$request->id = self::generateId();
@@ -99,11 +96,9 @@ public static function createRequestForQuery(Query $query)
/**
* Creates a new response message for the given query with the given answer records
*
* @param Query $query
* @param Record[] $answers
* @return self
* @param array<Record> $answers
*/
public static function createResponseWithAnswersForQuery(Query $query, array $answers)
public static function createResponseWithAnswersForQuery(Query $query, array $answers): self
{
$response = new Message();
$response->id = self::generateId();
@@ -126,11 +121,10 @@ public static function createResponseWithAnswersForQuery(Query $query, array $an
* DNS response messages can not guess the message ID to avoid possible
* cache poisoning attacks.
*
* @return int
* @see self::getId()
* @codeCoverageIgnore
*/
private static function generateId()
private static function generateId(): int
{
return random_int(0, 0xffff);
}
@@ -199,22 +193,22 @@ private static function generateId()
* ];
* ```
*
* @var Query[]
* @var array<Query>
*/
public $questions = [];

/**
* @var Record[]
* @var array<Record>
*/
public $answers = [];

/**
* @var Record[]
* @var array<Record>
*/
public $authority = [];

/**
* @var Record[]
* @var array<Record>
*/
public $additional = [];
}
10 changes: 3 additions & 7 deletions src/Model/Record.php
Original file line number Diff line number Diff line change
@@ -131,18 +131,14 @@ final class Record
* considered a BC break. See the format definition of known types above
* for more details.
*
* @var string|string[]|array
* @var string|array<string>
*/
public $data;

/**
* @param string $name
* @param int $type
* @param int $class
* @param int $ttl
* @param string|string[]|array $data
* @param string|array<string> $data
*/
public function __construct($name, $type, $class, $ttl, $data)
public function __construct(string $name, int $type, int $class, int $ttl, $data)
{
$this->name = $name;
$this->type = $type;
32 changes: 9 additions & 23 deletions src/Protocol/BinaryDumper.php
Original file line number Diff line number Diff line change
@@ -8,11 +8,7 @@

final class BinaryDumper
{
/**
* @param Message $message
* @return string
*/
public function toBinary(Message $message)
public function toBinary(Message $message): string
{
$data = '';

@@ -25,11 +21,7 @@ public function toBinary(Message $message)
return $data;
}

/**
* @param Message $message
* @return string
*/
private function headerToBinary(Message $message)
private function headerToBinary(Message $message): string
{
$data = '';

@@ -56,10 +48,9 @@ private function headerToBinary(Message $message)
}

/**
* @param Query[] $questions
* @return string
* @param array<Query> $questions
*/
private function questionToBinary(array $questions)
private function questionToBinary(array $questions): string
{
$data = '';

@@ -72,10 +63,10 @@ private function questionToBinary(array $questions)
}

/**
* @param Record[] $records
* @param array<Record> $records
* @return string
*/
private function recordsToBinary(array $records)
private function recordsToBinary(array $records): string
{
$data = '';

@@ -163,10 +154,9 @@ private function recordsToBinary(array $records)
}

/**
* @param string[] $texts
* @return string
* @param array<string> $texts
*/
private function textsToBinary(array $texts)
private function textsToBinary(array $texts): string
{
$data = '';
foreach ($texts as $text) {
@@ -175,11 +165,7 @@ private function textsToBinary(array $texts)
return $data;
}

/**
* @param string $host
* @return string
*/
private function domainNameToBinary($host)
private function domainNameToBinary(string $host): string
{
if ($host === '') {
return "\0";
33 changes: 13 additions & 20 deletions src/Protocol/Parser.php
Original file line number Diff line number Diff line change
@@ -17,11 +17,9 @@ final class Parser
/**
* Parses the given raw binary message into a Message object
*
* @param string $data
* @throws InvalidArgumentException
* @return Message
*/
public function parseMessage($data)
public function parseMessage(string $data): Message
{
$message = $this->parse($data, 0);
if ($message === null) {
@@ -32,11 +30,9 @@ public function parseMessage($data)
}

/**
* @param string $data
* @param int $consumed
* @return ?Message
*/
private function parse($data, $consumed)
private function parse(string $data, int $consumed)
{
if (!isset($data[12 - 1])) {
return null;
@@ -99,11 +95,9 @@ private function parse($data, $consumed)
}

/**
* @param string $data
* @param int $consumed
* @return array
* @return array{Query, int}|array{null, null}
*/
private function parseQuestion($data, $consumed)
private function parseQuestion(string $data, int $consumed): array
{
list($labels, $consumed) = $this->readLabels($data, $consumed);

@@ -125,11 +119,9 @@ private function parseQuestion($data, $consumed)
}

/**
* @param string $data
* @param int $consumed
* @return array An array with a parsed Record on success or array with null if data is invalid/incomplete
* @return array{Record, int}|array{null, null} An array with a parsed Record on success or array with null if data is invalid/incomplete
*/
private function parseRecord($data, $consumed)
private function parseRecord(string $data, int $consumed): array
{
list($name, $consumed) = $this->readDomain($data, $consumed);

@@ -278,7 +270,10 @@ private function parseRecord($data, $consumed)
];
}

private function readDomain($data, $consumed)
/**
* @return array{string, int}|array{null, null}
*/
private function readDomain(string $data, int $consumed): array
{
list ($labels, $consumed) = $this->readLabels($data, $consumed);

@@ -302,12 +297,10 @@ function ($label) {
}

/**
* @param string $data
* @param int $consumed
* @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion
* @return array
* @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion
* @return array{array<string>, int}|array{null, null}
*/
private function readLabels($data, $consumed, $compressionDepth = 127)
private function readLabels(string $data, int $consumed, int $compressionDepth = 127): array
{
$labels = [];

3 changes: 2 additions & 1 deletion src/Query/CachingExecutor.php
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
use React\Cache\CacheInterface;
use React\Dns\Model\Message;
use React\Promise\Promise;
use React\Promise\PromiseInterface;

final class CachingExecutor implements ExecutorInterface
{
@@ -24,7 +25,7 @@ public function __construct(ExecutorInterface $executor, CacheInterface $cache)
$this->cache = $cache;
}

public function query(Query $query)
public function query(Query $query): PromiseInterface
{
$id = $query->name . ':' . $query->type . ':' . $query->class;

3 changes: 2 additions & 1 deletion src/Query/CoopExecutor.php
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
namespace React\Dns\Query;

use React\Promise\Promise;
use React\Promise\PromiseInterface;

/**
* Cooperatively resolves hosts via the given base executor to ensure same query is not run concurrently
@@ -45,7 +46,7 @@ public function __construct(ExecutorInterface $base)
$this->executor = $base;
}

public function query(Query $query)
public function query(Query $query): PromiseInterface
{
$key = $this->serializeQueryToIdentity($query);
if (isset($this->pending[$key])) {
7 changes: 5 additions & 2 deletions src/Query/ExecutorInterface.php
Original file line number Diff line number Diff line change
@@ -2,6 +2,9 @@

namespace React\Dns\Query;

use React\Dns\Model\Message;
use React\Promise\PromiseInterface;

interface ExecutorInterface
{
/**
@@ -36,8 +39,8 @@ interface ExecutorInterface
* ```
*
* @param Query $query
* @return \React\Promise\PromiseInterface<\React\Dns\Model\Message>
* @return PromiseInterface<Message>
* resolves with response message on success or rejects with an Exception on error
*/
public function query(Query $query);
public function query(Query $query): PromiseInterface;
}
3 changes: 2 additions & 1 deletion src/Query/FallbackExecutor.php
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
namespace React\Dns\Query;

use React\Promise\Promise;
use React\Promise\PromiseInterface;

final class FallbackExecutor implements ExecutorInterface
{
@@ -15,7 +16,7 @@ public function __construct(ExecutorInterface $executor, ExecutorInterface $fall
$this->fallback = $fallback;
}

public function query(Query $query)
public function query(Query $query): PromiseInterface
{
$cancelled = false;
$promise = $this->executor->query($query);
3 changes: 2 additions & 1 deletion src/Query/HostsFileExecutor.php
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
use React\Dns\Config\HostsFile;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Promise\PromiseInterface;
use function React\Promise\resolve;

/**
@@ -25,7 +26,7 @@ public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
$this->fallback = $fallback;
}

public function query(Query $query)
public function query(Query $query): PromiseInterface
{
if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
// forward lookup for type A or AAAA
4 changes: 2 additions & 2 deletions src/Query/Query.php
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ final class Query
* @param int $type query type, see Message::TYPE_* constants
* @param int $class query class, see Message::CLASS_IN constant
*/
public function __construct($name, $type, $class)
public function __construct(string $name, int $type, int $class)
{
$this->name = $name;
$this->type = $type;
@@ -51,7 +51,7 @@ public function __construct($name, $type, $class)
* @return string "example.com (A)" or "example.com (CLASS0 TYPE1234)"
* @link https://tools.ietf.org/html/rfc3597
*/
public function describe()
public function describe(): string
{
$class = $this->class !== Message::CLASS_IN ? 'CLASS' . $this->class . ' ' : '';

13 changes: 10 additions & 3 deletions src/Query/RetryExecutor.php
Original file line number Diff line number Diff line change
@@ -7,21 +7,28 @@

final class RetryExecutor implements ExecutorInterface
{
/**
* @var ExecutorInterface
*/
private $executor;

/**
* @var int
*/
private $retries;

public function __construct(ExecutorInterface $executor, $retries = 2)
public function __construct(ExecutorInterface $executor, int $retries = 2)
{
$this->executor = $executor;
$this->retries = $retries;
}

public function query(Query $query)
public function query(Query $query): PromiseInterface
{
return $this->tryQuery($query, $this->retries);
}

public function tryQuery(Query $query, $retries)
public function tryQuery(Query $query, int $retries): PromiseInterface
{
$deferred = new Deferred(function () use (&$promise) {
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
3 changes: 2 additions & 1 deletion src/Query/SelectiveTransportExecutor.php
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
namespace React\Dns\Query;

use React\Promise\Promise;
use React\Promise\PromiseInterface;

/**
* Send DNS queries over a UDP or TCP/IP stream transport.
@@ -61,7 +62,7 @@ public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterfa
$this->streamExecutor = $streamExecutor;
}

public function query(Query $query)
public function query(Query $query): PromiseInterface
{
$pending = $this->datagramExecutor->query($query);

16 changes: 7 additions & 9 deletions src/Query/TcpTransportExecutor.php
Original file line number Diff line number Diff line change
@@ -6,8 +6,9 @@
use React\Dns\Protocol\BinaryDumper;
use React\Dns\Protocol\Parser;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\EventLoop\LoopInterface;;
use React\Promise\Deferred;
use React\Promise\PromiseInterface;
use function React\Promise\reject;

/**
@@ -131,11 +132,7 @@ class TcpTransportExecutor implements ExecutorInterface
/** @var string */
private $readChunk = 0xffff;

/**
* @param string $nameserver
* @param ?LoopInterface $loop
*/
public function __construct($nameserver, ?LoopInterface $loop = null)
public function __construct(string $nameserver, ?LoopInterface $loop = null)
{
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
@@ -153,7 +150,7 @@ public function __construct($nameserver, ?LoopInterface $loop = null)
$this->dumper = new BinaryDumper();
}

public function query(Query $query)
public function query(Query $query): PromiseInterface
{
$request = Message::createRequestForQuery($query);

@@ -327,9 +324,10 @@ public function handleRead()
}

/**
* @internal
* @param string $reason
* @param int $code
* @param int $code
*
* @internal
*/
public function closeError($reason, $code = 0)
{
5 changes: 3 additions & 2 deletions src/Query/TimeoutExecutor.php
Original file line number Diff line number Diff line change
@@ -5,21 +5,22 @@
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise\Promise;
use React\Promise\PromiseInterface;

final class TimeoutExecutor implements ExecutorInterface
{
private $executor;
private $loop;
private $timeout;

public function __construct(ExecutorInterface $executor, $timeout, ?LoopInterface $loop = null)
public function __construct(ExecutorInterface $executor, float $timeout, ?LoopInterface $loop = null)
{
$this->executor = $executor;
$this->loop = $loop ?: Loop::get();
$this->timeout = $timeout;
}

public function query(Query $query)
public function query(Query $query): PromiseInterface
{
$promise = $this->executor->query($query);

9 changes: 3 additions & 6 deletions src/Query/UdpTransportExecutor.php
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
use React\Promise\PromiseInterface;
use function React\Promise\reject;

/**
@@ -95,11 +96,7 @@ final class UdpTransportExecutor implements ExecutorInterface
*/
private $maxPacketSize = 512;

/**
* @param string $nameserver
* @param ?LoopInterface $loop
*/
public function __construct($nameserver, ?LoopInterface $loop = null)
public function __construct(string $nameserver, ?LoopInterface $loop = null)
{
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
@@ -117,7 +114,7 @@ public function __construct($nameserver, ?LoopInterface $loop = null)
$this->dumper = new BinaryDumper();
}

public function query(Query $query)
public function query(Query $query): PromiseInterface
{
$request = Message::createRequestForQuery($query);

8 changes: 4 additions & 4 deletions src/Resolver/Factory.php
Original file line number Diff line number Diff line change
@@ -32,11 +32,11 @@ final class Factory
*
* @param Config|string $config DNS Config object (recommended) or single nameserver address
* @param ?LoopInterface $loop
* @return \React\Dns\Resolver\ResolverInterface
* @return ResolverInterface
* @throws \InvalidArgumentException for invalid DNS server address
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
*/
public function create($config, ?LoopInterface $loop = null)
public function create($config, ?LoopInterface $loop = null): ResolverInterface
{
$executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get()));

@@ -55,11 +55,11 @@ public function create($config, ?LoopInterface $loop = null)
* @param Config|string $config DNS Config object (recommended) or single nameserver address
* @param ?LoopInterface $loop
* @param ?CacheInterface $cache
* @return \React\Dns\Resolver\ResolverInterface
* @return ResolverInterface
* @throws \InvalidArgumentException for invalid DNS server address
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
*/
public function createCached($config, ?LoopInterface $loop = null, ?CacheInterface $cache = null)
public function createCached($config, ?LoopInterface $loop = null, ?CacheInterface $cache = null): ResolverInterface
{
// default to keeping maximum of 256 responses in cache unless explicitly given
if (!($cache instanceof CacheInterface)) {
5 changes: 3 additions & 2 deletions src/Resolver/Resolver.php
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
use React\Dns\Query\ExecutorInterface;
use React\Dns\Query\Query;
use React\Dns\RecordNotFoundException;
use React\Promise\PromiseInterface;

/**
* @see ResolverInterface for the base interface
@@ -19,14 +20,14 @@ public function __construct(ExecutorInterface $executor)
$this->executor = $executor;
}

public function resolve($domain)
public function resolve(string $domain): PromiseInterface
{
return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
return $ips[array_rand($ips)];
});
}

public function resolveAll($domain, $type)
public function resolveAll(string $domain, int $type): PromiseInterface
{
$query = new Query($domain, $type, Message::CLASS_IN);

13 changes: 8 additions & 5 deletions src/Resolver/ResolverInterface.php
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

namespace React\Dns\Resolver;

use React\Promise\PromiseInterface;

interface ResolverInterface
{
/**
@@ -39,10 +41,10 @@ interface ResolverInterface
* ```
*
* @param string $domain
* @return \React\Promise\PromiseInterface<string>
* @return PromiseInterface<string>
* resolves with a single IP address on success or rejects with an Exception on error.
*/
public function resolve($domain);
public function resolve(string $domain): PromiseInterface;

/**
* Resolves all record values for the given $domain name and query $type.
@@ -86,9 +88,10 @@ public function resolve($domain);
* $promise->cancel();
* ```
*
* @param string $domain
* @return \React\Promise\PromiseInterface<array>
* @param string $domain domain to resolve
* @param int $type query type, see Message::TYPE_* constants
* @return PromiseInterface<array<string>>
* Resolves with all record values on success or rejects with an Exception on error.
*/
public function resolveAll($domain, $type);
public function resolveAll(string $domain, int $type): PromiseInterface;
}

0 comments on commit 6c7b3af

Please sign in to comment.