From 51f97b7a67829d177dafe5de78931f65247d42df Mon Sep 17 00:00:00 2001 From: flashwave Date: Thu, 1 Aug 2024 22:16:38 +0000 Subject: [PATCH] Updated PHPDocs and some argument lists. --- VERSION | 2 +- src/Bencode/Bencode.php | 98 +++++---- src/ByteFormat.php | 5 +- src/Data/DataException.php | 4 +- src/Data/DbStatementCache.php | 19 +- src/Data/Migration/DbMigrationManager.php | 77 ++++++- src/Data/Migration/FsDbMigrationInfo.php | 5 +- src/Data/Migration/FsDbMigrationRepo.php | 12 +- src/Data/Migration/IDbMigration.php | 10 +- src/Data/Migration/IDbMigrationInfo.php | 22 +- src/Data/Migration/IDbMigrationRepo.php | 10 +- src/Http/Content/BencodedContent.php | 40 +++- src/Http/Content/FormContent.php | 59 +++++- src/Http/Content/IHttpContent.php | 8 +- src/Http/Content/JsonContent.php | 40 +++- src/Http/Content/StreamContent.php | 27 ++- src/Http/Content/StringContent.php | 36 +++- .../ContentHandling/BencodeContentHandler.php | 5 +- src/Http/ContentHandling/IContentHandler.php | 18 +- .../ContentHandling/JsonContentHandler.php | 5 +- .../ContentHandling/StreamContentHandler.php | 5 +- src/Http/ErrorHandling/HtmlErrorHandler.php | 5 +- src/Http/ErrorHandling/IErrorHandler.php | 13 +- src/Http/ErrorHandling/PlainErrorHandler.php | 5 +- src/Http/HttpHeader.php | 24 ++- src/Http/HttpHeaders.php | 44 +++- src/Http/HttpHeadersBuilder.php | 35 +++- src/Http/HttpMessage.php | 87 +++++++- src/Http/HttpMessageBuilder.php | 67 +++++- src/Http/HttpRequest.php | 73 ++++++- src/Http/HttpRequestBuilder.php | 72 ++++++- src/Http/HttpResponse.php | 22 +- src/Http/HttpResponseBuilder.php | 156 +++++++++++++- src/Http/HttpUploadedFile.php | 72 ++++++- src/Http/Routing/HttpDelete.php | 5 +- src/Http/Routing/HttpGet.php | 5 +- src/Http/Routing/HttpOptions.php | 5 +- src/Http/Routing/HttpPatch.php | 5 +- src/Http/Routing/HttpPost.php | 5 +- src/Http/Routing/HttpPut.php | 5 +- src/Http/Routing/HttpRouter.php | 83 +++++++- src/Http/Routing/ResolvedRouteInfo.php | 38 +++- src/IO/FileStream.php | 197 ++++++++++++++++-- src/IO/GenericStream.php | 29 ++- src/IO/IOException.php | 5 +- src/IO/MemoryStream.php | 11 +- src/IO/NetworkStream.php | 90 +++++++- src/IO/ProcessStream.php | 12 +- src/IO/Stream.php | 130 +++++++++++- src/IO/TempFileStream.php | 20 +- src/MediaType.php | 5 +- src/Net/DnsEndPoint.php | 40 +++- src/Net/EndPoint.php | 42 ++-- src/Net/IPAddress.php | 84 +++++++- src/Net/IPAddressRange.php | 44 +++- src/Net/IPEndPoint.php | 35 +++- src/Net/UnixEndPoint.php | 30 ++- src/Performance/PerformanceCounter.php | 21 +- src/Performance/Stopwatch.php | 8 +- src/Performance/TimingPoint.php | 41 +++- src/Performance/Timings.php | 38 +++- src/XDateTime.php | 5 +- tests/BencodeTest.php | 4 +- 63 files changed, 2058 insertions(+), 166 deletions(-) diff --git a/VERSION b/VERSION index 461ce44..5babcac 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2408.12207 +0.2408.12215 diff --git a/src/Bencode/Bencode.php b/src/Bencode/Bencode.php index 77963de..d2d2e58 100644 --- a/src/Bencode/Bencode.php +++ b/src/Bencode/Bencode.php @@ -1,7 +1,7 @@ $value) { - $output .= self::encode((string)$key, $depth - 1); - $output .= self::encode($value, $depth - 1); + foreach($value as $_key => $_value) { + $output .= self::encode((string)$_key, $depth - 1); + $output .= self::encode($_value, $depth - 1); } } return $output . 'e'; case 'object': - if($input instanceof IBencodeSerialisable) - return self::encode($input->bencodeSerialise(), $depth - 1); + if($value instanceof IBencodeSerialisable) + return self::encode($value->bencodeSerialise(), $depth - 1); - $input = get_object_vars($input); + $value = get_object_vars($value); $output = 'd'; - foreach($input as $key => $value) { - $output .= self::encode((string)$key, $depth - 1); - $output .= self::encode($value, $depth - 1); + foreach($value as $_key => $_value) { + $output .= self::encode((string)$_key, $depth - 1); + $output .= self::encode($_value, $depth - 1); } return $output . 'e'; @@ -62,28 +75,39 @@ final class Bencode { } } - public static function decode(mixed $input, int $depth = self::DEFAULT_DEPTH, bool $dictAsObject = false): mixed { - if(is_string($input)) { - $input = TempFileStream::fromString($input); - $input->seek(0); - } elseif(is_resource($input)) - $input = new GenericStream($input); - elseif(!($input instanceof Stream)) - throw new InvalidArgumentException('$input must be a string, an Index Stream or a file resource.'); + /** + * Decodes a bencoded string. + * + * @param mixed $bencoded The bencoded string being decoded. Can be an Index Stream, readable resource or a string. + * @param int $depth Maximum nesting depth of the structure being decoded. The value must be greater than 0. + * @param bool $associative When true, Bencoded dictionaries will be returned as associative arrays; when false, Bencoded dictionaries will be returned as objects. + * @throws InvalidArgumentException If $bencoded string is not set to a valid type. + * @throws InvalidArgumentException If $depth is not greater than 0. + * @throws RuntimeException If the Bencoded stream is in an invalid state. + * @return mixed Returns the bencoded value as an appropriate PHP type. + */ + public static function decode(mixed $bencoded, int $depth = self::DEFAULT_DEPTH, bool $associative = true): mixed { + if(is_string($bencoded)) { + $bencoded = TempFileStream::fromString($bencoded); + $bencoded->seek(0); + } elseif(is_resource($bencoded)) + $bencoded = new GenericStream($bencoded); + elseif(!($bencoded instanceof Stream)) + throw new InvalidArgumentException('$bencoded must be a string, an Index Stream or a file resource.'); if($depth < 1) - throw new RuntimeException('Maximum depth reached, structure is too dense.'); + throw new InvalidArgumentException('Maximum depth reached, structure is too dense.'); - $char = $input->readChar(); + $char = $bencoded->readChar(); if($char === null) - throw new RuntimeException('Unexpected end of stream in $input.'); + throw new RuntimeException('Unexpected end of stream in $bencoded.'); switch($char) { case 'i': $number = ''; for(;;) { - $char = $input->readChar(); + $char = $bencoded->readChar(); if($char === null) throw new RuntimeException('Unexpected end of stream while parsing integer.'); if($char === 'e') @@ -105,13 +129,13 @@ final class Bencode { $list = []; for(;;) { - $char = $input->readChar(); + $char = $bencoded->readChar(); if($char === null) throw new RuntimeException('Unexpected end of stream while parsing list.'); if($char === 'e') break; - $input->seek(-1, Stream::CURRENT); - $list[] = self::decode($input, $depth - 1, $dictAsObject); + $bencoded->seek(-1, Stream::CURRENT); + $list[] = self::decode($bencoded, $depth - 1, $associative); } return $list; @@ -120,18 +144,18 @@ final class Bencode { $dict = []; for(;;) { - $char = $input->readChar(); + $char = $bencoded->readChar(); if($char === null) throw new RuntimeException('Unexpected end of stream while parsing dictionary'); if($char === 'e') break; if(!ctype_digit($char)) throw new RuntimeException('Unexpected dictionary key type, expected a string.'); - $input->seek(-1, Stream::CURRENT); - $dict[self::decode($input, $depth - 1, $dictAsObject)] = self::decode($input, $depth - 1, $dictAsObject); + $bencoded->seek(-1, Stream::CURRENT); + $dict[self::decode($bencoded, $depth - 1, $associative)] = self::decode($bencoded, $depth - 1, $associative); } - if($dictAsObject) + if(!$associative) $dict = (object)$dict; return $dict; @@ -143,7 +167,7 @@ final class Bencode { $length = $char; for(;;) { - $char = $input->readChar(); + $char = $bencoded->readChar(); if($char === null) throw new RuntimeException('Unexpected end of character while parsing string length.'); if($char === ':') @@ -156,7 +180,7 @@ final class Bencode { $length .= $char; } - return $input->read((int)$length); + return $bencoded->read((int)$length); } } } diff --git a/src/ByteFormat.php b/src/ByteFormat.php index 9478c1a..6b82b5b 100644 --- a/src/ByteFormat.php +++ b/src/ByteFormat.php @@ -1,10 +1,13 @@ dbConn = $dbConn; } @@ -20,6 +23,12 @@ class DbStatementCache { return hash('xxh3', $query, true); } + /** + * Gets a cached or creates a new IDbStatement instance. + * + * @param string $query SQL query. + * @return IDbStatement Statement representing the query. + */ public function get(string $query): IDbStatement { $hash = self::hash($query); @@ -32,10 +41,18 @@ class DbStatementCache { return $this->stmts[$hash] = $this->dbConn->prepare($query); } + /** + * Removes a cached statement from the cache. + * + * @param string $query SQL query of the statement to remove from the cache. + */ public function remove(string $query): void { unset($this->stmts[self::hash($query)]); } + /** + * Closes all statement instances and resets the cache. + */ public function clear(): void { foreach($this->stmts as $stmt) $stmt->close(); diff --git a/src/Data/Migration/DbMigrationManager.php b/src/Data/Migration/DbMigrationManager.php index 412bded..eb6db4d 100644 --- a/src/Data/Migration/DbMigrationManager.php +++ b/src/Data/Migration/DbMigrationManager.php @@ -1,7 +1,7 @@ conn instanceof SQLiteConnection ? 'TEXT' : 'VARCHAR(255)'; $this->conn->execute(sprintf(self::CREATE_TRACK_TABLE, $this->tableName, $nameType)); $this->conn->execute(sprintf(self::CREATE_TRACK_INDEX, $this->tableName)); } + /** + * Deletes the migration tracking table. + */ public function destroyTrackingTable(): void { $this->conn->execute(sprintf(self::DESTROY_TRACK_TABLE, $this->tableName)); } + /** + * Prepares statements required for migrations. + */ public function prepareStatements(): void { $this->checkStmt = $this->conn->prepare(sprintf(self::CHECK_STMT, $this->tableName)); $this->insertStmt = $this->conn->prepare(sprintf(self::INSERT_STMT, $this->tableName)); } + /** + * Runs all preparations required for running migrations. + */ public function init(): void { $this->createTrackingTable(); $this->prepareStatements(); } + /** + * Checks if a particular migration is present in the tracking table. + * + * @param string $name Name of the migration. + * @return bool true if the migration has been run, false otherwise. + */ public function checkMigration(string $name): bool { $this->checkStmt->reset(); $this->checkStmt->addParameter(1, $name, DbType::STRING); @@ -76,6 +106,12 @@ EOF; return $result->next() && !$result->isNull(0); } + /** + * Marks a migration as completed in the tracking table. + * + * @param string $name Name of the migration. + * @param ?DateTimeInterface $dateTime Timestamp of when the migration was run, null for now. + */ public function completeMigration(string $name, ?DateTimeInterface $dateTime = null): void { $dateTime = XDateTime::toISO8601String($dateTime); @@ -85,10 +121,25 @@ EOF; $this->insertStmt->execute(); } + /** + * Generates a template for a migration PHP file. + * + * @param string $name Name of the migration. + * @return string Migration template. + */ public function template(string $name): string { return sprintf(self::TEMPLATE, $name); } + /** + * Generates a filename for a migration PHP file. + * + * @param string $name Name of the migration. + * @param ?DateTimeInterface $dateTime Timestamp of when the migration was created, null for now. + * @throws InvalidArgumentException If $name is empty. + * @throws InvalidArgumentException If $name contains invalid characters. + * @return string Migration filename. + */ public function createFileName(string $name, ?DateTimeInterface $dateTime = null): string { if(empty($name)) throw new InvalidArgumentException('$name may not be empty.'); @@ -102,6 +153,15 @@ EOF; return $dateTime->format('Y_m_d_His_') . trim($name, '_'); } + /** + * Generates a class name for a migration PHP file. + * + * @param string $name Name of the migration. + * @param ?DateTimeInterface $dateTime Timestamp of when the migration was created, null for now. + * @throws InvalidArgumentException If $name is empty. + * @throws InvalidArgumentException If $name contains invalid characters. + * @return string Migration class name. + */ public function createClassName(string $name, ?DateTimeInterface $dateTime = null): string { if(empty($name)) throw new InvalidArgumentException('$name may not be empty.'); @@ -121,6 +181,13 @@ EOF; return $name . $dateTime->format('_Ymd_His'); } + /** + * Generate names for a PHP migration file. + * + * @param string $baseName Name of the migration. + * @param ?DateTimeInterface $dateTime Timestamp of when the migration was created, null for now. + * @return object Object containing names for a PHP migration file. + */ public function createNames(string $baseName, ?DateTimeInterface $dateTime = null): object { $dateTime ??= new DateTimeImmutable('now'); @@ -131,6 +198,12 @@ EOF; return $names; } + /** + * Runs all migrations present in a migration repository. This process is irreversible! + * + * @param IDbMigrationRepo $migrations Migrations repository to fetch migrations from. + * @return array Names of migrations that have been completed. + */ public function processMigrations(IDbMigrationRepo $migrations): array { $migrations = $migrations->getMigrations(); $completed = []; diff --git a/src/Data/Migration/FsDbMigrationInfo.php b/src/Data/Migration/FsDbMigrationInfo.php index 417c661..c8799e4 100644 --- a/src/Data/Migration/FsDbMigrationInfo.php +++ b/src/Data/Migration/FsDbMigrationInfo.php @@ -1,7 +1,7 @@ path = $path; $this->name = $name = pathinfo($path, PATHINFO_FILENAME); diff --git a/src/Data/Migration/FsDbMigrationRepo.php b/src/Data/Migration/FsDbMigrationRepo.php index 5c21077..4e792aa 100644 --- a/src/Data/Migration/FsDbMigrationRepo.php +++ b/src/Data/Migration/FsDbMigrationRepo.php @@ -1,11 +1,14 @@ path)) mkdir($this->path, 0777, true); diff --git a/src/Data/Migration/IDbMigration.php b/src/Data/Migration/IDbMigration.php index c9a67c5..878d522 100644 --- a/src/Data/Migration/IDbMigration.php +++ b/src/Data/Migration/IDbMigration.php @@ -1,12 +1,20 @@ content = $content; } + /** + * Retrieves unencoded content. + * + * @return mixed Content. + */ public function getContent(): mixed { return $this->content; } @@ -26,6 +36,11 @@ class BencodedContent implements Stringable, IHttpContent, IBencodeSerialisable return $this->content; } + /** + * Encodes the content. + * + * @return string Bencoded string. + */ public function encode(): string { return Bencode::encode($this->content); } @@ -34,14 +49,31 @@ class BencodedContent implements Stringable, IHttpContent, IBencodeSerialisable return $this->encode(); } - public static function fromEncoded(Stream|string $encoded): BencodedContent { + /** + * Creates an instance from encoded content. + * + * @param mixed $encoded Bencoded content. + * @return BencodedContent Instance representing the provided content. + */ + public static function fromEncoded(mixed $encoded): BencodedContent { return new BencodedContent(Bencode::decode($encoded)); } + /** + * Creates an instance from an encoded file. + * + * @param string $path Path to the bencoded file. + * @return BencodedContent Instance representing the provided path. + */ public static function fromFile(string $path): BencodedContent { return self::fromEncoded(FileStream::openRead($path)); } + /** + * Creates an instance from the raw request body. + * + * @return BencodedContent Instance representing the request body. + */ public static function fromRequest(): BencodedContent { return self::fromFile('php://input'); } diff --git a/src/Http/Content/FormContent.php b/src/Http/Content/FormContent.php index ecf4659..b9d715e 100644 --- a/src/Http/Content/FormContent.php +++ b/src/Http/Content/FormContent.php @@ -1,50 +1,102 @@ $postFields Form fields. + * @param array $uploadedFiles Uploaded files. + */ public function __construct(array $postFields, array $uploadedFiles) { $this->postFields = $postFields; $this->uploadedFiles = $uploadedFiles; } + /** + * Retrieves a form field with filtering. + * + * @param string $name Name of the form field. + * @param int $filter A PHP filter extension filter constant. + * @param array|int $options Options for the PHP filter. + * @return mixed Value of the form field, null if not present. + */ public function getParam(string $name, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed { if(!isset($this->postFields[$name])) return null; return filter_var($this->postFields[$name] ?? null, $filter, $options); } + /** + * Checks if a form field is present. + * + * @param string $name Name of the form field. + * @return bool true if the field is present, false if not. + */ public function hasParam(string $name): bool { return isset($this->postFields[$name]); } + /** + * Gets all form fields. + * + * @return array Form fields. + */ public function getParams(): array { return $this->postFields; } + /** + * Gets all form fields as a query string. + * + * @param bool $spacesAsPlus true if spaces should be represented with a +, false if %20. + * @return string Query string representation of form fields. + */ public function getParamString(bool $spacesAsPlus = false): string { return http_build_query($this->postFields, '', '&', $spacesAsPlus ? PHP_QUERY_RFC1738 : PHP_QUERY_RFC3986); } + /** + * Checks if a file upload is present. + * + * @param string $name Name of the form field. + * @return bool true if the upload is present, false if not. + */ public function hasUploadedFile(string $name): bool { return isset($this->uploadedFiles[$name]); } + /** + * Retrieves a file upload. + * + * @param string $name Name of the form field. + * @throws RuntimeException If no uploaded file with form field $name is present. + * @return HttpUploadedFile Uploaded file info. + */ public function getUploadedFile(string $name): HttpUploadedFile { if(!isset($this->uploadedFiles[$name])) throw new RuntimeException('No file with name $name present.'); return $this->uploadedFiles[$name]; } + /** + * Creates an instance from an array of form fields and uploaded files. + * + * @param array $post Form fields. + * @param array $files Uploaded files. + * @return FormContent Instance representing the request body. + */ public static function fromRaw(array $post, array $files): FormContent { return new FormContent( $post, @@ -52,6 +104,11 @@ class FormContent implements IHttpContent { ); } + /** + * Creates an instance from the $_POST and $_FILES superglobals. + * + * @return FormContent Instance representing the request body. + */ public static function fromRequest(): FormContent { return self::fromRaw($_POST, $_FILES); } diff --git a/src/Http/Content/IHttpContent.php b/src/Http/Content/IHttpContent.php index c48cd8a..23da171 100644 --- a/src/Http/Content/IHttpContent.php +++ b/src/Http/Content/IHttpContent.php @@ -1,11 +1,13 @@ content = $content; } + /** + * Retrieves unencoded content. + * + * @return mixed Content. + */ public function getContent(): mixed { return $this->content; } @@ -25,6 +35,11 @@ class JsonContent implements Stringable, IHttpContent, JsonSerializable { return $this->content; } + /** + * Encodes the content. + * + * @return string JSON encoded string. + */ public function encode(): string { return json_encode($this->content); } @@ -33,14 +48,31 @@ class JsonContent implements Stringable, IHttpContent, JsonSerializable { return $this->encode(); } - public static function fromEncoded(Stream|string $encoded): JsonContent { + /** + * Creates an instance from encoded content. + * + * @param mixed $encoded JSON encoded content. + * @return JsonContent Instance representing the provided content. + */ + public static function fromEncoded(mixed $encoded): JsonContent { return new JsonContent(json_decode($encoded)); } + /** + * Creates an instance from an encoded file. + * + * @param string $path Path to the JSON encoded file. + * @return JsonContent Instance representing the provided path. + */ public static function fromFile(string $path): JsonContent { return self::fromEncoded(FileStream::openRead($path)); } + /** + * Creates an instance from the raw request body. + * + * @return JsonContent Instance representing the request body. + */ public static function fromRequest(): JsonContent { return self::fromFile('php://input'); } diff --git a/src/Http/Content/StreamContent.php b/src/Http/Content/StreamContent.php index 2dff0fe..70b9d93 100644 --- a/src/Http/Content/StreamContent.php +++ b/src/Http/Content/StreamContent.php @@ -1,21 +1,31 @@ stream = $stream; } + /** + * Retrieves the underlying stream. + * + * @return Stream Underlying stream. + */ public function getStream(): Stream { return $this->stream; } @@ -24,12 +34,23 @@ class StreamContent implements Stringable, IHttpContent { return (string)$this->stream; } + /** + * Creates an instance from a file. + * + * @param string $path Path to the file. + * @return StreamContent Instance representing the provided path. + */ public static function fromFile(string $path): StreamContent { return new StreamContent( FileStream::openRead($path) ); } + /** + * Creates an instance from the raw request body. + * + * @return StreamContent Instance representing the request body. + */ public static function fromRequest(): StreamContent { return self::fromFile('php://input'); } diff --git a/src/Http/Content/StringContent.php b/src/Http/Content/StringContent.php index aed1bcf..a95bcbe 100644 --- a/src/Http/Content/StringContent.php +++ b/src/Http/Content/StringContent.php @@ -1,19 +1,30 @@ string = $string; } + /** + * Retrieves the underlying string. + * + * @return string Underlying string. + */ public function getString(): string { return $this->string; } @@ -22,14 +33,31 @@ class StringContent implements Stringable, IHttpContent { return $this->string; } - public static function fromObject(string $string): StringContent { - return new StringContent($string); + /** + * Creates an instance an existing object. + * + * @param Stringable|string $string Object to cast to a string. + * @return StringContent Instance representing the provided object. + */ + public static function fromObject(Stringable|string $string): StringContent { + return new StringContent((string)$string); } + /** + * Creates an instance from a file. + * + * @param string $path Path to the file. + * @return StringContent Instance representing the provided path. + */ public static function fromFile(string $path): StringContent { return new StringContent(file_get_contents($path)); } + /** + * Creates an instance from the raw request body. + * + * @return StringContent Instance representing the request body. + */ public static function fromRequest(): StringContent { return self::fromFile('php://input'); } diff --git a/src/Http/ContentHandling/BencodeContentHandler.php b/src/Http/ContentHandling/BencodeContentHandler.php index 9672813..9b97923 100644 --- a/src/Http/ContentHandling/BencodeContentHandler.php +++ b/src/Http/ContentHandling/BencodeContentHandler.php @@ -1,7 +1,7 @@ diff --git a/src/Http/ErrorHandling/IErrorHandler.php b/src/Http/ErrorHandling/IErrorHandler.php index d8d1842..da85d87 100644 --- a/src/Http/ErrorHandling/IErrorHandler.php +++ b/src/Http/ErrorHandling/IErrorHandler.php @@ -1,13 +1,24 @@ setTypePlain(); diff --git a/src/Http/HttpHeader.php b/src/Http/HttpHeader.php index 68873a4..5cef3ca 100644 --- a/src/Http/HttpHeader.php +++ b/src/Http/HttpHeader.php @@ -1,29 +1,51 @@ name = $name; $this->lines = $lines; } + /** + * Retrieves the name of the header. + * + * @return string Header name. + */ public function getName(): string { return $this->name; } + /** + * Retrieves lines of the header. + * + * @return string[] Header lines. + */ public function getLines(): array { return $this->lines; } + /** + * Retrieves the first line of the header. + * + * @return string First header line. + */ public function getFirstLine(): string { return $this->lines[0]; } diff --git a/src/Http/HttpHeaders.php b/src/Http/HttpHeaders.php index 391450b..e45d1cc 100644 --- a/src/Http/HttpHeaders.php +++ b/src/Http/HttpHeaders.php @@ -1,15 +1,21 @@ headers = $real; } + /** + * Checks if a header is present. + * + * @param string $name Name of the header. + * @return bool true if the header is present. + */ public function hasHeader(string $name): bool { return isset($this->headers[strtolower($name)]); } + /** + * Retrieves all headers. + * + * @return HttpHeader[] All headers. + */ public function getHeaders(): array { return array_values($this->headers); } + /** + * Retrieves a header by name. + * + * @param string $name Name of the header. + * @throws RuntimeException If no header with $name exists. + * @return HttpHeader Instance of the requested header. + */ public function getHeader(string $name): HttpHeader { $name = strtolower($name); if(!isset($this->headers[$name])) @@ -36,18 +60,36 @@ class HttpHeaders { return $this->headers[$name]; } + /** + * Gets the contents of a header as a string. + * + * @param string $name Name of the header. + * @return string Contents of the header. + */ public function getHeaderLine(string $name): string { if(!$this->hasHeader($name)) return ''; return (string)$this->getHeader($name); } + /** + * Gets lines of a header. + * + * @param string $name Name of the header. + * @return string[] Header lines. + */ public function getHeaderLines(string $name): array { if(!$this->hasHeader($name)) return []; return $this->getHeader($name)->getLines(); } + /** + * Gets the first line of a header. + * + * @param string $name Name of the header. + * @return string First line of the header. + */ public function getHeaderFirstLine(string $name): string { if(!$this->hasHeader($name)) return ''; diff --git a/src/Http/HttpHeadersBuilder.php b/src/Http/HttpHeadersBuilder.php index ec85c19..da7cccd 100644 --- a/src/Http/HttpHeadersBuilder.php +++ b/src/Http/HttpHeadersBuilder.php @@ -1,15 +1,25 @@ headers[$nameLower])) @@ -17,18 +27,41 @@ class HttpHeadersBuilder { $this->headers[$nameLower][] = $value; } + /** + * Sets a header to the HTTP message. + * If a header with the same name is already present, it will be overwritten. + * + * @param string $name Name of the header to set. + * @param mixed $value Value to apply for this header. + */ public function setHeader(string $name, mixed $value): void { $this->headers[strtolower($name)] = [$name, $value]; } + /** + * Removes a header from the HTTP message. + * + * @param string $name Name of the header to remove. + */ public function removeHeader(string $name): void { unset($this->headers[strtolower($name)]); } + /** + * Checks if a header is already present. + * + * @param string $name Name of the header. + * @return bool true if it is present. + */ public function hasHeader(string $name): bool { return isset($this->headers[strtolower($name)]); } + /** + * Create HttpHeaders instance from this builder. + * + * @return HttpHeaders Instance containing HTTP headers. + */ public function toHeaders(): HttpHeaders { $headers = []; diff --git a/src/Http/HttpMessage.php b/src/Http/HttpMessage.php index be7e496..3f21e9f 100644 --- a/src/Http/HttpMessage.php +++ b/src/Http/HttpMessage.php @@ -1,10 +1,11 @@ version = $version; $this->headers = $headers; $this->content = $content; } + /** + * Retrieves the HTTP version of this message. + * + * @return string HTTP version. + */ public function getHttpVersion(): string { return $this->version; } + /** + * Retrieves the collection of headers for this HTTP message. + * + * @return HttpHeader[] HTTP headers. + */ public function getHeaders(): array { return $this->headers->getHeaders(); } + /** + * Checks if a header is present. + * + * @param string $name Name of the header. + * @return bool true if the header is present. + */ public function hasHeader(string $name): bool { return $this->headers->hasHeader($name); } + /** + * Retrieves a header by name. + * + * @param string $name Name of the header. + * @throws RuntimeException If no header with $name exists. + * @return HttpHeader Instance of the requested header. + */ public function getHeader(string $name): HttpHeader { return $this->headers->getHeader($name); } + /** + * Gets the contents of a header as a string. + * + * @param string $name Name of the header. + * @return string Contents of the header. + */ public function getHeaderLine(string $name): string { return $this->headers->getHeaderLine($name); } + /** + * Gets lines of a header. + * + * @param string $name Name of the header. + * @return string[] Header lines. + */ public function getHeaderLines(string $name): array { return $this->headers->getHeaderLines($name); } + /** + * Gets the first line of a header. + * + * @param string $name Name of the header. + * @return string First line of the header. + */ public function getHeaderFirstLine(string $name): string { return $this->headers->getHeaderFirstLine($name); } + /** + * Checks whether this HTTP message has body contents. + * + * @return bool true if it has body contents. + */ public function hasContent(): bool { return $this->content !== null; } + /** + * Retrieves message body contents, if present. + * + * @return ?IHttpContent Body contents, null if none present. + */ public function getContent(): ?IHttpContent { return $this->content; } + /** + * Checks if the body content is JsonContent. + * + * @return bool true if it is JsonContent. + */ public function isJsonContent(): bool { return $this->content instanceof JsonContent; } + /** + * Checks if the body content is FormContent. + * + * @return bool true if it is FormContent. + */ public function isFormContent(): bool { return $this->content instanceof FormContent; } + /** + * Checks if the body content is StreamContent. + * + * @return bool true if it is StreamContent. + */ public function isStreamContent(): bool { return $this->content instanceof StreamContent; } + /** + * Checks if the body content is StringContent. + * + * @return bool true if it is StringContent. + */ public function isStringContent(): bool { return $this->content instanceof StringContent; } + /** + * Checks if the body content is BencodedContent. + * + * @return bool true if it is BencodedContent. + */ public function isBencodedContent(): bool { return $this->content instanceof BencodedContent; } diff --git a/src/Http/HttpMessageBuilder.php b/src/Http/HttpMessageBuilder.php index 673f7db..e9b2e59 100644 --- a/src/Http/HttpMessageBuilder.php +++ b/src/Http/HttpMessageBuilder.php @@ -1,7 +1,7 @@ headers = new HttpHeadersBuilder; } + /** + * Returns HTTP version of the message. + * + * @return string HTTP version. + */ protected function getHttpVersion(): string { return $this->version ?? '1.1'; } + /** + * Sets HTTP version for the message. + * + * @param string $version HTTP version. + */ public function setHttpVersion(string $version): void { $this->version = $version; } + /** + * Create HttpHeaders instance from this builder. + * + * @return HttpHeaders Instance containing HTTP headers. + */ protected function getHeaders(): HttpHeaders { return $this->headers->toHeaders(); } + /** + * Retrieves instance of the underlying HTTP header builder. + * + * @return HttpHeadersBuilder HTTP header builder. + */ public function getHeadersBuilder(): HttpHeadersBuilder { return $this->headers; } + /** + * Adds a header to the HTTP message. + * If a header with the same name is already present, it will be appended with a new line. + * + * @param string $name Name of the header to add. + * @param mixed $value Value to apply for this header. + */ public function addHeader(string $name, mixed $value): void { $this->headers->addHeader($name, $value); } + /** + * Sets a header to the HTTP message. + * If a header with the same name is already present, it will be overwritten. + * + * @param string $name Name of the header to set. + * @param mixed $value Value to apply for this header. + */ public function setHeader(string $name, mixed $value): void { $this->headers->setHeader($name, $value); } + /** + * Removes a header from the HTTP message. + * + * @param string $name Name of the header to remove. + */ public function removeHeader(string $name): void { $this->headers->removeHeader($name); } + /** + * Checks if a header is already present. + * + * @param string $name Name of the header. + * @return bool true if it is present. + */ public function hasHeader(string $name): bool { return $this->headers->hasHeader($name); } + /** + * Retrieves HTTP message body content. + * + * @return ?IHttpContent Body content. + */ protected function getContent(): ?IHttpContent { return $this->content; } - /** @phpstan-impure */ + /** + * Checks whether this HTTP message has body contents. + * + * @return bool true if it has body contents. + * @phpstan-impure + */ public function hasContent(): bool { return $this->content !== null; } + /** + * Sets HTTP message body contents. + * + * @param IHttpContent|Stream|string|null $content Body contents + */ public function setContent(IHttpContent|Stream|string|null $content): void { if($content instanceof Stream) $content = new StreamContent($content); diff --git a/src/Http/HttpRequest.php b/src/Http/HttpRequest.php index 6bd7db4..a768ab2 100644 --- a/src/Http/HttpRequest.php +++ b/src/Http/HttpRequest.php @@ -1,7 +1,7 @@ $params HTTP request query parameters. + * @param array $cookies HTTP request cookies. + * @param HttpHeaders $headers HTTP message headers. + * @param ?IHttpContent $content Body contents. + */ public function __construct( string $version, string $method, @@ -36,46 +48,105 @@ class HttpRequest extends HttpMessage { parent::__construct($version, $headers, $content); } + /** + * Retrieves the HTTP request method. + * + * @return string HTTP request method. + */ public function getMethod(): string { return $this->method; } + /** + * Retrieves the HTTP request path. + * + * @return string HTTP request path. + */ public function getPath(): string { return $this->path; } + /** + * Retrieves all HTTP request query fields as a query string. + * + * @param bool $spacesAsPlus true if spaces should be represented with a +, false if %20. + * @return string Query string representation of query fields. + */ public function getParamString(bool $spacesAsPlus = false): string { return http_build_query($this->params, '', '&', $spacesAsPlus ? PHP_QUERY_RFC1738 : PHP_QUERY_RFC3986); } + /** + * Retrieves all HTTP request query fields. + * + * @return array Query fields. + */ public function getParams(): array { return $this->params; } + /** + * Retrieves an HTTP request query field, or null if it is not present. + * + * @param string $name Name of the request query field. + * @param int $filter A PHP filter extension filter constant. + * @param array|int $options Options for the PHP filter. + * @return mixed Value of the query field, null if not present. + */ public function getParam(string $name, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed { if(!isset($this->params[$name])) return null; return filter_var($this->params[$name] ?? null, $filter, $options); } + /** + * Checks if a query field is present. + * + * @param string $name Name of the query field. + * @return bool true if the field is present, false if not. + */ public function hasParam(string $name): bool { return isset($this->params[$name]); } + /** + * Retrieves all HTTP request cookies. + * + * @return array All cookies. + */ public function getCookies(): array { return $this->cookies; } + /** + * Retrieves an HTTP request cookie, or null if it is not present. + * + * @param string $name Name of the request cookie. + * @param int $filter A PHP filter extension filter constant. + * @param array|int $options Options for the PHP filter. + * @return mixed Value of the cookie, null if not present. + */ public function getCookie(string $name, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed { if(!isset($this->cookies[$name])) return null; return filter_var($this->cookies[$name] ?? null, $filter, $options); } + /** + * Checks if a cookie is present. + * + * @param string $name Name of the cookie. + * @return bool true if the cookie is present, false if not. + */ public function hasCookie(string $name): bool { return isset($this->cookies[$name]); } + /** + * Creates an HttpRequest instance from the current request. + * + * @return HttpRequest An instance representing the current request. + */ public static function fromRequest(): HttpRequest { $build = new HttpRequestBuilder; $build->setHttpVersion($_SERVER['SERVER_PROTOCOL']); diff --git a/src/Http/HttpRequestBuilder.php b/src/Http/HttpRequestBuilder.php index be0b3bb..0fcc4e8 100644 --- a/src/Http/HttpRequestBuilder.php +++ b/src/Http/HttpRequestBuilder.php @@ -1,64 +1,134 @@ method; } + /** + * Sets HTTP request method. + * + * @param string $method HTTP request method. + */ public function setMethod(string $method): void { $this->method = $method; } + /** + * Returns HTTP request path. + * + * @return string HTTP request path. + */ protected function getPath(): string { return $this->path; } + /** + * Sets HTTP request path. + * + * @param string $path HTTP request path. + */ public function setPath(string $path): void { $this->path = $path; } + /** + * Returns HTTP request query params. + * + * @return array HTTP request query params. + */ protected function getParams(): array { return $this->params; } + /** + * Sets HTTP request query params. + * + * @param array $params HTTP request query params. + */ public function setParams(array $params): void { $this->params = $params; } + /** + * Sets a HTTP request query param. + * + * @param string $name Name of the query field. + * @param mixed $value Value of the query field. + */ public function setParam(string $name, mixed $value): void { $this->params[$name] = $value; } + /** + * Removes a HTTP request query param. + * + * @param string $name Name of the query field. + */ public function removeParam(string $name): void { unset($this->params[$name]); } + /** + * Returns HTTP request cookies. + * + * @return array HTTP request cookies. + */ protected function getCookies(): array { return $this->cookies; } + /** + * Sets HTTP request cookies. + * + * @param array $cookies HTTP request cookies. + */ public function setCookies(array $cookies): void { $this->cookies = $cookies; } + /** + * Sets a HTTP request cookie. + * + * @param string $name Name of the cookie. + * @param mixed $value Value of the cookie. + */ public function setCookie(string $name, mixed $value): void { $this->cookies[$name] = $value; } + /** + * Removes a HTTP request cookie. + * + * @param string $name Name of the cookie. + */ public function removeCookie(string $name): void { unset($this->cookies[$name]); } + /** + * Creates a HttpRequest instance from this builder. + * + * @return HttpRequest An instance representing this builder. + */ public function toRequest(): HttpRequest { return new HttpRequest( $this->getHttpVersion(), diff --git a/src/Http/HttpResponse.php b/src/Http/HttpResponse.php index 690460a..c078468 100644 --- a/src/Http/HttpResponse.php +++ b/src/Http/HttpResponse.php @@ -1,16 +1,26 @@ statusCode; } + /** + * Retrieves the HTTP response message status text. + * + * @return string HTTP status text. + */ public function getStatusText(): string { return $this->statusText; } diff --git a/src/Http/HttpResponseBuilder.php b/src/Http/HttpResponseBuilder.php index f9d99c4..9602b66 100644 --- a/src/Http/HttpResponseBuilder.php +++ b/src/Http/HttpResponseBuilder.php @@ -1,7 +1,7 @@ statusCode < 0 ? 200 : $this->statusCode; } + /** + * Sets the HTTP status code for the target HTTP response message. + * + * @param int $statusCode HTTP status code. + */ public function setStatusCode(int $statusCode): void { $this->statusCode = $statusCode; } + /** + * Checks whether this response message builder has a status code. + * + * @return bool true if it has a status code greater than and including 100. + */ public function hasStatusCode(): bool { return $this->statusCode >= 100; } + /** + * Retrieves the status text for this response. + * + * @return string Status text. + */ public function getStatusText(): string { return $this->statusText ?? self::STATUS[$this->getStatusCode()] ?? 'Unknown Status'; } + /** + * Sets the status text for this response. + * + * @param string $statusText Status text. + */ public function setStatusText(string $statusText): void { $this->statusText = (string)$statusText; } + /** + * Clears the status text for this message. + */ public function clearStatusText(): void { $this->statusText = null; } + /** + * Adds a cookie to this response. + * + * @param string $name Name of the cookie. + * @param mixed $value Value of the cookie. + * @param DateTimeInterface|int|null $expires Point at which the cookie should expire. + * @param string $path Path to which to apply the cookie. + * @param string $domain Domain name to which to apply the cookie. + * @param bool $secure true to only make the client include this cookie in a secure context. + * @param bool $httpOnly true to make the client hide this cookie to Javascript code. + * @param bool $sameSiteStrict true to set the SameSite attribute to Strict, false to set it to Lax. + */ public function addCookie( string $name, mixed $value, @@ -71,6 +114,16 @@ class HttpResponseBuilder extends HttpMessageBuilder { $this->addHeader('Set-Cookie', $cookie); } + /** + * Make the client remove a cookie. + * + * @param string $name Name of the cookie. + * @param string $path Path to which the cookie was applied to cookie. + * @param string $domain Domain name to which the cookie was applied to cookie. + * @param bool $secure true to only make the client include this cookie in a secure context. + * @param bool $httpOnly true to make the client hide this cookie to Javascript code. + * @param bool $sameSiteStrict true to set the SameSite attribute to Strict, false to set it to Lax. + */ public function removeCookie( string $name, string $path = '', @@ -82,11 +135,22 @@ class HttpResponseBuilder extends HttpMessageBuilder { $this->addCookie($name, '', -9001, $path, $domain, $secure, $httpOnly, $sameSiteStrict); } + /** + * Specifies a redirect header on the response message. + * + * @param string $to Target to redirect to. + * @param bool $permanent true for status code 301, false for status code 302. Makes the client cache the redirect. + */ public function redirect(string $to, bool $permanent = false): void { $this->setStatusCode($permanent ? 301 : 302); $this->setHeader('Location', $to); } + /** + * Sets a Vary header. + * + * @param string|array $headers Header or headers that will vary. + */ public function addVary(string|array $headers): void { if(!is_array($headers)) $headers = [$headers]; @@ -100,10 +164,21 @@ class HttpResponseBuilder extends HttpMessageBuilder { $this->setHeader('Vary', implode(', ', $this->vary)); } + /** + * Sets an X-Powered-By header. + * + * @param string $poweredBy Thing that your website is powered by. + */ public function setPoweredBy(string $poweredBy): void { $this->setHeader('X-Powered-By', $poweredBy); } + /** + * Sets an ETag header. + * + * @param string $eTag Unique identifier for the current state of the content. + * @param bool $weak Whether to use weak matching. + */ public function setEntityTag(string $eTag, bool $weak = false): void { $eTag = '"' . $eTag . '"'; @@ -113,6 +188,11 @@ class HttpResponseBuilder extends HttpMessageBuilder { $this->setHeader('ETag', $eTag); } + /** + * Sets a Server-Timing header. + * + * @param Timings $timings Timings to supply to the devtools. + */ public function setServerTiming(Timings $timings): void { $laps = $timings->getLaps(); $timings = []; @@ -129,50 +209,109 @@ class HttpResponseBuilder extends HttpMessageBuilder { $this->setHeader('Server-Timing', implode(', ', $timings)); } + /** + * Whether a Content-Type header is present. + * + * @return bool true if a Content-Type header is present. + */ public function hasContentType(): bool { return $this->hasHeader('Content-Type'); } + /** + * Sets a Content-Type header. + * + * @param MediaType|string $mediaType Media type to set as the content type of the response body. + */ public function setContentType(MediaType|string $mediaType): void { $this->setHeader('Content-Type', (string)$mediaType); } + /** + * Sets the Content-Type to 'application/octet-stream' for raw content. + */ public function setTypeStream(): void { $this->setContentType('application/octet-stream'); } + /** + * Sets the Content-Type to 'text/plain' with a provided character set for plain text content. + * + * @param string $charset Character set. + */ public function setTypePlain(string $charset = 'us-ascii'): void { $this->setContentType('text/plain; charset=' . $charset); } + /** + * Sets the Content-Type to 'text/html' with a provided character set for HTML content. + * + * @param string $charset Character set. + */ public function setTypeHTML(string $charset = 'utf-8'): void { $this->setContentType('text/html; charset=' . $charset); } + /** + * Sets the Content-Type to 'application/json' with a provided character set for JSON content. + * + * @param string $charset Character set. + */ public function setTypeJson(string $charset = 'utf-8'): void { $this->setContentType('application/json; charset=' . $charset); } + /** + * Sets the Content-Type to 'application/xml' with a provided character set for XML content. + * + * @param string $charset Character set. + */ public function setTypeXML(string $charset = 'utf-8'): void { $this->setContentType('application/xml; charset=' . $charset); } + /** + * Sets the Content-Type to 'text/css' with a provided character set for CSS content. + * + * @param string $charset Character set. + */ public function setTypeCSS(string $charset = 'utf-8'): void { $this->setContentType('text/css; charset=' . $charset); } + /** + * Sets the Content-Type to 'application/javascript' with a provided character set for Javascript content. + * + * @param string $charset Character set. + */ public function setTypeJS(string $charset = 'utf-8'): void { $this->setContentType('application/javascript; charset=' . $charset); } + /** + * Specifies an Apache Web Server X-Sendfile header. + * + * @param string $absolutePath Absolute path to the content to serve. + */ public function sendFile(string $absolutePath): void { $this->setHeader('X-Sendfile', $absolutePath); } + /** + * Specifies an NGINX X-Accel-Redirect header. + * + * @param string $uri Relative URI to the content to serve. + */ public function accelRedirect(string $uri): void { $this->setHeader('X-Accel-Redirect', $uri); } + /** + * Specifies a Content-Disposition header to supply a filename for the content. + * + * @param string $fileName Name of the file. + * @param bool $attachment true if the browser should prompt the user to download the body. + */ public function setFileName(string $fileName, bool $attachment = false): void { $this->setHeader( 'Content-Disposition', @@ -184,6 +323,11 @@ class HttpResponseBuilder extends HttpMessageBuilder { ); } + /** + * Specifies a Clear-Site-Data header. + * + * @param string ...$directives Directives to specify. + */ public function clearSiteData(string ...$directives): void { $this->setHeader( 'Clear-Site-Data', @@ -191,10 +335,20 @@ class HttpResponseBuilder extends HttpMessageBuilder { ); } + /** + * Specifies a Cache-Control header. + * + * @param string ...$directives Directives to specify. + */ public function setCacheControl(string ...$directives): void { $this->setHeader('Cache-Control', implode(', ', $directives)); } + /** + * Creates an instance of HttpResponse from this builder. + * + * @return HttpResponse An instance representing this builder. + */ public function toResponse(): HttpResponse { return new HttpResponse( $this->getHttpVersion(), diff --git a/src/Http/HttpUploadedFile.php b/src/Http/HttpUploadedFile.php index aa7a35c..8327acf 100644 --- a/src/Http/HttpUploadedFile.php +++ b/src/Http/HttpUploadedFile.php @@ -1,7 +1,7 @@ suggestedMediaType = $suggestedMediaType; } + /** + * Retrieves the PHP file upload error code. + * + * @return int PHP file upload error code. + */ public function getErrorCode(): int { return $this->errorCode; } + /** + * Retrieves the size of the uploaded file. + * + * @return int Size of uploaded file. + */ public function getSize(): int { return $this->size; } + /** + * Retrieves the local path to the uploaded file. + * + * @return ?string Path to file, or null. + */ public function getLocalFileName(): ?string { return $this->localFileName; } + /** + * Retrieves media type of the uploaded file. + * + * @return ?MediaType Type of file, or null. + */ public function getLocalMediaType(): ?MediaType { return MediaType::fromPath($this->localFileName); } + /** + * Retrieves the suggested name for the uploaded file. + * + * @return string Suggested name for the file. + */ public function getSuggestedFileName(): string { return $this->suggestedFileName; } + /** + * Retrieves the suggested media type for the uploaded file. + * + * @return MediaType Suggested type for the file. + */ public function getSuggestedMediaType(): MediaType { return $this->suggestedMediaType; } + /** + * Checks whether the file has been moved to its final destination. + * + * @return bool true if it has been moved. + */ public function hasMoved(): bool { return $this->hasMoved; } + /** + * Gets a read-only stream to the uploaded file. + * + * @throws RuntimeException If the file cannot be opened. + * @return Stream Read-only stream of the upload file. + */ public function getStream(): Stream { if($this->stream === null) { if($this->errorCode !== UPLOAD_ERR_OK) @@ -79,6 +130,15 @@ class HttpUploadedFile implements ICloseable { return $this->stream; } + /** + * Moves the uploaded file to its final destination. + * + * @param string $path Path to move the file to. + * @throws RuntimeException If the file has already been moved. + * @throws RuntimeException If an upload error occurred. + * @throws InvalidArgumentException If the provided $path is not valid. + * @throws RuntimeException If the file failed to move. + */ public function moveTo(string $path): void { if($this->hasMoved) throw new RuntimeException('This uploaded file has already been moved.'); @@ -110,6 +170,11 @@ class HttpUploadedFile implements ICloseable { $this->close(); } + /** + * Creates a HttpUploadedFile instance from an entry in the $_FILES superglobal. + * + * @return HttpUploadedFile Uploaded file info. + */ public static function createFromFILE(array $file): self { return new HttpUploadedFile( $file['error'] ?? UPLOAD_ERR_NO_FILE, @@ -120,6 +185,11 @@ class HttpUploadedFile implements ICloseable { ); } + /** + * Creates a collection of HttpUploadedFile instances from the $_FILES superglobal. + * + * @return array Uploaded files. + */ public static function createFromFILES(array $files): array { if(empty($files)) return []; diff --git a/src/Http/Routing/HttpDelete.php b/src/Http/Routing/HttpDelete.php index 475542c..62374b7 100644 --- a/src/Http/Routing/HttpDelete.php +++ b/src/Http/Routing/HttpDelete.php @@ -1,7 +1,7 @@ registerDefaultContentHandlers(); } + /** + * Retrieves the normalised name of the preferred character set. + * + * @return string Normalised character set name. + */ public function getCharSet(): string { if($this->defaultCharSet === '') return strtolower(mb_preferred_mime_name(mb_internal_encoding())); return $this->defaultCharSet; } + /** + * Retrieves the error handler instance. + * + * @return IErrorHandler The error handler. + */ public function getErrorHandler(): IErrorHandler { return $this->errorHandler; } + /** + * Changes the active error handler. + * + * @param IErrorHandler|string $handler Error handling to use for error responses with an empty body. 'html' for the default HTML implementation, 'plain' for the plaintext implementation. + */ public function setErrorHandler(IErrorHandler|string $handler): void { if($handler instanceof IErrorHandler) $this->errorHandler = $handler; @@ -57,25 +77,45 @@ class HttpRouter implements IRouter { $this->setPlainErrorHandler(); } + /** + * Set the error handler to the basic HTML one. + */ public function setHTMLErrorHandler(): void { $this->errorHandler = new HtmlErrorHandler; } + /** + * Set the error handler to the plain text one. + */ public function setPlainErrorHandler(): void { $this->errorHandler = new PlainErrorHandler; } + /** + * Register a message body content handler. + * + * @param IContentHandler $contentHandler Content handler to register. + */ public function registerContentHandler(IContentHandler $contentHandler): void { if(!in_array($contentHandler, $this->contentHandlers)) $this->contentHandlers[] = $contentHandler; } + /** + * Register the default content handlers. + */ public function registerDefaultContentHandlers(): void { $this->registerContentHandler(new StreamContentHandler); $this->registerContentHandler(new JsonContentHandler); $this->registerContentHandler(new BencodeContentHandler); } + /** + * Retrieve a scoped router to a given path prefix. + * + * @param string $prefix Prefix to apply to paths within the returned router. + * @return IRouter Scopes router proxy. + */ public function scopeTo(string $prefix): IRouter { return new ScopedRouter($this, $prefix); } @@ -92,6 +132,12 @@ class HttpRouter implements IRouter { return sprintf('#^%s%s#su', $path, $prefixMatch ? '' : '$'); } + /** + * Registers a middleware handler. + * + * @param string $path Path prefix or regex to apply this middleware on. + * @param callable $handler Middleware handler. + */ public function use(string $path, callable $handler): void { $this->middlewares[] = $mwInfo = new stdClass; $mwInfo->handler = $handler; @@ -105,6 +151,15 @@ class HttpRouter implements IRouter { $mwInfo->prefix = $path; } + /** + * Registers a route handler for a given method and path. + * + * @param string $method Method to use this handler for. + * @param string $path Path or regex to use this handler with. + * @param callable $handler Handler to use for this method/path combination. + * @throws InvalidArgumentException If $method is empty. + * @throws InvalidArgumentException If $method starts or ends with spaces. + */ public function add(string $method, string $path, callable $handler): void { if($method === '') throw new InvalidArgumentException('$method may not be empty'); @@ -130,6 +185,13 @@ class HttpRouter implements IRouter { } } + /** + * Resolves middlewares and a route handler for a given method and path. + * + * @param string $method Method to resolve for. + * @param string $path Path to resolve for. + * @return ResolvedRouteInfo Resolved route information. + */ public function resolve(string $method, string $path): ResolvedRouteInfo { if(str_ends_with($path, '/')) $path = substr($path, 0, -1); @@ -178,6 +240,12 @@ class HttpRouter implements IRouter { return new ResolvedRouteInfo($middlewares, array_keys($methods), $handler, $args); } + /** + * Dispatches a route based on a given HTTP request message with additional prefix arguments and output to stdout. + * + * @param ?HttpRequest $request HTTP request message to handle, null to use the current request. + * @param array $args Additional arguments to prepend to the argument list sent to the middleware and route handlers. + */ public function dispatch(?HttpRequest $request = null, array $args = []): void { $request ??= HttpRequest::fromRequest(); $response = new HttpResponseBuilder; @@ -232,11 +300,24 @@ class HttpRouter implements IRouter { self::output($response->toResponse(), $request->getMethod() !== 'HEAD'); } + /** + * Writes an error page to a given HTTP response builder. + * + * @param HttpResponseBuilder $response HTTP response builder to apply the error page to. + * @param HttpRequest $request HTTP request that triggered this error. + * @param int $statusCode HTTP status code for this error page. + */ public function writeErrorPage(HttpResponseBuilder $response, HttpRequest $request, int $statusCode): void { $response->setStatusCode($statusCode); $this->errorHandler->handle($response, $request, $response->getStatusCode(), $response->getStatusText()); } + /** + * Outputs a HTTP response message to stdout. + * + * @param HttpResponse $response HTTP response message to output. + * @param bool $includeBody true to include the response message body, false to omit it for HEAD requests. + */ public static function output(HttpResponse $response, bool $includeBody): void { header(sprintf( 'HTTP/%s %03d %s', diff --git a/src/Http/Routing/ResolvedRouteInfo.php b/src/Http/Routing/ResolvedRouteInfo.php index 3fc9cd2..d74509d 100644 --- a/src/Http/Routing/ResolvedRouteInfo.php +++ b/src/Http/Routing/ResolvedRouteInfo.php @@ -1,11 +1,20 @@ middlewares as $middleware) { $result = $middleware[0](...array_merge($args, $middleware[1])); @@ -23,18 +38,39 @@ class ResolvedRouteInfo { return null; } + /** + * Whether this route has a handler. + * + * @return bool true if it does. + */ public function hasHandler(): bool { return $this->handler !== null; } + /** + * Whether this route supports other HTTP request methods. + * + * @return bool true if it does. + */ public function hasOtherMethods(): bool { return !empty($this->supportedMethods); } + /** + * Gets the list of supported HTTP request methods for this route. + * + * @return string[] Supported HTTP request methods. + */ public function getSupportedMethods(): array { return $this->supportedMethods; } + /** + * Dispatches this route. + * + * @param array $args Additional arguments to pass to the route handler. + * @return mixed Return value of the route handler. + */ public function dispatch(array $args): mixed { return ($this->handler)(...array_merge($args, $this->args)); } diff --git a/src/IO/FileStream.php b/src/IO/FileStream.php index 4f2842c..2d58729 100644 --- a/src/IO/FileStream.php +++ b/src/IO/FileStream.php @@ -1,31 +1,121 @@ lock = $lock; @@ -52,33 +142,112 @@ class FileStream extends GenericStream { parent::close(); } + /** + * Open file for reading, throw if not exists. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function openRead(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::OPEN_READ, $lock); } + + /** + * Open file for reading and writing, throw if not exist. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function openReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::OPEN_READ_WRITE, $lock); } + + /** + * Create file for writing, truncate if exist. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function newWrite(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::NEW_WRITE, $lock); } + + /** + * Create file for reading and writing, truncate if exist. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function newReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::NEW_READ_WRITE, $lock); } + + /** + * Open file for appending, create if not exist. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function appendWrite(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::APPEND_WRITE, $lock); } + + /** + * Open file for reading and appending, create if not exist. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function appendReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::APPEND_READ_WRITE, $lock); } + + /** + * Create file for writing, throw if exist. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function createWrite(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::CREATE_WRITE, $lock); } + + /** + * Create file for reading and writing, throw if exist. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function createReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::CREATE_READ_WRITE, $lock); } + + /** + * Opens or creates a file for writing. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function openOrCreateWrite(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::OPEN_OR_CREATE_WRITE, $lock); } + + /** + * Opens or creates a file for reading and writing. + * + * @param string $path Path to the file. + * @param int $lock Locking method to use. + * @return FileStream Stream to file. + */ public static function openOrCreateReadWrite(string $path, int $lock = self::LOCK_NONE): FileStream { return new FileStream($path, self::OPEN_OR_CREATE_READ_WRITE, $lock); } diff --git a/src/IO/GenericStream.php b/src/IO/GenericStream.php index 4023251..c37bcd7 100644 --- a/src/IO/GenericStream.php +++ b/src/IO/GenericStream.php @@ -1,17 +1,31 @@ canSeek = $metaData['seekable']; } + /** + * Returns the underlying resource handle. + * + * @return resource Underlying resource handle. + */ public function getResource() { return $this->stream; } @@ -98,8 +120,8 @@ class GenericStream extends Stream { return $buffer; } - public function seek(int $offset, int $origin = Stream::START): int { - return fseek($this->stream, $offset, $origin); + public function seek(int $offset, int $origin = Stream::START): bool { + return fseek($this->stream, $offset, $origin) === 0; } public function write(string $buffer, int $length = -1): void { @@ -123,6 +145,7 @@ class GenericStream extends Stream { } catch(\Error $ex) {} } + #[\Override] public function copyTo(Stream $other): void { if($other instanceof GenericStream) { stream_copy_to_stream($this->stream, $other->stream); diff --git a/src/IO/IOException.php b/src/IO/IOException.php index 212a07f..812e9fb 100644 --- a/src/IO/IOException.php +++ b/src/IO/IOException.php @@ -1,10 +1,13 @@ write($string); diff --git a/src/IO/NetworkStream.php b/src/IO/NetworkStream.php index c6b36cd..16ebbb2 100644 --- a/src/IO/NetworkStream.php +++ b/src/IO/NetworkStream.php @@ -1,7 +1,7 @@ stream, $blocking); } + /** + * Opens a network socket stream to an endpoint represented by an Index EndPoint instance. + * + * @param EndPoint $endPoint Host to connect to. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openEndPoint(EndPoint $endPoint, float|null $timeout = null): NetworkStream { return new NetworkStream((string)$endPoint, -1, $timeout); } + + /** + * Opens an SSL network socket stream to an endpoint represented by an Index EndPoint instance. + * + * @param EndPoint $endPoint Host to connect to. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openEndPointSSL(EndPoint $endPoint, float|null $timeout = null): NetworkStream { return new NetworkStream('ssl://' . ((string)$endPoint), -1, $timeout); } + + /** + * Opens a TLS network socket stream to an endpoint represented by an Index EndPoint instance. + * + * @param EndPoint $endPoint Host to connect to. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openEndPointTLS(EndPoint $endPoint, float|null $timeout = null): NetworkStream { return new NetworkStream('tls://' . ((string)$endPoint), -1, $timeout); } + /** + * Opens a network socket stream to an endpoint represented by an hostname and port. + * + * @param string $hostname Hostname to connect to. + * @param int $port Port to connect at. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openHost(string $hostname, int $port, float|null $timeout = null): NetworkStream { return new NetworkStream($hostname, $port, $timeout); } + + /** + * Opens an SSL network socket stream to an endpoint represented by an hostname and port. + * + * @param string $hostname Hostname to connect to. + * @param int $port Port to connect at. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openHostSSL(string $hostname, int $port, float|null $timeout = null): NetworkStream { return new NetworkStream('ssl://' . ((string)$hostname), $port, $timeout); } + + /** + * Opens a TLS network socket stream to an endpoint represented by an hostname and port. + * + * @param string $hostname Hostname to connect to. + * @param int $port Port to connect at. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openHostTLS(string $hostname, int $port, float|null $timeout = null): NetworkStream { return new NetworkStream('tls://' . ((string)$hostname), $port, $timeout); } + /** + * Opens a network socket stream to an endpoint represented by an Index IPAddress instance and port. + * + * @param IPAddress $address Address to connect to. + * @param int $port Port to connect at. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openAddress(IPAddress $address, int $port, float|null $timeout = null): NetworkStream { return new NetworkStream($address->getAddress(), $port, $timeout); } + + /** + * Opens an SSL network socket stream to an endpoint represented by an Index IPAddress instance and port. + * + * @param IPAddress $address Address to connect to. + * @param int $port Port to connect at. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openAddressSSL(IPAddress $address, int $port, float|null $timeout = null): NetworkStream { return new NetworkStream('ssl://' . $address->getAddress(), $port, $timeout); } + + /** + * Opens a TLS network socket stream to an endpoint represented by an Index IPAddress instance and port. + * + * @param IPAddress $address Address to connect to. + * @param int $port Port to connect at. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openAddressTLS(IPAddress $address, int $port, float|null $timeout = null): NetworkStream { return new NetworkStream('tls://' . $address->getAddress(), $port, $timeout); } + /** + * Opens a network socket stream to an endpoint represented by a UNIX socket path. + * + * @param string $path Path to connect to. + * @param float|null $timeout Amount of seconds until timeout, null for php.ini default. + */ public static function openUnix(string $path, float|null $timeout = null): NetworkStream { - return new NetworkStream('unix://' . ((string)$path), -1, $timeout); + return new NetworkStream('unix://' . $path, -1, $timeout); } } diff --git a/src/IO/ProcessStream.php b/src/IO/ProcessStream.php index bdc4619..c3595ff 100644 --- a/src/IO/ProcessStream.php +++ b/src/IO/ProcessStream.php @@ -1,15 +1,23 @@ handle = popen($command, $mode); $this->canRead = strpos($mode, 'r') !== false; @@ -71,7 +79,7 @@ class ProcessStream extends Stream { return $buffer; } - public function seek(int $offset, int $origin = self::START): int { + public function seek(int $offset, int $origin = self::START): bool { throw new IOException('Cannot seek ProcessStream.'); } diff --git a/src/IO/Stream.php b/src/IO/Stream.php index 1a9f592..942dc85 100644 --- a/src/IO/Stream.php +++ b/src/IO/Stream.php @@ -1,37 +1,113 @@ readChar(); if($char === null) @@ -39,25 +115,72 @@ abstract class Stream implements Stringable, ICloseable { return ord($char); } + /** + * Read a line of text from the stream. + * + * @return ?string One line of text or null if we're at the end of the stream. + */ abstract public function readLine(): ?string; + /** + * Read an amount of data from the stream. + * + * @param int $length Maximum amount of data to read. + * @return ?string At most a $length sized string or null if we're at the end of the stream. + */ abstract public function read(int $length): ?string; - abstract public function seek(int $offset, int $origin = self::START): int; + /** + * Move the cursor to a different place in the stream. + * + * @param int $offset Offset to apply to the cursor position. + * @param int $origin Point from which to apply the offset. + * @throws IOException If the stream is not seekable. + * @return bool true if the cursor offset was applied successfully. + */ + abstract public function seek(int $offset, int $origin = self::START): bool; + /** + * Write an amount of data to the stream. + * + * @param string $buffer Buffer to write from. + * @param int $length Amount of data to write from the buffer, or -1 to write the entire buffer. + */ abstract public function write(string $buffer, int $length = -1): void; + /** + * Writes a line of text to the stream. + * + * @param string $line Line of text to write. + */ public function writeLine(string $line): void { $this->write($line); $this->write(PHP_EOL); } + /** + * Writes a single character to the stream. + * + * @param string $char Character to write to the stream. + */ abstract public function writeChar(string $char): void; + /** + * Writes a single byte to the stream. + * + * @param int $byte Byte to write to the stream. + */ public function writeByte(int $byte): void { $this->writeChar(chr($byte & 0xFF)); } + /** + * Copy the entire contents of this stream to another. + * + * Will seek to the beginning of the stream if the stream is seekable. + * + * @param Stream $other Target stream. + */ public function copyTo(Stream $other): void { if($this->canSeek()) $this->seek(0); @@ -66,6 +189,9 @@ abstract class Stream implements Stringable, ICloseable { $other->write($this->read(8192)); } + /** + * Force write of all buffered output to the stream. + */ abstract public function flush(): void; abstract public function close(): void; diff --git a/src/IO/TempFileStream.php b/src/IO/TempFileStream.php index 0431191..5ab89da 100644 --- a/src/IO/TempFileStream.php +++ b/src/IO/TempFileStream.php @@ -1,13 +1,25 @@ 0xFFFF) throw new InvalidArgumentException('$port is not a valid port number.'); @@ -19,14 +26,29 @@ class DnsEndPoint extends EndPoint implements Stringable { $this->port = $port; } + /** + * Retrieves host name. + * + * @return string Host name. + */ public function getHost(): string { return $this->host; } + /** + * Whether a port is specified. + * + * @return bool true if the port number is greater than 0. + */ public function hasPort(): bool { return $this->port > 0; } + /** + * Retrieves port number. + * + * @return int Network port number. + */ public function getPort(): int { return $this->port; } @@ -34,6 +56,7 @@ class DnsEndPoint extends EndPoint implements Stringable { public function __serialize(): array { return [$this->host, $this->port]; } + public function __unserialize(array $serialized): void { $this->host = $serialized[0]; $this->port = $serialized[1]; @@ -45,11 +68,18 @@ class DnsEndPoint extends EndPoint implements Stringable { && $this->host === $other->host; } - public static function parse(string $string): EndPoint { + /** + * Attempts to parse a string into a DNS end point. + * + * @param string $string String to parse. + * @throws InvalidArgumentException If $string does not contain a valid DNS end point string. + * @return DnsEndPoint Representation of the given string. + */ + public static function parse(string $string): DnsEndPoint { $parts = explode(':', $string); if(count($parts) < 2) throw new InvalidArgumentException('$string is not a valid host and port combo.'); - return new DnsEndPoint($parts[0], (int)$parts[1]); + return new DnsEndPoint($parts[0], (int)($parts[1] ?? '0')); } public function __toString(): string { diff --git a/src/Net/EndPoint.php b/src/Net/EndPoint.php index ff751b6..7f980a2 100644 --- a/src/Net/EndPoint.php +++ b/src/Net/EndPoint.php @@ -1,7 +1,7 @@ raw = $raw; $this->width = strlen($raw); } + /** + * Get raw address bytes. + * + * @return string Raw address bytes. + */ public function getRaw(): string { return $this->raw; } + + /** + * Gets the width of the raw address byte data. + * + * 4 bytes for IPv4 + * 16 bytes for IPv6 + * + * @return int Raw address byte width. + */ public function getWidth(): int { return $this->width; } + /** + * Gets safe string representation of IP address. + * + * IPv6 addresses will be wrapped in square brackets. + * Use getCleanAddress if this is undesirable. + * + * @return string Safe string IP address. + */ public function getAddress(): string { return sprintf($this->isV6() ? '[%s]' : '%s', inet_ntop($this->raw)); } + + /** + * Gets string representation of IP address. + * + * @return string String IP address. + */ public function getCleanAddress(): string { return inet_ntop($this->raw); } + /** + * Gets IP version this address is for. + * + * @return int IP version. + */ public function getVersion(): int { if($this->isV4()) return self::V4; @@ -45,9 +101,20 @@ final class IPAddress implements JsonSerializable, Stringable, IEquatable { return self::UNKNOWN; } + /** + * Checks whether this is an IPv4 address. + * + * @return bool true if IPv4. + */ public function isV4(): bool { return $this->width === 4; } + + /** + * Checks whether this is an IPv6 address. + * + * @return bool true if IPv6. + */ public function isV6(): bool { return $this->width === 16; } @@ -60,6 +127,7 @@ final class IPAddress implements JsonSerializable, Stringable, IEquatable { return $other instanceof IPAddress && $this->getRaw() === $other->getRaw(); } + #[\Override] public function jsonSerialize(): mixed { return inet_ntop($this->raw); } @@ -67,15 +135,23 @@ final class IPAddress implements JsonSerializable, Stringable, IEquatable { public function __serialize(): array { return [$this->raw]; } + public function __unserialize(array $serialized): void { $this->raw = $serialized[0]; $this->width = strlen($this->raw); } - public static function parse(string $address): self { - $parsed = inet_pton($address); + /** + * Attempts to parse a string into an IP address. + * + * @param string $string String to parse. + * @throws InvalidArgumentException If $string does not contain a valid IP address string. + * @return IPAddress Representation of the given string. + */ + public static function parse(string $string): self { + $parsed = inet_pton($string); if($parsed === false) - throw new InvalidArgumentException('$address is not a valid IP address.'); + throw new InvalidArgumentException('$string is not a valid IP address.'); return new static($parsed); } } diff --git a/src/Net/IPAddressRange.php b/src/Net/IPAddressRange.php index 5ef4658..83b948b 100644 --- a/src/Net/IPAddressRange.php +++ b/src/Net/IPAddressRange.php @@ -1,7 +1,7 @@ base = $base; $this->mask = $mask; } + /** + * Retrieves the base IP address. + * + * @return IPAddress Base IP address. + */ public function getBaseAddress(): IPAddress { return $this->base; } + /** + * Retrieves the CIDR mask. + * + * @return int CIDR mask. + */ public function getMask(): int { return $this->mask; } + /** + * Retrieves full CIDR representation of this range. + * + * @return string CIDR string. + */ public function getCIDR(): string { return ((string)$this->base) . '/' . $this->getMask(); } @@ -41,6 +63,12 @@ final class IPAddressRange implements JsonSerializable, Stringable, IEquatable { && $this->base->equals($other->base); } + /** + * Checks if a given IP address matches with this range. + * + * @param IPAddress $address IP address to check. + * @return bool true if the address matches. + */ public function match(IPAddress $address): bool { $width = $this->base->getWidth(); if($address->getWidth() !== $width) @@ -83,15 +111,23 @@ final class IPAddressRange implements JsonSerializable, Stringable, IEquatable { 'm' => $this->mask, ]; } + public function __unserialize(array $serialized): void { $this->base = new IPAddress($serialized['b']); $this->mask = $serialized['m']; } - public static function parse(string $cidr): IPAddressRange { - $parts = explode('/', $cidr, 2); + /** + * Attempts to parse a string into an IP address range. + * + * @param string $string String to parse. + * @throws InvalidArgumentException If $string does not contain a valid CIDR range string. + * @return IPAddressRange Representation of the given string. + */ + public static function parse(string $string): IPAddressRange { + $parts = explode('/', $string, 2); if(empty($parts[0]) || empty($parts[1])) - throw new InvalidArgumentException('$cidr is not a valid CIDR range.'); + throw new InvalidArgumentException('$string is not a valid CIDR range.'); return new static(IPAddress::parse($parts[0]), (int)$parts[1]); } } diff --git a/src/Net/IPEndPoint.php b/src/Net/IPEndPoint.php index 1ac936b..504895c 100644 --- a/src/Net/IPEndPoint.php +++ b/src/Net/IPEndPoint.php @@ -1,17 +1,25 @@ 0xFFFF) throw new InvalidArgumentException('$port is not a valid port number.'); @@ -19,14 +27,29 @@ class IPEndPoint extends EndPoint { $this->port = $port; } + /** + * Returns the IP address. + * + * @return IPAddress IP address. + */ public function getAddress(): IPAddress { return $this->address; } + /** + * Whether a port is specified. + * + * @return bool true if the port number is greater than 0. + */ public function hasPort(): bool { return $this->port > 0; } + /** + * Retrieves port number. + * + * @return int Network port number. + */ public function getPort(): int { return $this->port; } @@ -34,6 +57,7 @@ class IPEndPoint extends EndPoint { public function __serialize(): array { return [$this->address, $this->port]; } + public function __unserialize(array $serialized): void { $this->address = $serialized[0]; $this->port = $serialized[1]; @@ -45,7 +69,14 @@ class IPEndPoint extends EndPoint { && $this->address->equals($other->address); } - public static function parse(string $string): EndPoint { + /** + * Attempts to parse a string into an IP end point. + * + * @param string $string String to parse. + * @throws InvalidArgumentException If $string does not contain a valid IP end point string. + * @return IPEndPoint Representation of the given string. + */ + public static function parse(string $string): IPEndPoint { if(str_starts_with($string, '[')) { // IPv6 $closing = strpos($string, ']'); if($closing === false) diff --git a/src/Net/UnixEndPoint.php b/src/Net/UnixEndPoint.php index 8da0e13..b9ab7b6 100644 --- a/src/Net/UnixEndPoint.php +++ b/src/Net/UnixEndPoint.php @@ -1,17 +1,28 @@ path = $socketPath; } + /** + * Gets path to the socket file. + * + * @return string Path to the socket file. + */ public function getPath(): string { return $this->path; } @@ -19,6 +30,7 @@ class UnixEndPoint extends EndPoint { public function __serialize(): array { return [$this->path]; } + public function __unserialize(array $serialized): void { $this->path = $serialized[0]; } @@ -28,13 +40,19 @@ class UnixEndPoint extends EndPoint { && $this->path === $other->path; } - public static function parse(string $socketPath): UnixEndPoint { - if(str_starts_with($socketPath, 'unix:')) { - $uri = parse_url($socketPath); - $socketPath = $uri['path'] ?? ''; + /** + * Attempts to parse a string into an UNIX end point. + * + * @param string $string String to parse. + * @return UnixEndPoint Representation of the given string. + */ + public static function parse(string $string): UnixEndPoint { + if(str_starts_with($string, 'unix:')) { + $uri = parse_url($string); + $string = $uri['path'] ?? ''; } - return new UnixEndPoint($socketPath); + return new UnixEndPoint($string); } public function __toString(): string { diff --git a/src/Performance/PerformanceCounter.php b/src/Performance/PerformanceCounter.php index 4a40a29..631b149 100644 --- a/src/Performance/PerformanceCounter.php +++ b/src/Performance/PerformanceCounter.php @@ -1,19 +1,38 @@ frequency = PerformanceCounter::getFrequency(); } + /** + * Start time measurement. + */ public function start(): void { if($this->start < 0) $this->start = PerformanceCounter::getTicks(); } + /** + * Stop time measurement. + */ public function stop(): void { if($this->start < 0) return; diff --git a/src/Performance/TimingPoint.php b/src/Performance/TimingPoint.php index f98ab36..7aa25ba 100644 --- a/src/Performance/TimingPoint.php +++ b/src/Performance/TimingPoint.php @@ -1,16 +1,25 @@ comment = $comment; } + /** + * Gets the starting ticks count. + * + * @return int|float Starting ticks count. + */ public function getTimePointTicks(): int|float { return $this->timePoint; } + /** + * Gets the duration ticks count. + * + * @return int|float Duration ticks count. + */ public function getDurationTicks(): int|float { return $this->duration; } + /** + * Gets the duration time amount. + * + * @return float Duration time amount. + */ public function getDurationTime(): float { return $this->duration / PerformanceCounter::getFrequency(); } + /** + * Gets time point name. + * + * @return string Time point name. + */ public function getName(): string { return $this->name; } + /** + * Checks whether time point has a comment. + * + * @return bool true if the time point has a comment. + */ public function hasComment(): bool { return $this->comment !== ''; } + /** + * Gets time point comment. + * + * @return string Time point comment. + */ public function getComment(): string { return $this->comment; } diff --git a/src/Performance/Timings.php b/src/Performance/Timings.php index 4709d43..f8798f3 100644 --- a/src/Performance/Timings.php +++ b/src/Performance/Timings.php @@ -1,45 +1,79 @@ sw = $sw ?? Stopwatch::startNew(); $this->lastLapTicks = $this->sw->getElapsedTicks(); } + /** + * Gets the underlying stopwatch object. + * + * @return Stopwatch Stopwatch object. + */ public function getStopwatch(): Stopwatch { return $this->sw; } + /** + * Returns timing points. + * + * @return TimingPoint[] Timing points. + */ public function getLaps(): array { return $this->laps; } + /** + * Starts the underlying stopwatch. + */ public function start(): void { $this->sw->start(); } + /** + * Stops the underlying stopwatch. + */ public function stop(): void { $this->sw->stop(); } + /** + * Checks whether the underlying stopwatch is running. + * + * @return bool true if it is running. + */ public function isRunning(): bool { return $this->sw->isRunning(); } + /** + * Record a timing point. + * + * @param string $name Timing point name, must be alphanumeric. + * @param string $comment Timing point comment. + * @throws InvalidArgumentException If $name is not alphanumeric. + */ public function lap(string $name, string $comment = ''): void { if(!ctype_alnum($name)) - throw new InvalidArgumentException('$name must be alpha-numeric.'); + throw new InvalidArgumentException('$name must be alphanumeric.'); $lapTicks = $this->sw->getElapsedTicks(); $elapsed = $lapTicks - $this->lastLapTicks; diff --git a/src/XDateTime.php b/src/XDateTime.php index 1e72794..0cc3252 100644 --- a/src/XDateTime.php +++ b/src/XDateTime.php @@ -1,7 +1,7 @@ assertArrayHasKey('private', $decoded['info']); $this->assertEquals(1, $decoded['info']['private']); - $decoded = Bencode::decode(base64_decode(self::TORRENT), dictAsObject: true); + $decoded = Bencode::decode(base64_decode(self::TORRENT), associative: false); $this->assertEquals('https://tracker.flashii.net/announce.php/meow', $decoded->announce); $this->assertEquals(1689973664, $decoded->{'creation date'});