diff --git a/composer.json b/composer.json index ececb30..a9ea690 100644 --- a/composer.json +++ b/composer.json @@ -7,8 +7,13 @@ "email": "vintsukevich@gmail.com" } ], + "require": { + "php": "^8.1", + "ext-zlib": "*", + "ext-mbstring": "*" + }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^11.0" }, "autoload": { "psr-4": { @@ -20,4 +25,4 @@ "Vvval\\Binary\\Tests\\": "tests" } } -} \ No newline at end of file +} diff --git a/phpunit.xml b/phpunit.xml index 94dc2bd..7d079ed 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,18 +1,12 @@ + stopOnError="false"> ./tests/ - \ No newline at end of file + diff --git a/src/Binary.php b/src/Binary.php new file mode 100644 index 0000000..70cecda --- /dev/null +++ b/src/Binary.php @@ -0,0 +1,34 @@ +handler = $handler; + } + + public function finish(): void + { + unset($this->handler); + } + + /** + * @return iterable + * @throws ReadDataException + */ + public function readAll(): iterable + { + while (true) { + $line = $this->readLine(); + if ($line === null) { + $this->finish(); + return; + } + + yield $line; + } + } + + /** + * @throws ReadDataException + */ + public function readLine(): ?string + { + return $this->read(); + } + + public function __destruct() + { + $this->finish(); + } + + /** + * @throws ReadDataException + */ + private function read(): ?string + { + if (empty($this->handler)) { + throw ReadDataException::handlerAlreadyClosed($this->filename); + } + + $length = $this->handler->read(self::FORMAT_LENGTH); + if (empty($length)) { + return null; + } + + if (mb_strlen($length) !== self::FORMAT_LENGTH) { + throw ReadDataException::dataCorrupted($this->filename); + } + + $data = unpack(self::FORMAT . 'len', $length); + $line = $this->handler->read((int)$data['len']); + + return $line !== false ? $line : null; + } +} diff --git a/src/Binary/Writer.php b/src/Binary/Writer.php new file mode 100644 index 0000000..acb6129 --- /dev/null +++ b/src/Binary/Writer.php @@ -0,0 +1,50 @@ +handler = $handler; + } + + public function finish(): void + { + unset($this->handler); + } + + public function writeLine(string $data): int|false + { + if (empty($this->handler)) { + throw WriteDataException::handlerAlreadyClosed($this->filename); + } + + $length = pack(self::FORMAT, mb_strlen($data)); + + return $this->handler->write($length, $data); + } + + /** + * @throws BinaryException + */ + public function __destruct() + { + $this->finish(); + } +} diff --git a/src/BinaryGenerator.php b/src/BinaryGenerator.php deleted file mode 100644 index 5ae94c5..0000000 --- a/src/BinaryGenerator.php +++ /dev/null @@ -1,207 +0,0 @@ -handler)) { - throw new HandlerException("Binary generator has already been started, please finish it before next usage."); - } - - $this->mode = $mode; - $this->filename = $filename; - $this->compression = $compression; - - $this->handler = $this->open(); - - if (empty($this->handler)) { - throw new HandlerException("Error during opening \"$this->filename\" file."); - } - } - - /** - * Finish process. - */ - public function finish() - { - if (!empty($this->handler)) { - $this->close(); - $this->handler = null; - } - } - - /** - * Write part of data into file. - * - * @param string $data - * @return int - * @throws HandlerException - * @throws WriteDataException - */ - public function writeData($data) - { - if (!$this->isWriteMode()) { - throw new WriteDataException("Unable to write data into \"$this->filename\" file, read mode is set."); - } - - if (empty($this->handler)) { - throw new HandlerException("Unable to write data into \"$this->filename\" file, no resource is available."); - } - - $packedLength = pack('L', mb_strlen($data)); - - return $this->write($packedLength, $data); - } - - /** - * Write part of data. - * - * @return null|string - * @throws HandlerException - * @throws ReadDataException - */ - public function readData() - { - if (!$this->isReadMode()) { - throw new ReadDataException("Unable to read data from \"$this->filename\" file, write mode is set."); - } - - if (empty($this->handler)) { - throw new HandlerException("Unable to read data from \"$this->filename\" file, no resource is available."); - } - - $length = $this->read(4); - - if (empty($length)) { - return null; - } - - if (mb_strlen($length) != 4) { - throw new ReadDataException("Unable to read data from \"$this->filename\" file, data is corrupted."); - } - - $length = unpack('L', $length); - - return $this->read((int)$length); - } - - /** - * Open file and get handler. - * - * @return resource - */ - private function open() - { - if (!empty($this->compression)) { - return gzopen($this->filename, $this->mode); - } else { - return fopen($this->filename, $this->mode); - } - } - - /** - * Close handler file. - */ - private function close() - { - if (!empty($this->compression)) { - gzclose($this->handler); - } else { - fclose($this->handler); - } - } - - /** - * Read piece of data with the defined length. - * - * @param int $length - * @return string - */ - private function read($length) - { - if (!empty($this->compression)) { - return gzread($this->handler, $length); - } else { - return fread($this->handler, $length); - } - } - - /** - * Write data into file. - * - * @param int $length - * @param string $data - * @return int - */ - private function write($length, $data) - { - if (!empty($this->compression)) { - return gzwrite($this->handler, $length . $data); - } else { - return fwrite($this->handler, $length . $data); - } - } - - /** - * If current file in read mode. - * - * @return bool - */ - private function isReadMode() - { - return $this->mode === self::READ; - } - - /** - * If current file in write mode. - * - * @return bool - */ - private function isWriteMode() - { - return in_array($this->mode, [self::WRITE, self::APPEND]); - } - - /** - * Check if file was closed. - */ - public function __destruct() - { - if (!empty($this->handler)) { - throw new HandlerException("Binary generator hasn't finished \"$this->filename\" file yet."); - } - } -} \ No newline at end of file diff --git a/src/Exceptions/BinaryException.php b/src/Exceptions/BinaryException.php new file mode 100644 index 0000000..82891e2 --- /dev/null +++ b/src/Exceptions/BinaryException.php @@ -0,0 +1,15 @@ +getMessage(), $e->getCode(), $e); + } +} diff --git a/src/Exceptions/ClosedHandlerException.php b/src/Exceptions/ClosedHandlerException.php new file mode 100644 index 0000000..7d51564 --- /dev/null +++ b/src/Exceptions/ClosedHandlerException.php @@ -0,0 +1,13 @@ +status = Status::PENDING; + } + + public function read(int $length): string|false + { + try { + $this->open(); + } catch (ClosedHandlerException) { + throw ReadDataException::handlerAlreadyClosed($this->filename); + } catch (OpenHandlerException $e) { + throw ReadDataException::wrap($e); + } + + return $this->compression + ? gzread($this->handler, $length) + : fread($this->handler, $length); + } + + public function write(string $length, string $data): int|false + { + try { + $this->open(); + } catch (ClosedHandlerException) { + throw WriteDataException::handlerAlreadyClosed($this->filename); + } catch (OpenHandlerException $e) { + throw WriteDataException::wrap($e); + } + + return $this->compression + ? gzwrite($this->handler, $length . $data) + : fwrite($this->handler, $length . $data); + } + + public function __destruct() + { + $this->close(); + } + + private function open(): void + { + if ($this->status === Status::CLOSED) { + throw new ClosedHandlerException($this->filename); + } + + if ($this->status === Status::OPENED) { + return; + } + + $handler = $this->compression + ? gzopen($this->filename, $this->mode->value) + : fopen($this->filename, $this->mode->value); + + if (!$handler) { + throw new OpenHandlerException($this->filename); + } + + $this->handler = $handler; + $this->status = Status::OPENED; + } + + private function close(): void + { + if ($this->status === Status::CLOSED) { + return; + } + + if ($this->status === Status::OPENED) { + if (!empty($this->compression)) { + gzclose($this->handler); + } else { + fclose($this->handler); + } + } + + $this->handler = null; + $this->status = Status::CLOSED; + } +} diff --git a/src/Handler/HandlerInterface.php b/src/Handler/HandlerInterface.php new file mode 100644 index 0000000..28cb96f --- /dev/null +++ b/src/Handler/HandlerInterface.php @@ -0,0 +1,9 @@ +assertSame('hello', $reader->readLine()); + $this->assertSame('world', $reader->readLine()); + $this->assertNull($reader->readLine()); + } + + public function testReadAllLines(): void + { + $reader = Binary::reader(self::FILENAME); + $lines = [...$reader->readAll()]; + + $this->assertSame(['hello', 'world'], $lines); + } + + public function testCantReadClosedFile(): void + { + $this->expectException(ReadDataException::class); - $binary->start($this->writeFile, BinaryGenerator::WRITE); - $binary->writeData('some data.1'); - $binary->writeData('some data.2'); - $binary->finish(); + $reader = Binary::reader(self::FILENAME); + $reader->finish(); + $reader->readLine(); + } + + public function testWriteSingleLine(): void + { + $writer = Binary::writer(self::FILENAME); + $writer->writeLine('world'); + $writer->finish(); + + $reader = Binary::reader(self::FILENAME); + + $this->assertSame('world', $reader->readLine()); + $this->assertNull($reader->readLine()); + } + + public function testAppendSingleLine(): void + { + $writer = Binary::appender(self::FILENAME); + $writer->writeLine('!'); + $writer->finish(); + + $reader = Binary::reader(self::FILENAME); + + $this->assertSame('hello', $reader->readLine()); + $this->assertSame('world', $reader->readLine()); + $this->assertSame('!', $reader->readLine()); + $this->assertNull($reader->readLine()); + } + + + public function testCantWriteToClosedFile(): void + { + $this->expectException(WriteDataException::class); - $this->assertFileExists($this->writeFile); + $writer = Binary::writer(self::FILENAME); + $writer->finish(); + $writer->writeLine('!'); } -} \ No newline at end of file +} diff --git a/tests/assets/.empty b/tests/assets/.empty deleted file mode 100644 index e69de29..0000000 diff --git a/tests/assets/stub.txt b/tests/assets/stub.txt new file mode 100644 index 0000000..a3176ca Binary files /dev/null and b/tests/assets/stub.txt differ