Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
paranoiq committed Feb 24, 2023
1 parent 116f5b6 commit 9842693
Show file tree
Hide file tree
Showing 62 changed files with 5,407 additions and 770 deletions.
81 changes: 50 additions & 31 deletions build/PHPStan/phpstan.conf.php
Original file line number Diff line number Diff line change
@@ -1,33 +1,52 @@
<?php declare(strict_types = 1);
<?php declare(strict_types=1);

$ignore = [];
// 8.0+
if (PHP_VERSION_ID >= 80000) {
$ignore[] = '~Parameter #1 \$objectOrClass of class ReflectionClass constructor expects class-string<T of object>\|T of object, string given.~'; # in MethodTypeParser; temporary
$ignore[] = '~Strict comparison using === between CurlMultiHandle and false will always evaluate to false.~'; # in HttpChannelManager; probably a reflection bug
}
// 7.1 - 8.0
if (PHP_VERSION_ID < 80000) {
$ignore[] = '~Parameter #1 \$argument of class ReflectionClass constructor expects class-string<T of object>\|T of object, string given.~'; # you know nothing
$ignore[] = '~Method Dogma\\\\Arr::combine\(\) should return array but returns array\|false.~'; # in Arr
$ignore[] = '~Parameter #1 \$items of class Dogma\\\\ImmutableArray constructor expects array, array\|false given.~'; # in ImmutableArray
$ignore[] = '~has unknown class Curl(Multi)?Handle as its type.~'; # PHP 7 -> 8
$ignore[] = '~has invalid return type Curl(Multi)?Handle~'; # PHP 7 -> 8
}

$excludePaths = [
'*/tests/*/data/*',
];
if (PHP_VERSION_ID < 70200) {
// interface changes allowed in later versions, non-fatal, but not able to ignore in phpstan
$excludePaths[] = '*/Time/DateTime.php';
}

return [
'parameters' => [
'ignoreErrors' => $ignore,
'excludePaths' => [
'analyse' => $excludePaths,
$ignore = PHP_VERSION_ID < 80000
? [
'~Parameter #1 \$argument of class ReflectionClass constructor expects class-string<T of object>\|T of object, string given.~', # you know nothing
'~Method Dogma\\\\Arr::combine\(\) should return array but returns array\|false.~', # in Arr
'~Parameter #1 \$items of class Dogma\\\\ImmutableArray constructor expects array, array\|false given.~', # in ImmutableArray
'~Strict comparison using === between array<string, class-string> and false will always evaluate to false.~', # in Cls
'~Strict comparison using === between DateInterval and false will always evaluate to false.~', # in Time
'~Strict comparison using === between static\(Dogma\\\\Time\\\\DateTime\) and false will always evaluate to false.~', # in DateTime
'~has unknown class Curl(Multi)?Handle as its type.~', # PHP 7 -> 8
'~has invalid type Curl(Multi)?Handle.~', # PHP 7 -> 8
[
'message' => '~Strict comparison using === between int and false will always evaluate to false.~',
'path' => '../../src/Time/DateTime.php',
],
[
'message' => '~Strict comparison using === between string and false will always evaluate to false.~',
'path' => '../../src/Language/Locale/Locale.php',
],
[
'message' => '~Strict comparison using === between (string|int) and false will always evaluate to false.~',
'path' => '../../src/Language/Collator.php',
],
],
];
[
'message' => '~Strict comparison using === between resource and false will always evaluate to false.~',
'path' => '../../src/Http/HttpRequest.php',
],
[
'message' => '~Strict comparison using === between (PDOStatement|string) and false will always evaluate to false.~',
'path' => '../../src/Database/SimplePdo.php',
],
[
'message' => '~Strict comparison using === between array<string, array<int, mixed>\|string\|false> and false will always evaluate to false.~',
'path' => '../../src/Application/Configurator.php',
]
]
: [
'~expects DateTimeZone(\|null)?, DateTimeZone\|false given~', # ignore DateTime::getTimeZone() returning false everywhere, because in that case, something is very wrong (probably php.ini)
'~should return DateTimeZone but returns DateTimeZone\|false.~', # -//-
'~\(DateTimeZone\) does not accept DateTimeZone\|false.~', # -//-
'~Cannot call method [a-zA-Z]+\(\) on DateTimeZone\|false.~', # -//-
'~Parameter #1 \$objectOrClass of class ReflectionClass constructor expects class-string<T of object>\|T of object, string given.~', # in MethodTypeParser; temporary
'~Strict comparison using === between CurlMultiHandle and false will always evaluate to false.~', # in HttpChannelManager; probably a reflection bug
'~Method Dogma\\\\Language\\\\Locale\\\\Locale::matches\(\) should return bool but returns bool\|null.~', # not sure
[
'message' => '~Strict comparison using === between array and null will always evaluate to false.~',
'path' => '../../src/Io/TextFile.php',
]
];

return ['parameters' => ['ignoreErrors' => $ignore]];
25 changes: 25 additions & 0 deletions doc/Io.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Dogma\Io
========

Main classes:
-------------

- **Io** (static) - filesystem toolbox. basic file and directory operations
- **FileInfo** - file/directory path and operations on that
- **LinkInfo** - symbolic link path and operations on that
- **File** - open file in binary mode
- **TextFile** - open file in text mode (lines, CSV)

Exception hierarchy:
--------------------

- **IoException**
- **FilesystemException**
- **FileAlreadyExistsException**
- **FileDoesNotExistException**
- **FilePermissionsException**
- **FileLockingException**
- **StreamException**
- **IniException**
- **ContentTypeDetectionException**
7 changes: 7 additions & 0 deletions src/Enum/IntEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public function dump(): string
);
}

final public static function checkValue(int $value): void
{
if (!self::isValid($value)) {
throw new InvalidValueException($value, static::class);
}
}

/**
* Validates given value. Can also normalize the value, if needed.
*
Expand Down
7 changes: 7 additions & 0 deletions src/Enum/StringEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public function dump(): string
);
}

final public static function checkValue(string $value): void
{
if (!self::isValid($value)) {
throw new InvalidValueException($value, static::class);
}
}

/**
* Validates given value. Can also normalize the value, if needed.
*
Expand Down
6 changes: 3 additions & 3 deletions src/Http/HttpDownloadRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Dogma\Http;

use Dogma\Io\File;
use Dogma\Io\BinaryFile;
use const CURLOPT_BINARYTRANSFER;
use const CURLOPT_FILE;

Expand All @@ -19,7 +19,7 @@
class HttpDownloadRequest extends HttpRequest
{

/** @var File */
/** @var BinaryFile */
private $file;

/**
Expand All @@ -38,7 +38,7 @@ public function prepare(): void
{
parent::prepare();

$this->file = File::createTemporaryFile();
$this->file = BinaryFile::createTemporaryFile();

$this->setOption(CURLOPT_FILE, $this->file->getHandle());
$this->setOption(CURLOPT_BINARYTRANSFER, true);
Expand Down
217 changes: 217 additions & 0 deletions src/Io/BinaryFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<?php declare(strict_types = 1);
/**
* This file is part of the Dogma library (https://github.com/paranoiq/dogma)
*
* Copyright (c) 2012 Vlasta Neubauer (@paranoiq)
*
* For the full copyright and license information read the file 'license.md', distributed with this source code
*/

// spell-check-ignore: maxmemory

namespace Dogma\Io;

use Dogma\InvalidArgumentException;
use Dogma\LogicException;
use Dogma\ResourceType;
use StreamContext;
use function error_clear_last;
use function error_get_last;
use function fread;
use function ftruncate;
use function fwrite;
use function get_resource_type;
use function implode;
use function is_callable;
use function is_resource;
use function is_string;
use function min;
use function strlen;
use function tmpfile;

/**
* An open file in "binary mode"
* All length arguments are in bytes
*/
class BinaryFile extends File
{

/**
* @param string|resource|Path $file
* @param string $mode
* @param StreamContext|null $context
*/
public function __construct($file, string $mode = FileMode::OPEN_READ, ?StreamContext $context = null)
{
if (is_resource($file) && get_resource_type($file) === ResourceType::STREAM) {
$this->handle = $file;
$this->mode = $mode;
return;
} elseif (is_string($file)) {
$this->path = Io::normalizePath($file);
} elseif ($file instanceof Path) {
$this->path = $file->getPath();
} else {
throw new InvalidArgumentException('Argument $file must be a file path or a stream resource.');
}

$this->mode = $mode;
$this->context = $context;

if ($this->handle === null) {
$this->reopen();
}
}

/**
* @return static
*/
public static function createTemporaryFile(): self
{
error_clear_last();
/** @var resource|false $handle */
$handle = tmpfile();

if ($handle === false) {
throw FilesystemException::create("Cannot create a temporary file", null, null, error_get_last());
}

return new static($handle, FileMode::CREATE_OR_TRUNCATE_READ_WRITE);
}

public static function createMemoryFile(?int $maxSize = null): self
{
if ($maxSize === null) {
return new static('php://memory', FileMode::CREATE_OR_TRUNCATE_READ_WRITE);
} else {
return new static("php://temp/maxmemory:$maxSize", FileMode::CREATE_OR_TRUNCATE_READ_WRITE);
}
}

public function toTextFile(?string $encoding = null, ?string $lineEnding = null): TextFile
{
return new TextFile($this->getHandle(), $this->mode, $this->context, $encoding, $lineEnding);
}

// content ---------------------------------------------------------------------------------------------------------

public function getContents(): string
{
if ($this->getPosition()) {
$this->setPosition(0);
}

$results = [];
while (!$this->endOfFileReached()) {
$results[] = $this->read();
}

return implode('', $results);
}

public function read(?int $bytes = null): ?string
{
$bytes = $bytes ?? self::$defaultChunkSize;

if (!FileMode::isReadable($this->mode)) {
throw new LogicException('Cannot read - file opened in write only mode.');
}

error_clear_last();
$data = @fread($this->getHandle(), $bytes);

if ($data === false) {
if ($this->endOfFileReached()) {
throw FilesystemException::create("Cannot read from file, end of file was reached", $this->path, $this->context, error_get_last());
} else {
throw FilesystemException::create("Cannot read from file", $this->path, $this->context, error_get_last());
}
}

return $data === '' ? null : $data;
}

public function write(string $data, ?int $bytes = null): void
{
error_clear_last();
if ($bytes !== null) {
$result = @fwrite($this->getHandle(), $data, $bytes);
} else {
$result = @fwrite($this->getHandle(), $data);
}

if ($result === false) {
throw FilesystemException::create("Cannot write to file", $this->path, $this->context, error_get_last());
}
}

/**
* Truncate file and move pointer at the end
* @param int $bytes
*/
public function truncate(int $bytes = 0): void
{
error_clear_last();
$result = @ftruncate($this->getHandle(), $bytes);

if ($result === false) {
throw FilesystemException::create("Cannot truncate file", $this->path, $this->context, error_get_last());
}

$this->setPosition($bytes);
}

/**
* Copy range of data to another file (appending) or callback. Returns actual length of copied data.
*
* @param self|string|FileInfo|callable $destination
* @param int|null $start
* @param int $bytes
* @param int|null $chunkSize
* @return int
*/
public function copyData($destination, ?int $start = null, int $bytes = 0, ?int $chunkSize = null): int
{
$chunkSize = $chunkSize ?? self::$defaultChunkSize;
if ($start !== null) {
$this->setPosition($start);
}

$close = false;
if (is_string($destination) && !is_callable($destination)) {
$destination = new FileInfo($destination);
}
if ($destination instanceof FileInfo) {
$destination = $destination->open(FileMode::CREATE_OR_APPEND_WRITE);
$close = true;
}

$done = 0;
$chunk = $bytes ? min($bytes - $done, $chunkSize) : $chunkSize;
while (!$this->endOfFileReached() && (!$bytes || $done < $bytes)) {
$buffer = $this->read($chunk);
if ($buffer === null) {
return $done;
}

$done += strlen($buffer);

if ($destination instanceof self) {
$destination->write($buffer);

} elseif (is_callable($destination)) {
$destination($buffer);

} else {
throw new InvalidArgumentException('Destination must be File or callable!');
}
}

if ($close) {
$destination->close();
}

return $done;
}

}
Loading

0 comments on commit 9842693

Please sign in to comment.