From 5b33d0126a3a156fbf0844887394b7a49a70bf86 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Mon, 27 May 2024 16:54:12 +0200 Subject: [PATCH] Add native types to public API This changeset adds native types to the public API as discussed in #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 #222, #223 and reactphp/cache#60 --- src/Config/Config.php | 9 ++++--- src/Config/HostsFile.php | 17 +++++------- src/Model/Message.php | 22 ++++++---------- src/Model/Record.php | 10 +++---- src/Protocol/BinaryDumper.php | 32 +++++++---------------- src/Protocol/Parser.php | 33 ++++++++++-------------- src/Query/CachingExecutor.php | 3 ++- src/Query/CoopExecutor.php | 3 ++- src/Query/ExecutorInterface.php | 4 ++- src/Query/FallbackExecutor.php | 3 ++- src/Query/HostsFileExecutor.php | 3 ++- src/Query/Query.php | 4 +-- src/Query/RetryExecutor.php | 13 +++++++--- src/Query/SelectiveTransportExecutor.php | 3 ++- src/Query/TcpTransportExecutor.php | 16 +++++------- src/Query/TimeoutExecutor.php | 5 ++-- src/Query/UdpTransportExecutor.php | 9 +++---- src/Resolver/Factory.php | 8 +++--- src/Resolver/Resolver.php | 5 ++-- src/Resolver/ResolverInterface.php | 11 +++++--- 20 files changed, 98 insertions(+), 115 deletions(-) diff --git a/src/Config/Config.php b/src/Config/Config.php index 0b19e9af..c30bb26a 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -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 + */ public $nameservers = []; } diff --git a/src/Config/HostsFile.php b/src/Config/HostsFile.php index 1333102d..6381979b 100644 --- a/src/Config/HostsFile.php +++ b/src/Config/HostsFile.php @@ -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 */ - 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 */ - 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); diff --git a/src/Model/Message.php b/src/Model/Message.php index 56cc65b8..045f0279 100644 --- a/src/Model/Message.php +++ b/src/Model/Message.php @@ -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 $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 */ public $questions = []; /** - * @var Record[] + * @var array */ public $answers = []; /** - * @var Record[] + * @var array */ public $authority = []; /** - * @var Record[] + * @var array */ public $additional = []; } diff --git a/src/Model/Record.php b/src/Model/Record.php index c20403f5..2952dd48 100644 --- a/src/Model/Record.php +++ b/src/Model/Record.php @@ -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 */ public $data; /** - * @param string $name - * @param int $type - * @param int $class - * @param int $ttl - * @param string|string[]|array $data + * @param string|array $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; diff --git a/src/Protocol/BinaryDumper.php b/src/Protocol/BinaryDumper.php index 6f4030f6..6fa1ee13 100644 --- a/src/Protocol/BinaryDumper.php +++ b/src/Protocol/BinaryDumper.php @@ -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 $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 $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 $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"; diff --git a/src/Protocol/Parser.php b/src/Protocol/Parser.php index 64b45666..c0ef86af 100644 --- a/src/Protocol/Parser.php +++ b/src/Protocol/Parser.php @@ -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, int}|array{null, null} */ - private function readLabels($data, $consumed, $compressionDepth = 127) + private function readLabels(string $data, int $consumed, int $compressionDepth = 127): array { $labels = []; diff --git a/src/Query/CachingExecutor.php b/src/Query/CachingExecutor.php index 03d56c95..bce57989 100644 --- a/src/Query/CachingExecutor.php +++ b/src/Query/CachingExecutor.php @@ -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; diff --git a/src/Query/CoopExecutor.php b/src/Query/CoopExecutor.php index 3ce41691..ec7dd216 100644 --- a/src/Query/CoopExecutor.php +++ b/src/Query/CoopExecutor.php @@ -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])) { diff --git a/src/Query/ExecutorInterface.php b/src/Query/ExecutorInterface.php index 0bc3945f..dc73d13f 100644 --- a/src/Query/ExecutorInterface.php +++ b/src/Query/ExecutorInterface.php @@ -2,6 +2,8 @@ namespace React\Dns\Query; +use React\Promise\PromiseInterface; + interface ExecutorInterface { /** @@ -39,5 +41,5 @@ interface ExecutorInterface * @return \React\Promise\PromiseInterface<\React\Dns\Model\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; } diff --git a/src/Query/FallbackExecutor.php b/src/Query/FallbackExecutor.php index 6999e1b8..4d876ee3 100644 --- a/src/Query/FallbackExecutor.php +++ b/src/Query/FallbackExecutor.php @@ -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); diff --git a/src/Query/HostsFileExecutor.php b/src/Query/HostsFileExecutor.php index 590f8abe..a7c2d981 100644 --- a/src/Query/HostsFileExecutor.php +++ b/src/Query/HostsFileExecutor.php @@ -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 diff --git a/src/Query/Query.php b/src/Query/Query.php index 4cdfe63f..2b09680e 100644 --- a/src/Query/Query.php +++ b/src/Query/Query.php @@ -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 . ' ' : ''; diff --git a/src/Query/RetryExecutor.php b/src/Query/RetryExecutor.php index e68dcc61..bbecee2a 100644 --- a/src/Query/RetryExecutor.php +++ b/src/Query/RetryExecutor.php @@ -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')) { diff --git a/src/Query/SelectiveTransportExecutor.php b/src/Query/SelectiveTransportExecutor.php index b76632fd..214249bd 100644 --- a/src/Query/SelectiveTransportExecutor.php +++ b/src/Query/SelectiveTransportExecutor.php @@ -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); diff --git a/src/Query/TcpTransportExecutor.php b/src/Query/TcpTransportExecutor.php index c3900e4f..3eb6f780 100644 --- a/src/Query/TcpTransportExecutor.php +++ b/src/Query/TcpTransportExecutor.php @@ -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) { diff --git a/src/Query/TimeoutExecutor.php b/src/Query/TimeoutExecutor.php index 4901db6d..7680f9a2 100644 --- a/src/Query/TimeoutExecutor.php +++ b/src/Query/TimeoutExecutor.php @@ -5,6 +5,7 @@ use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Promise\Promise; +use React\Promise\PromiseInterface; final class TimeoutExecutor implements ExecutorInterface { @@ -12,14 +13,14 @@ final class TimeoutExecutor implements ExecutorInterface 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); diff --git a/src/Query/UdpTransportExecutor.php b/src/Query/UdpTransportExecutor.php index d1cb86e2..0e8e28bb 100644 --- a/src/Query/UdpTransportExecutor.php +++ b/src/Query/UdpTransportExecutor.php @@ -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); diff --git a/src/Resolver/Factory.php b/src/Resolver/Factory.php index eedebe1a..10c38fb3 100644 --- a/src/Resolver/Factory.php +++ b/src/Resolver/Factory.php @@ -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)) { diff --git a/src/Resolver/Resolver.php b/src/Resolver/Resolver.php index 6108b3da..b655080f 100644 --- a/src/Resolver/Resolver.php +++ b/src/Resolver/Resolver.php @@ -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); diff --git a/src/Resolver/ResolverInterface.php b/src/Resolver/ResolverInterface.php index 555a1cb1..fadfee86 100644 --- a/src/Resolver/ResolverInterface.php +++ b/src/Resolver/ResolverInterface.php @@ -2,6 +2,8 @@ namespace React\Dns\Resolver; +use React\Promise\PromiseInterface; + interface ResolverInterface { /** @@ -42,7 +44,7 @@ interface ResolverInterface * @return \React\Promise\PromiseInterface * 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 + * @param string $domain domain to resolve + * @param int $type query type, see Message::TYPE_* constants + * @return \React\Promise\PromiseInterface> * 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; }