Removed .NET-style stream types.
This commit is contained in:
parent
2a84a614be
commit
a5aa0152a0
27 changed files with 98 additions and 1246 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.2410.20209
|
0.2410.21617
|
||||||
|
|
|
@ -7,7 +7,6 @@ namespace Index\Bencode;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\Io\{GenericStream,Stream,TempFileStream};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a Bencode serialiser.
|
* Provides a Bencode serialiser.
|
||||||
|
@ -80,7 +79,7 @@ final class Bencode {
|
||||||
/**
|
/**
|
||||||
* Decodes a bencoded string.
|
* Decodes a bencoded string.
|
||||||
*
|
*
|
||||||
* @param mixed $bencoded The bencoded string being decoded. Can be an Index Stream, readable resource or a string.
|
* @param mixed $bencoded The bencoded string being decoded. Can be a readable resource or a string.
|
||||||
* @param int $depth Maximum nesting depth of the structure being decoded. The value must be greater than 0.
|
* @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.
|
* @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 $bencoded string is not set to a valid type.
|
||||||
|
@ -90,18 +89,22 @@ final class Bencode {
|
||||||
*/
|
*/
|
||||||
public static function decode(mixed $bencoded, int $depth = self::DEFAULT_DEPTH, bool $associative = true): mixed {
|
public static function decode(mixed $bencoded, int $depth = self::DEFAULT_DEPTH, bool $associative = true): mixed {
|
||||||
if(is_string($bencoded)) {
|
if(is_string($bencoded)) {
|
||||||
$bencoded = TempFileStream::fromString($bencoded);
|
$handle = fopen('php://memory', 'r+b');
|
||||||
$bencoded->seek(0);
|
if($handle === false)
|
||||||
} elseif(is_resource($bencoded))
|
throw new RuntimeException('failed to fopen a memory stream');
|
||||||
$bencoded = new GenericStream($bencoded);
|
|
||||||
elseif(!($bencoded instanceof Stream))
|
fwrite($handle, $bencoded);
|
||||||
throw new InvalidArgumentException('$bencoded must be a string, an Index Stream or a file resource.');
|
fseek($handle, 0);
|
||||||
|
$bencoded = $handle;
|
||||||
|
$handle = null;
|
||||||
|
} elseif(!is_resource($bencoded))
|
||||||
|
throw new InvalidArgumentException('$bencoded must be a string or a stream resource.');
|
||||||
|
|
||||||
if($depth < 1)
|
if($depth < 1)
|
||||||
throw new InvalidArgumentException('Maximum depth reached, structure is too dense.');
|
throw new InvalidArgumentException('Maximum depth reached, structure is too dense.');
|
||||||
|
|
||||||
$char = $bencoded->readChar();
|
$char = fgetc($bencoded);
|
||||||
if($char === null)
|
if($char === false)
|
||||||
throw new RuntimeException('Unexpected end of stream in $bencoded.');
|
throw new RuntimeException('Unexpected end of stream in $bencoded.');
|
||||||
|
|
||||||
switch($char) {
|
switch($char) {
|
||||||
|
@ -109,8 +112,8 @@ final class Bencode {
|
||||||
$number = '';
|
$number = '';
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
$char = $bencoded->readChar();
|
$char = fgetc($bencoded);
|
||||||
if($char === null)
|
if($char === false)
|
||||||
throw new RuntimeException('Unexpected end of stream while parsing integer.');
|
throw new RuntimeException('Unexpected end of stream while parsing integer.');
|
||||||
if($char === 'e')
|
if($char === 'e')
|
||||||
break;
|
break;
|
||||||
|
@ -131,12 +134,12 @@ final class Bencode {
|
||||||
$list = [];
|
$list = [];
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
$char = $bencoded->readChar();
|
$char = fgetc($bencoded);
|
||||||
if($char === null)
|
if($char === false)
|
||||||
throw new RuntimeException('Unexpected end of stream while parsing list.');
|
throw new RuntimeException('Unexpected end of stream while parsing list.');
|
||||||
if($char === 'e')
|
if($char === 'e')
|
||||||
break;
|
break;
|
||||||
$bencoded->seek(-1, Stream::CURRENT);
|
fseek($bencoded, -1, SEEK_CUR);
|
||||||
$list[] = self::decode($bencoded, $depth - 1, $associative);
|
$list[] = self::decode($bencoded, $depth - 1, $associative);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,14 +149,14 @@ final class Bencode {
|
||||||
$dict = [];
|
$dict = [];
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
$char = $bencoded->readChar();
|
$char = fgetc($bencoded);
|
||||||
if($char === null)
|
if($char === false)
|
||||||
throw new RuntimeException('Unexpected end of stream while parsing dictionary');
|
throw new RuntimeException('Unexpected end of stream while parsing dictionary');
|
||||||
if($char === 'e')
|
if($char === 'e')
|
||||||
break;
|
break;
|
||||||
if(!ctype_digit($char))
|
if(!ctype_digit($char))
|
||||||
throw new RuntimeException('Unexpected dictionary key type, expected a string.');
|
throw new RuntimeException('Unexpected dictionary key type, expected a string.');
|
||||||
$bencoded->seek(-1, Stream::CURRENT);
|
fseek($bencoded, -1, SEEK_CUR);
|
||||||
$dict[self::decode($bencoded, $depth - 1, $associative)] = self::decode($bencoded, $depth - 1, $associative);
|
$dict[self::decode($bencoded, $depth - 1, $associative)] = self::decode($bencoded, $depth - 1, $associative);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,8 +172,8 @@ final class Bencode {
|
||||||
$length = $char;
|
$length = $char;
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
$char = $bencoded->readChar();
|
$char = fgetc($bencoded);
|
||||||
if($char === null)
|
if($char === false)
|
||||||
throw new RuntimeException('Unexpected end of character while parsing string length.');
|
throw new RuntimeException('Unexpected end of character while parsing string length.');
|
||||||
if($char === ':')
|
if($char === ':')
|
||||||
break;
|
break;
|
||||||
|
@ -182,7 +185,13 @@ final class Bencode {
|
||||||
$length .= $char;
|
$length .= $char;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $bencoded->read((int)$length);
|
$length = (int)$length;
|
||||||
|
if($length < 0)
|
||||||
|
throw new RuntimeException('Length is Bencoded stream is less than 0.');
|
||||||
|
if($length < 1)
|
||||||
|
return '';
|
||||||
|
|
||||||
|
return fread($bencoded, $length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
namespace Index\Data;
|
namespace Index\Data;
|
||||||
|
|
||||||
use Index\Closeable;
|
use Index\Closeable;
|
||||||
use Index\Io\Stream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a database result set.
|
* Represents a database result set.
|
||||||
|
@ -99,14 +98,6 @@ interface DbResult extends Closeable {
|
||||||
*/
|
*/
|
||||||
function getBooleanOrNull(int|string $index): ?bool;
|
function getBooleanOrNull(int|string $index): ?bool;
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the value from the target index as a Stream.
|
|
||||||
*
|
|
||||||
* @param int|string $index Target index.
|
|
||||||
* @return ?Stream A Stream if data is available, null if not.
|
|
||||||
*/
|
|
||||||
function getStream(int|string $index): ?Stream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an iterator for this result.
|
* Creates an iterator for this result.
|
||||||
*
|
*
|
||||||
|
|
|
@ -154,11 +154,9 @@ final class DbTools {
|
||||||
return DbType::FLOAT;
|
return DbType::FLOAT;
|
||||||
if(is_int($value))
|
if(is_int($value))
|
||||||
return DbType::INTEGER;
|
return DbType::INTEGER;
|
||||||
// ┌ should probably also check for Stringable, length should also be taken into consideration
|
if(is_resource($value))
|
||||||
// ↓ though maybe with that it's better to assume that when an object is passed it'll always be Massive
|
|
||||||
if(is_string($value))
|
|
||||||
return DbType::STRING;
|
|
||||||
return DbType::BLOB;
|
return DbType::BLOB;
|
||||||
|
return DbType::STRING;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,7 +9,6 @@ use mysqli_result;
|
||||||
use mysqli_stmt;
|
use mysqli_stmt;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Index\Data\{DbResult,DbResultTrait};
|
use Index\Data\{DbResult,DbResultTrait};
|
||||||
use Index\Io\{Stream,TempFileStream};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a MariaDB/MySQL database result.
|
* Represents a MariaDB/MySQL database result.
|
||||||
|
@ -68,17 +67,6 @@ abstract class MariaDbResult implements DbResult {
|
||||||
return $this->currentRow[$index] ?? null;
|
return $this->currentRow[$index] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStream(int|string $index): ?Stream {
|
|
||||||
if($this->isNull($index))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
$value = $this->getValue($index);
|
|
||||||
if(!is_scalar($value))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new TempFileStream((string)$value);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract function close(): void;
|
abstract function close(): void;
|
||||||
|
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
|
|
|
@ -8,8 +8,8 @@ namespace Index\Data\MariaDb;
|
||||||
use mysqli_stmt;
|
use mysqli_stmt;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use Stringable;
|
||||||
use Index\Data\{DbType,DbStatement};
|
use Index\Data\{DbType,DbStatement};
|
||||||
use Index\Io\Stream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a MariaDB/MySQL prepared statement.
|
* Represents a MariaDB/MySQL prepared statement.
|
||||||
|
@ -132,13 +132,10 @@ class MariaDbStatement implements DbStatement {
|
||||||
if($type === DbType::NULL)
|
if($type === DbType::NULL)
|
||||||
$value = null;
|
$value = null;
|
||||||
elseif($type === DbType::BLOB) {
|
elseif($type === DbType::BLOB) {
|
||||||
if($value instanceof Stream) {
|
if(is_resource($value)) {
|
||||||
while(($data = $value->read(8192)) !== null)
|
|
||||||
$this->statement->send_long_data($key, $data);
|
|
||||||
} elseif(is_resource($value)) {
|
|
||||||
while($data = fread($value, 8192))
|
while($data = fread($value, 8192))
|
||||||
$this->statement->send_long_data($key, $data);
|
$this->statement->send_long_data($key, $data);
|
||||||
} elseif(is_scalar($value))
|
} elseif(is_scalar($value) || $value instanceof Stringable)
|
||||||
$this->statement->send_long_data($key, (string)$value);
|
$this->statement->send_long_data($key, (string)$value);
|
||||||
|
|
||||||
$value = null;
|
$value = null;
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
namespace Index\Data\NullDb;
|
namespace Index\Data\NullDb;
|
||||||
|
|
||||||
use Index\Data\{DbResult,DbResultIterator};
|
use Index\Data\{DbResult,DbResultIterator};
|
||||||
use Index\Io\Stream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a dummy database result.
|
* Represents a dummy database result.
|
||||||
|
@ -56,10 +55,6 @@ class NullDbResult implements DbResult {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStream(int|string $index): ?Stream {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIterator(callable $construct): DbResultIterator {
|
public function getIterator(callable $construct): DbResultIterator {
|
||||||
return new DbResultIterator($this, $construct);
|
return new DbResultIterator($this, $construct);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ namespace Index\Data\Sqlite;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use SQLite3;
|
use SQLite3;
|
||||||
use Index\Data\{DbConnection,DbTransactions,DbStatement,DbResult};
|
use Index\Data\{DbConnection,DbTransactions,DbStatement,DbResult};
|
||||||
use Index\Io\{GenericStream,Stream};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a client for an SQLite database.
|
* Represents a client for an SQLite database.
|
||||||
|
@ -458,22 +457,20 @@ class SqliteConnection implements DbConnection, DbTransactions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a BLOB field as a Stream.
|
* Opens a BLOB field as a resource.
|
||||||
*
|
|
||||||
* To be used instead of getStream on SqliteResult.
|
|
||||||
*
|
*
|
||||||
* @param string $table Name of the source table.
|
* @param string $table Name of the source table.
|
||||||
* @param string $column Name of the source column.
|
* @param string $column Name of the source column.
|
||||||
* @param int $rowId ID of the source row.
|
* @param int $rowId ID of the source row.
|
||||||
* @throws RuntimeException If opening the BLOB failed.
|
* @throws RuntimeException If opening the BLOB failed.
|
||||||
* @return Stream BLOB field as a Stream.
|
* @return resource BLOB field as a file resource.
|
||||||
*/
|
*/
|
||||||
public function getBlobStream(string $table, string $column, int $rowId): Stream {
|
public function getBlobStream(string $table, string $column, int $rowId): mixed {
|
||||||
$handle = $this->connection->openBlob($table, $column, $rowId);
|
$handle = $this->connection->openBlob($table, $column, $rowId);
|
||||||
if(!is_resource($handle))
|
if(!is_resource($handle))
|
||||||
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
throw new RuntimeException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||||
|
|
||||||
return new GenericStream($handle);
|
return $handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLastInsertId(): int|string {
|
public function getLastInsertId(): int|string {
|
||||||
|
|
|
@ -8,7 +8,6 @@ namespace Index\Data\Sqlite;
|
||||||
use SQLite3Result;
|
use SQLite3Result;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Index\Data\{DbResult,DbResultTrait};
|
use Index\Data\{DbResult,DbResultTrait};
|
||||||
use Index\Io\{Stream,TempFileStream};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an SQLite result set.
|
* Represents an SQLite result set.
|
||||||
|
@ -54,20 +53,5 @@ class SqliteResult implements DbResult {
|
||||||
return $this->currentRow[$index] ?? null;
|
return $this->currentRow[$index] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the value from the target index as a Stream.
|
|
||||||
* If you're aware that you're using SQLite it may make more sense to use SqliteConnection::getBlobStream instead.
|
|
||||||
*/
|
|
||||||
public function getStream(int|string $index): ?Stream {
|
|
||||||
if($this->isNull($index))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
$value = $this->getValue($index);
|
|
||||||
if(!is_scalar($value))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new TempFileStream((string)$value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close(): void {}
|
public function close(): void {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ use SQLite3Stmt;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\Data\{DbTools,DbType,DbStatement,DbResult};
|
use Index\Data\{DbTools,DbType,DbStatement,DbResult};
|
||||||
use Index\Io\Stream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a prepared SQLite SQL statement.
|
* Represents a prepared SQLite SQL statement.
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
namespace Index\Http\Content;
|
namespace Index\Http\Content;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
use Index\Bencode\{Bencode,BencodeSerializable};
|
use Index\Bencode\{Bencode,BencodeSerializable};
|
||||||
use Index\Io\{Stream,FileStream};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents Bencoded body content for a HTTP message.
|
* Represents Bencoded body content for a HTTP message.
|
||||||
|
@ -62,7 +62,15 @@ class BencodedContent implements BencodeSerializable, HttpContent {
|
||||||
* @return BencodedContent Instance representing the provided path.
|
* @return BencodedContent Instance representing the provided path.
|
||||||
*/
|
*/
|
||||||
public static function fromFile(string $path): BencodedContent {
|
public static function fromFile(string $path): BencodedContent {
|
||||||
return self::fromEncoded(FileStream::openRead($path));
|
$handle = fopen($path, 'rb');
|
||||||
|
if(!is_resource($handle))
|
||||||
|
throw new RuntimeException('$path could not be opened');
|
||||||
|
|
||||||
|
try {
|
||||||
|
return self::fromEncoded($handle);
|
||||||
|
} finally {
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
namespace Index\Http\Content;
|
namespace Index\Http\Content;
|
||||||
|
|
||||||
use JsonSerializable;
|
use JsonSerializable;
|
||||||
use Index\Io\{Stream,FileStream};
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents JSON body content for a HTTP message.
|
* Represents JSON body content for a HTTP message.
|
||||||
|
@ -66,7 +66,11 @@ class JsonContent implements HttpContent, JsonSerializable {
|
||||||
* @return JsonContent Instance representing the provided path.
|
* @return JsonContent Instance representing the provided path.
|
||||||
*/
|
*/
|
||||||
public static function fromFile(string $path): JsonContent {
|
public static function fromFile(string $path): JsonContent {
|
||||||
return self::fromEncoded(FileStream::openRead($path));
|
$contents = file_get_contents($path);
|
||||||
|
if($contents === false)
|
||||||
|
throw new RuntimeException('was unable to read file at $path');
|
||||||
|
|
||||||
|
return self::fromEncoded($contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
<?php
|
|
||||||
// StreamContent.php
|
|
||||||
// Created: 2022-02-10
|
|
||||||
// Updated: 2024-10-02
|
|
||||||
|
|
||||||
namespace Index\Http\Content;
|
|
||||||
|
|
||||||
use Index\Io\{Stream,FileStream};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents Stream body content for a HTTP message.
|
|
||||||
*/
|
|
||||||
class StreamContent implements HttpContent {
|
|
||||||
/**
|
|
||||||
* @param Stream $stream Stream that represents this message body.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private Stream $stream
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the underlying stream.
|
|
||||||
*
|
|
||||||
* @return Stream Underlying stream.
|
|
||||||
*/
|
|
||||||
public function getStream(): Stream {
|
|
||||||
return $this->stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString(): string {
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
<?php
|
|
||||||
// StreamContentHandler.php
|
|
||||||
// Created: 2024-03-28
|
|
||||||
// Updated: 2024-10-02
|
|
||||||
|
|
||||||
namespace Index\Http\ContentHandling;
|
|
||||||
|
|
||||||
use Index\Http\HttpResponseBuilder;
|
|
||||||
use Index\Http\Content\StreamContent;
|
|
||||||
use Index\Io\Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a Stream content handler for building HTTP response messages.
|
|
||||||
*/
|
|
||||||
class StreamContentHandler implements ContentHandler {
|
|
||||||
public function match(mixed $content): bool {
|
|
||||||
return $content instanceof Stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param Stream $content */
|
|
||||||
public function handle(HttpResponseBuilder $response, mixed $content): void {
|
|
||||||
if(!$response->hasContentType())
|
|
||||||
$response->setTypeStream();
|
|
||||||
|
|
||||||
$response->setContent(new StreamContent($content));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,8 +6,7 @@
|
||||||
namespace Index\Http;
|
namespace Index\Http;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\Io\Stream;
|
use Index\Http\Content\{BencodedContent,FormContent,HttpContent,JsonContent,StringContent};
|
||||||
use Index\Http\Content\{BencodedContent,FormContent,HttpContent,JsonContent,StreamContent,StringContent};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a base HTTP message.
|
* Represents a base HTTP message.
|
||||||
|
@ -129,15 +128,6 @@ abstract class HttpMessage {
|
||||||
return $this->content instanceof FormContent;
|
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.
|
* Checks if the body content is StringContent.
|
||||||
*
|
*
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
|
|
||||||
namespace Index\Http;
|
namespace Index\Http;
|
||||||
|
|
||||||
use Index\Io\Stream;
|
use InvalidArgumentException;
|
||||||
use Index\Http\Content\{HttpContent,StreamContent,StringContent};
|
use RuntimeException;
|
||||||
|
use Stringable;
|
||||||
|
use Index\Http\Content\{HttpContent,StringContent};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a base HTTP message builder.
|
* Represents a base HTTP message builder.
|
||||||
|
@ -119,13 +121,28 @@ class HttpMessageBuilder {
|
||||||
/**
|
/**
|
||||||
* Sets HTTP message body contents.
|
* Sets HTTP message body contents.
|
||||||
*
|
*
|
||||||
* @param HttpContent|Stream|string|null $content Body contents
|
* @param HttpContent|Stringable|string|int|float|resource|null $content Body contents
|
||||||
*/
|
*/
|
||||||
public function setContent(HttpContent|Stream|string|null $content): void {
|
public function setContent(mixed $content): void {
|
||||||
if($content instanceof Stream)
|
if($content instanceof HttpContent || $content === null) {
|
||||||
$content = new StreamContent($content);
|
|
||||||
elseif(is_string($content))
|
|
||||||
$content = new StringContent($content);
|
|
||||||
$this->content = $content;
|
$this->content = $content;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_scalar($content) || $content instanceof Stringable) {
|
||||||
|
$this->content = new StringContent((string)$content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_resource($content)) {
|
||||||
|
$content = stream_get_contents($content);
|
||||||
|
if($content === false)
|
||||||
|
throw new RuntimeException('was unable to read the stream resource in $content');
|
||||||
|
|
||||||
|
$this->content = new StringContent($content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException('$content not a supported type');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace Index\Http;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\MediaType;
|
use Index\MediaType;
|
||||||
use Index\Http\Content\{HttpContent,JsonContent,StreamContent,FormContent};
|
use Index\Http\Content\{HttpContent,FormContent,JsonContent,StringContent};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a HTTP request message.
|
* Represents a HTTP request message.
|
||||||
|
@ -187,7 +187,7 @@ class HttpRequest extends HttpMessage {
|
||||||
&& ($contentType->equals('application/x-www-form-urlencoded') || $contentType->equals('multipart/form-data')))
|
&& ($contentType->equals('application/x-www-form-urlencoded') || $contentType->equals('multipart/form-data')))
|
||||||
$build->setContent(FormContent::fromRequest());
|
$build->setContent(FormContent::fromRequest());
|
||||||
elseif($contentLength > 0)
|
elseif($contentLength > 0)
|
||||||
$build->setContent(StreamContent::fromRequest());
|
$build->setContent(StringContent::fromRequest());
|
||||||
|
|
||||||
return $build->toRequest();
|
return $build->toRequest();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,13 @@ namespace Index\Http;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\{MediaType,Closeable};
|
use Index\MediaType;
|
||||||
use Index\Io\{Stream,FileStream};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an uploaded file in a multipart/form-data request.
|
* Represents an uploaded file in a multipart/form-data request.
|
||||||
*/
|
*/
|
||||||
class HttpUploadedFile implements Closeable {
|
class HttpUploadedFile {
|
||||||
private bool $hasMoved = false;
|
private bool $hasMoved = false;
|
||||||
private ?Stream $stream = null;
|
|
||||||
|
|
||||||
private MediaType $suggestedMediaType;
|
private MediaType $suggestedMediaType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,25 +98,6 @@ class HttpUploadedFile implements Closeable {
|
||||||
return $this->hasMoved;
|
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)
|
|
||||||
throw new RuntimeException('Can\'t open stream because of an upload error.');
|
|
||||||
if($this->hasMoved)
|
|
||||||
throw new RuntimeException('Can\'t open stream because file has already been moved.');
|
|
||||||
|
|
||||||
$this->stream = FileStream::openRead($this->localFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves the uploaded file to its final destination.
|
* Moves the uploaded file to its final destination.
|
||||||
*
|
*
|
||||||
|
@ -138,9 +116,6 @@ class HttpUploadedFile implements Closeable {
|
||||||
if(empty($path))
|
if(empty($path))
|
||||||
throw new InvalidArgumentException('$path is not a valid path.');
|
throw new InvalidArgumentException('$path is not a valid path.');
|
||||||
|
|
||||||
if($this->stream !== null)
|
|
||||||
$this->stream->close();
|
|
||||||
|
|
||||||
$this->hasMoved = PHP_SAPI === 'CLI'
|
$this->hasMoved = PHP_SAPI === 'CLI'
|
||||||
? rename($this->localFileName, $path)
|
? rename($this->localFileName, $path)
|
||||||
: move_uploaded_file($this->localFileName, $path);
|
: move_uploaded_file($this->localFileName, $path);
|
||||||
|
@ -151,15 +126,6 @@ class HttpUploadedFile implements Closeable {
|
||||||
$this->localFileName = $path;
|
$this->localFileName = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function close(): void {
|
|
||||||
if($this->stream !== null)
|
|
||||||
$this->stream->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __destruct() {
|
|
||||||
$this->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a HttpUploadedFile instance from an entry in the $_FILES superglobal.
|
* Creates a HttpUploadedFile instance from an entry in the $_FILES superglobal.
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,7 +9,7 @@ use stdClass;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Index\Http\{HttpResponse,HttpResponseBuilder,HttpRequest};
|
use Index\Http\{HttpResponse,HttpResponseBuilder,HttpRequest};
|
||||||
use Index\Http\Content\StringContent;
|
use Index\Http\Content\StringContent;
|
||||||
use Index\Http\ContentHandling\{BencodeContentHandler,ContentHandler,JsonContentHandler,StreamContentHandler};
|
use Index\Http\ContentHandling\{BencodeContentHandler,ContentHandler,JsonContentHandler};
|
||||||
use Index\Http\ErrorHandling\{HtmlErrorHandler,ErrorHandler,PlainErrorHandler};
|
use Index\Http\ErrorHandling\{HtmlErrorHandler,ErrorHandler,PlainErrorHandler};
|
||||||
|
|
||||||
class HttpRouter implements Router {
|
class HttpRouter implements Router {
|
||||||
|
@ -113,7 +113,6 @@ class HttpRouter implements Router {
|
||||||
* Register the default content handlers.
|
* Register the default content handlers.
|
||||||
*/
|
*/
|
||||||
public function registerDefaultContentHandlers(): void {
|
public function registerDefaultContentHandlers(): void {
|
||||||
$this->registerContentHandler(new StreamContentHandler);
|
|
||||||
$this->registerContentHandler(new JsonContentHandler);
|
$this->registerContentHandler(new JsonContentHandler);
|
||||||
$this->registerContentHandler(new BencodeContentHandler);
|
$this->registerContentHandler(new BencodeContentHandler);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,256 +0,0 @@
|
||||||
<?php
|
|
||||||
// FileStream.php
|
|
||||||
// Created: 2021-04-30
|
|
||||||
// Updated: 2024-10-02
|
|
||||||
|
|
||||||
namespace Index\Io;
|
|
||||||
|
|
||||||
use ErrorException;
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a Stream representing a file.
|
|
||||||
*/
|
|
||||||
class FileStream extends GenericStream {
|
|
||||||
/**
|
|
||||||
* Open file for reading, throw if not exists.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const OPEN_READ = 'rb';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open file for reading and writing, throw if not exist.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const OPEN_READ_WRITE = 'r+b';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create file for writing, truncate if exist.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const NEW_WRITE = 'wb';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create file for reading and writing, truncate if exist.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const NEW_READ_WRITE = 'w+b';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open file for appending, create if not exist.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const APPEND_WRITE = 'ab';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open file for reading and appending, create if not exist.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const APPEND_READ_WRITE = 'a+b';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create file for writing, throw if exist.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const CREATE_WRITE = 'xb';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create file for reading and writing, throw if exist.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const CREATE_READ_WRITE = 'x+b';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens or creates a file for writing.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const OPEN_OR_CREATE_WRITE = 'cb';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens or creates a file for reading and writing.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
public const OPEN_OR_CREATE_READ_WRITE = 'c+b';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Don't lock file.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public const LOCK_NONE = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lock for reading.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public const LOCK_READ = LOCK_SH;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lock for writing.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public const LOCK_WRITE = LOCK_EX;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Don't block during lock attempt.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public const LOCK_NON_BLOCKING = LOCK_NB;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $path Path to the file.
|
|
||||||
* @param string $mode Stream mode to use to open the file.
|
|
||||||
* @param int $lock Locking method to use.
|
|
||||||
* @throws RuntimeException If the file could not be opened.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
string $path,
|
|
||||||
string $mode,
|
|
||||||
private int $lock
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
$stream = fopen($path, $mode);
|
|
||||||
} catch(ErrorException $ex) {
|
|
||||||
throw new RuntimeException('An error occurred while trying to open a file.', $ex->getCode(), $ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if($stream === false)
|
|
||||||
throw new RuntimeException('An unhandled error occurred while trying to open a file.');
|
|
||||||
|
|
||||||
parent::__construct($stream);
|
|
||||||
|
|
||||||
if($this->lock & self::LOCK_WRITE)
|
|
||||||
flock($this->stream, LOCK_EX);
|
|
||||||
elseif($this->lock & self::LOCK_READ)
|
|
||||||
flock($this->stream, LOCK_SH);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close(): void {
|
|
||||||
if($this->lock !== self::LOCK_NONE)
|
|
||||||
flock($this->stream, LOCK_UN);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,195 +0,0 @@
|
||||||
<?php
|
|
||||||
// GenericStream.php
|
|
||||||
// Created: 2021-04-30
|
|
||||||
// Updated: 2024-10-02
|
|
||||||
|
|
||||||
namespace Index\Io;
|
|
||||||
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents any stream that can be handled using the C-like f* functions.
|
|
||||||
*/
|
|
||||||
class GenericStream extends Stream {
|
|
||||||
/**
|
|
||||||
* Readable file modes.
|
|
||||||
*
|
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
public const READABLE = [
|
|
||||||
'r', 'rb', 'r+', 'r+b', 'w+', 'w+b',
|
|
||||||
'a+', 'a+b', 'x+', 'x+b', 'c+', 'c+b',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writeable file modes.
|
|
||||||
*
|
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
public const WRITEABLE = [
|
|
||||||
'r+', 'r+b', 'w', 'wb', 'w+', 'w+b',
|
|
||||||
'a', 'ab', 'a+', 'a+b', 'x', 'xb',
|
|
||||||
'x+', 'x+b', 'c', 'cb', 'c+', 'c+b',
|
|
||||||
];
|
|
||||||
|
|
||||||
// can't seem to turn this one into a constructor property thing?
|
|
||||||
/** @var resource */
|
|
||||||
protected $stream;
|
|
||||||
|
|
||||||
private bool $canRead;
|
|
||||||
private bool $canWrite;
|
|
||||||
private bool $canSeek;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param resource $stream Resource that this stream represents.
|
|
||||||
*/
|
|
||||||
public function __construct($stream) {
|
|
||||||
if(!is_resource($stream) || get_resource_type($stream) !== 'stream')
|
|
||||||
throw new InvalidArgumentException('$stream is not a valid stream resource.');
|
|
||||||
$this->stream = $stream;
|
|
||||||
|
|
||||||
$metaData = $this->metaData();
|
|
||||||
$this->canRead = in_array($metaData['mode'], self::READABLE);
|
|
||||||
$this->canWrite = in_array($metaData['mode'], self::WRITEABLE);
|
|
||||||
$this->canSeek = is_bool($metaData['seekable']) ? $metaData['seekable'] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the underlying resource handle.
|
|
||||||
*
|
|
||||||
* @return resource Underlying resource handle.
|
|
||||||
*/
|
|
||||||
public function getResource() {
|
|
||||||
return $this->stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<string|int, mixed> */
|
|
||||||
private function stat(): array {
|
|
||||||
$stat = fstat($this->stream);
|
|
||||||
if($stat === false)
|
|
||||||
throw new RuntimeException('fstat returned false');
|
|
||||||
|
|
||||||
return $stat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPosition(): int {
|
|
||||||
$tell = ftell($this->stream);
|
|
||||||
if($tell === false)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return $tell;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLength(): int {
|
|
||||||
if(!$this->canSeek())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
$size = $this->stat()['size'];
|
|
||||||
if(!is_int($size))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return $size;
|
|
||||||
}
|
|
||||||
public function setLength(int $length): void {
|
|
||||||
if($length < 0)
|
|
||||||
throw new InvalidArgumentException('$length must be a positive integer');
|
|
||||||
|
|
||||||
ftruncate($this->stream, $length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<string, mixed> */
|
|
||||||
private function metaData(): array {
|
|
||||||
return stream_get_meta_data($this->stream);
|
|
||||||
}
|
|
||||||
public function canRead(): bool {
|
|
||||||
return $this->canRead;
|
|
||||||
}
|
|
||||||
public function canWrite(): bool {
|
|
||||||
return $this->canWrite;
|
|
||||||
}
|
|
||||||
public function canSeek(): bool {
|
|
||||||
return $this->canSeek;
|
|
||||||
}
|
|
||||||
public function hasTimedOut(): bool {
|
|
||||||
$timedOut = $this->metaData()['timed_out'];
|
|
||||||
if(!is_bool($timedOut))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return $timedOut;
|
|
||||||
}
|
|
||||||
public function isBlocking(): bool {
|
|
||||||
$blocked = $this->metaData()['blocked'];
|
|
||||||
if(!is_bool($blocked))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return $blocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isEnded(): bool {
|
|
||||||
return feof($this->stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function readChar(): ?string {
|
|
||||||
$char = fgetc($this->stream);
|
|
||||||
if($char === false)
|
|
||||||
return null;
|
|
||||||
return $char;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function readLine(): ?string {
|
|
||||||
return ($line = fgets($this->stream)) === false
|
|
||||||
? null : $line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function read(int $length): ?string {
|
|
||||||
if($length < 1)
|
|
||||||
throw new InvalidArgumentException('$length must be greater than 0');
|
|
||||||
|
|
||||||
$buffer = fread($this->stream, $length);
|
|
||||||
if($buffer === false)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return $buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if($length >= 0)
|
|
||||||
fwrite($this->stream, $buffer, $length);
|
|
||||||
else
|
|
||||||
fwrite($this->stream, $buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function writeChar(string $char): void {
|
|
||||||
fwrite($this->stream, $char, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function flush(): void {
|
|
||||||
fflush($this->stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close(): void {
|
|
||||||
try {
|
|
||||||
fclose($this->stream);
|
|
||||||
} catch(\Error $ex) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[\Override]
|
|
||||||
public function copyTo(Stream $other): void {
|
|
||||||
if($other instanceof GenericStream) {
|
|
||||||
stream_copy_to_stream($this->stream, $other->stream);
|
|
||||||
} else parent::copyTo($other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString(): string {
|
|
||||||
$contents = stream_get_contents($this->stream);
|
|
||||||
if($contents === false)
|
|
||||||
return '';
|
|
||||||
|
|
||||||
return $contents;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
<?php
|
|
||||||
// MemoryStream.php
|
|
||||||
// Created: 2021-05-02
|
|
||||||
// Updated: 2024-10-02
|
|
||||||
|
|
||||||
namespace Index\Io;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an in-memory stream.
|
|
||||||
*/
|
|
||||||
class MemoryStream extends GenericStream {
|
|
||||||
public function __construct() {
|
|
||||||
$stream = fopen('php://memory', 'r+b');
|
|
||||||
if($stream === false)
|
|
||||||
throw new RuntimeException('failed to fopen a memory stream');
|
|
||||||
|
|
||||||
parent::__construct($stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a memory stream from a string/binary data.
|
|
||||||
*
|
|
||||||
* @param string $string String to create the stream from.
|
|
||||||
* @return MemoryStream Stream representing the string.
|
|
||||||
*/
|
|
||||||
public static function fromString(string $string): MemoryStream {
|
|
||||||
$stream = new MemoryStream;
|
|
||||||
$stream->write($string);
|
|
||||||
return $stream;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
<?php
|
|
||||||
// NetworkStream.php
|
|
||||||
// Created: 2021-04-30
|
|
||||||
// Updated: 2024-10-02
|
|
||||||
|
|
||||||
namespace Index\Io;
|
|
||||||
|
|
||||||
use ErrorException;
|
|
||||||
use RuntimeException;
|
|
||||||
use Index\Net\{IpAddress,EndPoint};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a network socket stream.
|
|
||||||
*/
|
|
||||||
class NetworkStream extends GenericStream {
|
|
||||||
/**
|
|
||||||
* @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.
|
|
||||||
* @throws RuntimeException If the socket failed to open.
|
|
||||||
*/
|
|
||||||
public function __construct(string $hostname, int $port, float|null $timeout) {
|
|
||||||
try {
|
|
||||||
$stream = fsockopen($hostname, $port, $errcode, $errmsg, $timeout);
|
|
||||||
} catch(ErrorException $ex) {
|
|
||||||
throw new RuntimeException('An error occurred while trying to connect.', $ex->getCode(), $ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if($stream === false)
|
|
||||||
throw new RuntimeException('An unhandled error occurred while trying to connect.');
|
|
||||||
|
|
||||||
parent::__construct($stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets whether the network stream should have blocking reads and writes.
|
|
||||||
*
|
|
||||||
* @param bool $blocking true to block, false to async.
|
|
||||||
*/
|
|
||||||
public function setBlocking(bool $blocking): void {
|
|
||||||
stream_set_blocking($this->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://' . $path, -1, $timeout);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
<?php
|
|
||||||
// ProcessStream.php
|
|
||||||
// Created: 2023-01-25
|
|
||||||
// Updated: 2024-10-02
|
|
||||||
|
|
||||||
namespace Index\Io;
|
|
||||||
|
|
||||||
use InvalidArgumentException;
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a stream to a running sub-process.
|
|
||||||
*/
|
|
||||||
class ProcessStream extends Stream {
|
|
||||||
/** @var resource */
|
|
||||||
private $handle;
|
|
||||||
|
|
||||||
private bool $canRead;
|
|
||||||
private bool $canWrite;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $command Command to run for this stream.
|
|
||||||
* @param string $mode File mode to use.
|
|
||||||
* @throws RuntimeException If we were unable to spawn the process.
|
|
||||||
*/
|
|
||||||
public function __construct(string $command, string $mode) {
|
|
||||||
$handle = popen($command, $mode);
|
|
||||||
if($handle === false)
|
|
||||||
throw new RuntimeException('Failed to create process.');
|
|
||||||
|
|
||||||
$this->handle = $handle;
|
|
||||||
$this->canRead = strpos($mode, 'r') !== false;
|
|
||||||
$this->canWrite = strpos($mode, 'w') !== false;
|
|
||||||
|
|
||||||
if(!is_resource($this->handle))
|
|
||||||
throw new RuntimeException('Failed to create process.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPosition(): int {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLength(): int {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
public function setLength(int $length): void {}
|
|
||||||
|
|
||||||
public function canRead(): bool {
|
|
||||||
return $this->canRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function canWrite(): bool {
|
|
||||||
return $this->canWrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function canSeek(): bool {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasTimedOut(): bool {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isBlocking(): bool {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isEnded(): bool {
|
|
||||||
return feof($this->handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function readChar(): ?string {
|
|
||||||
$char = fgetc($this->handle);
|
|
||||||
if($char === false)
|
|
||||||
return null;
|
|
||||||
return $char;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function readLine(): ?string {
|
|
||||||
return ($line = fgets($this->handle)) === false
|
|
||||||
? null : $line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function read(int $length): ?string {
|
|
||||||
if($length < 1)
|
|
||||||
throw new InvalidArgumentException('$length must be greater than 0');
|
|
||||||
|
|
||||||
$buffer = fread($this->handle, $length);
|
|
||||||
if($buffer === false)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return $buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function seek(int $offset, int $origin = self::START): bool {
|
|
||||||
throw new RuntimeException('Cannot seek ProcessStream.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function write(string $buffer, int $length = -1): void {
|
|
||||||
if($length >= 0)
|
|
||||||
fwrite($this->handle, $buffer, $length);
|
|
||||||
else
|
|
||||||
fwrite($this->handle, $buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function writeChar(string $char): void {
|
|
||||||
fwrite($this->handle, $char, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function flush(): void {}
|
|
||||||
|
|
||||||
public function close(): void {
|
|
||||||
if(is_resource($this->handle))
|
|
||||||
pclose($this->handle);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,219 +0,0 @@
|
||||||
<?php
|
|
||||||
// Stream.php
|
|
||||||
// Created: 2021-04-30
|
|
||||||
// Updated: 2024-10-02
|
|
||||||
|
|
||||||
namespace Index\Io;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
use Stringable;
|
|
||||||
use Index\Closeable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a generic data stream.
|
|
||||||
*/
|
|
||||||
abstract class Stream implements Stringable, Closeable {
|
|
||||||
/**
|
|
||||||
* Place the cursor relative to the start of the file.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public const START = SEEK_SET;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Place the cursor relative to the current position of the cursor.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public const CURRENT = SEEK_CUR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Place the cursor relative to the end of the file.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public const END = SEEK_END;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the current cursor position.
|
|
||||||
*
|
|
||||||
* @return int Cursor position.
|
|
||||||
*/
|
|
||||||
abstract public function getPosition(): int;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the current length of the stream.
|
|
||||||
*
|
|
||||||
* @return int Stream length.
|
|
||||||
*/
|
|
||||||
abstract public function getLength(): int;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the length of the stream.
|
|
||||||
*
|
|
||||||
* @param int $length Desired stream length.
|
|
||||||
*/
|
|
||||||
abstract public function setLength(int $length): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this stream is readable.
|
|
||||||
*
|
|
||||||
* @return bool true if the stream is readable.
|
|
||||||
*/
|
|
||||||
abstract public function canRead(): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this stream is writeable.
|
|
||||||
*
|
|
||||||
* @return bool true if the stream is writeable.
|
|
||||||
*/
|
|
||||||
abstract public function canWrite(): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this stream is seekable.
|
|
||||||
*
|
|
||||||
* @return bool true if the stream is seekable.
|
|
||||||
*/
|
|
||||||
abstract public function canSeek(): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this stream has timed out.
|
|
||||||
*
|
|
||||||
* @return bool true if the stream has timed out.
|
|
||||||
*/
|
|
||||||
abstract public function hasTimedOut(): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this stream has blocking operations.
|
|
||||||
*
|
|
||||||
* @return bool true if the stream has blocking operations.
|
|
||||||
*/
|
|
||||||
abstract public function isBlocking(): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the cursor is at the end of the file.
|
|
||||||
*
|
|
||||||
* @return bool true if the cursor is at the end of the file.
|
|
||||||
*/
|
|
||||||
abstract public function isEnded(): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a single character from the stream.
|
|
||||||
*
|
|
||||||
* @return ?string One character or null if we're at the end of the stream.
|
|
||||||
*/
|
|
||||||
abstract public function readChar(): ?string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a single byte from the stream.
|
|
||||||
*
|
|
||||||
* @return int One byte or -1 if we're at the end of the stream.
|
|
||||||
*/
|
|
||||||
public function readByte(): int {
|
|
||||||
$char = $this->readChar();
|
|
||||||
if($char === null)
|
|
||||||
return -1;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 RuntimeException 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);
|
|
||||||
|
|
||||||
while(!$this->isEnded()) {
|
|
||||||
$buffer = $this->read(8192);
|
|
||||||
if($buffer === null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
$other->write($buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force write of all buffered output to the stream.
|
|
||||||
*/
|
|
||||||
abstract public function flush(): void;
|
|
||||||
|
|
||||||
abstract public function close(): void;
|
|
||||||
|
|
||||||
public function __toString(): string {
|
|
||||||
if($this->canSeek())
|
|
||||||
$this->seek(0);
|
|
||||||
|
|
||||||
$string = '';
|
|
||||||
while(!$this->isEnded())
|
|
||||||
$string .= $this->read(8192);
|
|
||||||
|
|
||||||
return $string;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __destruct() {
|
|
||||||
$this->close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
<?php
|
|
||||||
// TempFileStream.php
|
|
||||||
// Created: 2021-05-02
|
|
||||||
// Updated: 2024-10-02
|
|
||||||
|
|
||||||
namespace Index\Io;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a temporary file stream. Will remain in memory if the size is below a given threshold.
|
|
||||||
*/
|
|
||||||
class TempFileStream extends GenericStream {
|
|
||||||
/**
|
|
||||||
* Default maximum size the stream can be to remain in memory and not be written to disk.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public const MAX_MEMORY = 2 * 1024 * 1024;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ?string $body Contents of the stream.
|
|
||||||
* @param int $maxMemory Maximum filesize until the file is moved out of memory and to disk.
|
|
||||||
*/
|
|
||||||
public function __construct(?string $body = null, int $maxMemory = self::MAX_MEMORY) {
|
|
||||||
$stream = fopen("php://temp/maxmemory:{$maxMemory}", 'r+b');
|
|
||||||
if($stream === false)
|
|
||||||
throw new RuntimeException('failed to fopen a temporary file stream');
|
|
||||||
|
|
||||||
parent::__construct($stream);
|
|
||||||
|
|
||||||
if($body !== null) {
|
|
||||||
$this->write($body);
|
|
||||||
$this->flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a TempFileStream from a string.
|
|
||||||
*
|
|
||||||
* @param string $string Data to write to the temporary file.
|
|
||||||
* @return TempFileStream Stream representing the given text.
|
|
||||||
*/
|
|
||||||
public static function fromString(string $string): TempFileStream {
|
|
||||||
return new TempFileStream($string);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,21 +11,27 @@ use Index\Data\{DbTools,DbType};
|
||||||
use Index\Data\MariaDb\MariaDbBackend;
|
use Index\Data\MariaDb\MariaDbBackend;
|
||||||
use Index\Data\NullDb\NullDbConnection;
|
use Index\Data\NullDb\NullDbConnection;
|
||||||
use Index\Data\Sqlite\SqliteBackend;
|
use Index\Data\Sqlite\SqliteBackend;
|
||||||
use Index\Io\MemoryStream;
|
|
||||||
|
|
||||||
#[CoversClass(DbTools::class)]
|
#[CoversClass(DbTools::class)]
|
||||||
#[CoversClass(DbType::class)]
|
#[CoversClass(DbType::class)]
|
||||||
#[CoversClass(NullDbConnection::class)]
|
#[CoversClass(NullDbConnection::class)]
|
||||||
#[CoversClass(MariaDbBackend::class)]
|
#[CoversClass(MariaDbBackend::class)]
|
||||||
#[CoversClass(SqliteBackend::class)]
|
#[CoversClass(SqliteBackend::class)]
|
||||||
#[UsesClass(MemoryStream::class)]
|
|
||||||
final class DbToolsTest extends TestCase {
|
final class DbToolsTest extends TestCase {
|
||||||
public function testDetectType(): void {
|
public function testDetectType(): void {
|
||||||
$this->assertEquals(DbType::NULL, DbTools::detectType(null));
|
$this->assertEquals(DbType::NULL, DbTools::detectType(null));
|
||||||
$this->assertEquals(DbType::INTEGER, DbTools::detectType(12345));
|
$this->assertEquals(DbType::INTEGER, DbTools::detectType(12345));
|
||||||
$this->assertEquals(DbType::FLOAT, DbTools::detectType(123.45));
|
$this->assertEquals(DbType::FLOAT, DbTools::detectType(123.45));
|
||||||
$this->assertEquals(DbType::STRING, DbTools::detectType('This is a string.'));
|
$this->assertEquals(DbType::STRING, DbTools::detectType('This is a string.'));
|
||||||
$this->assertEquals(DbType::BLOB, DbTools::detectType(MemoryStream::fromString('This is a string inside a memory stream.')));
|
|
||||||
|
$blob = fopen('php://memory', 'r+b');
|
||||||
|
if($blob === false)
|
||||||
|
throw new RuntimeException('failed to fopen a memory stream');
|
||||||
|
|
||||||
|
fwrite($blob, 'This is a string inside a memory stream.');
|
||||||
|
fseek($blob, 0);
|
||||||
|
|
||||||
|
$this->assertEquals(DbType::BLOB, DbTools::detectType($blob));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDSN(): void {
|
public function testDSN(): void {
|
||||||
|
|
Loading…
Add table
Reference in a new issue