From 8f2beade53ca43a3835ef3717922109b4ac615ae Mon Sep 17 00:00:00 2001 From: Frantisek Sitner Date: Sun, 17 Sep 2017 21:38:46 +0200 Subject: [PATCH] Initial commit --- .gitignore | 6 + LICENSE.md | 12 + README.md | 12 + composer.json | 29 + composer.lock | 1333 +++++++++++++++++++ src/AtomicFileHandler.php | 297 +++++ src/AtomicFileReader.php | 128 ++ src/AtomicFileWriter.php | 154 +++ src/Exceptions/AtomicFileException.php | 42 + src/Exceptions/FileLockException.php | 40 + src/Exceptions/FileOperationException.php | 89 ++ src/Exceptions/NonExistentFileException.php | 28 + src/Exceptions/NotReadableFileException.php | 27 + src/Exceptions/NotWritableFileException.php | 27 + src/Exceptions/ReadOnlyFileException.php | 28 + src/Exceptions/WriteOnlyFileException.php | 28 + src/IAtomicFileHandler.php | 86 ++ src/IAtomicFileReader.php | 44 + src/IAtomicFileWriter.php | 45 + tests/AtomicFileHandlerTest.php | 194 +++ tests/AtomicFileReaderTest.php | 99 ++ tests/AtomicFileWriterTest.php | 69 + tests/ParallelRunningTest.php | 69 + tests/bootstrap.php | 20 + tests/phpunit.xml | 10 + 25 files changed, 2916 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/AtomicFileHandler.php create mode 100644 src/AtomicFileReader.php create mode 100644 src/AtomicFileWriter.php create mode 100644 src/Exceptions/AtomicFileException.php create mode 100644 src/Exceptions/FileLockException.php create mode 100644 src/Exceptions/FileOperationException.php create mode 100644 src/Exceptions/NonExistentFileException.php create mode 100644 src/Exceptions/NotReadableFileException.php create mode 100644 src/Exceptions/NotWritableFileException.php create mode 100644 src/Exceptions/ReadOnlyFileException.php create mode 100644 src/Exceptions/WriteOnlyFileException.php create mode 100644 src/IAtomicFileHandler.php create mode 100644 src/IAtomicFileReader.php create mode 100644 src/IAtomicFileWriter.php create mode 100644 tests/AtomicFileHandlerTest.php create mode 100644 tests/AtomicFileReaderTest.php create mode 100644 tests/AtomicFileWriterTest.php create mode 100644 tests/ParallelRunningTest.php create mode 100644 tests/bootstrap.php create mode 100644 tests/phpunit.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2df2e20 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Created by .ignore support plugin (hsz.mobi) + +.idea +log.php +index.php +vendor \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b7657d8 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,12 @@ +Copyright (c) 2017, František Šitner +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..344f3c6 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# AtomicFile: Atomic operations # + +AtomicFile is package for atomic operations with file. It guarantees nobody will overwrite your file, while you are writing into/reading file. + +```php +$file_path = dirname(__FILE__).'/../tmp/test.txt'; +$reader = new PHPComponent\AtomicFile\AtomicFileReader($file_path); //create instance of Reader +print_r($reader->readFile()); //read file +``` + +## Important notice ## +It works only when other writers/readers use same class, function flock() cannot lock file for others functions then flock(). diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2c4f3de --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "php-component/atomic-file", + "description": "Library for atomic operations with files", + "minimum-stability": "stable", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "František Šitner", + "email": "frantisek.sitner@gmail.com", + "role": "Developer" + } + ], + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.5" + }, + "autoload": { + "psr-4": { + "PHPComponent\\AtomicFile\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PHPComponent\\DI\\Tests\\": "tests/" + } + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..30913ef --- /dev/null +++ b/composer.lock @@ -0,0 +1,1333 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "5d5fe69cb568aefb1235e3a0516f1702", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2017-07-22T11:58:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-04-12T18:52:22+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.1.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2", + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-08-30T18:51:59+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.7.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8 || ^5.6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2017-09-04T11:05:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2016-10-03T07:40:28+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0", + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-08-20T05:47:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.21", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3b91adfb64264ddec5a2dee9851f354aa66327db", + "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0.3|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-06-21T08:11:54+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.3.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "1d8c2a99c80862bdc3af94c1781bf70f86bccac0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/1d8c2a99c80862bdc3af94c1781bf70f86bccac0", + "reference": "1d8c2a99c80862bdc3af94c1781bf70f86bccac0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-07-29T21:54:42+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", + "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-11-23T20:04:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.5" + }, + "platform-dev": [] +} diff --git a/src/AtomicFileHandler.php b/src/AtomicFileHandler.php new file mode 100644 index 0000000..087ccf9 --- /dev/null +++ b/src/AtomicFileHandler.php @@ -0,0 +1,297 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile; + +use PHPComponent\AtomicFile\Exceptions\AtomicFileException; +use PHPComponent\AtomicFile\Exceptions\FileLockException; +use PHPComponent\AtomicFile\Exceptions\FileOperationException; +use PHPComponent\AtomicFile\Exceptions\NonExistentFileException; +use PHPComponent\AtomicFile\Exceptions\NotReadableFileException; +use PHPComponent\AtomicFile\Exceptions\NotWritableFileException; + +/** + * @author František Šitner + */ +abstract class AtomicFileHandler implements IAtomicFileHandler +{ + + /** @var string */ + private $file_path; + + /** @var resource */ + private $file; + + /** @var int */ + private $lock_retries; + + /** @var int */ + private $retry_interval; + + /** + * AtomicFileHandler constructor. + * @param string $file_path + * @param int $lock_retries + * @param int $retry_interval + * @throws \InvalidArgumentException + */ + public function __construct($file_path, $lock_retries = self::LOCK_RETRIES, $retry_interval = self::RETRY_INTERVAL) + { + if(!is_string($file_path)) + { + throw new \InvalidArgumentException('File path must be string instead of '.gettype($file_path)); + } + if(($file_path = trim($file_path)) === '') + { + throw new \InvalidArgumentException('File path must not be empty'); + } + $this->file_path = $file_path; + $this->lock_retries = $lock_retries; + $this->retry_interval = $retry_interval; + } + + /** + * Rewind the file + * @return $this + * @throws AtomicFileException + */ + public function rewindFile() + { + $this->openFile(); + if(!rewind($this->getFile())) + { + throw FileOperationException::createForFailedToRewindFile($this->getFilePath()); + } + return $this; + } + + /** + * @param int $offset + * @param int $whence + * @return $this + * @throws AtomicFileException + */ + public function seekFile($offset, $whence = SEEK_SET) + { + $this->openFile(); + $seek = fseek($this->getFile(), $offset, $whence); + if($seek === -1) + { + throw FileOperationException::createForFailedToSeekFile($this->getFilePath()); + } + return $this; + } + + /** + * @return $this + * @throws AtomicFileException + */ + public function seekFileToEnd() + { + $this->seekFile(0, SEEK_END); + return $this; + } + + /** + * @return bool + * @throws AtomicFileException + */ + public function isEndOfFile() + { + $this->openFile(); + return fgetc($this->getFile()) === false; + } + + /** + * Return actual pointer of file resource + * @return int + * @throws AtomicFileException + */ + public function getFilePointer() + { + $this->openFile(); + return ftell($this->getFile()); + } + + /** + * @throws AtomicFileException + */ + public function closeFile() + { + if(!$this->isFileOpened()) return; + $this->unlockFile(); + $close = fclose($this->file); + if(!$close) + { + throw FileOperationException::createForFailedToCloseFile($this->getFilePath()); + } + } + + /** + * Find whether file is empty + * @return bool + * @throws AtomicFileException + */ + public function isFileEmpty() + { + $this->openFile(); + $saved_pointer = $this->getFilePointer(); + $this->rewindFile()->seekFileToEnd(); + $size_pointer = $this->getFilePointer(); + $this->seekFile($saved_pointer); + + return $size_pointer === 0; + } + + /** + * @return int + */ + public function getFileSize() + { + $this->openFile(); + $stats = fstat($this->file); + return $stats['size']; + } + + /** + * Find whether file is opened + * @return bool + */ + protected function isFileOpened() + { + return is_resource($this->file); + } + + /** + * @param int $lock_type + * @param int $lock_retries + * @param int $retry_interval + * @return bool + */ + protected function tryLockFile($lock_type, $lock_retries, $retry_interval) + { + for($i = 0; $i < $lock_retries; $i++) + { + if(!flock($this->file, $lock_type | LOCK_NB)) + { + if($i === ($lock_retries - 1)) return false; + usleep($retry_interval); + } + else + { + break; + } + } + + return true; + } + + /** + * @throws AtomicFileException + */ + protected function unlockFile() + { + $unlock = flock($this->file, LOCK_UN); + if(!$unlock) + { + throw FileLockException::createForFailedToUnlockFile($this->getFilePath()); + } + } + + /** + * Checks the file whether exists and is readable or writable + * @param bool $writable + * @throws AtomicFileException + */ + protected function checkFile($writable = true) + { + if(!$this->fileExists()) throw NonExistentFileException::createForFileDoesNotExists($this->getFilePath()); + if(!is_readable($this->file_path)) throw NotReadableFileException::createForNotReadable($this->getFilePath()); + if($writable) + { + if(!is_writable($this->file_path)) throw NotWritableFileException::createForNotWritable($this->getFilePath()); + } + } + + /** + * Checks whether file exists + * @return bool + */ + protected function fileExists() + { + $this->clearStatCache(true); + return (file_exists($this->file_path) && is_file($this->file_path)); + } + + /** + * Clear cache + * @param bool $all_files If true, it clears all files + */ + protected function clearStatCache($all_files = false) + { + if(version_compare(PHP_VERSION, '5.3.0') >= 0) + { + if($all_files === true) + { + clearstatcache(true); + return; + } + + clearstatcache(true, $this->file_path); + return; + } + clearstatcache(); + } + + public function __destruct() + { + $this->closeFile(); + } + + /** + * @return string + */ + public function getFilePath() + { + return $this->file_path; + } + + /** + * @return resource + */ + protected function getFile() + { + return $this->file; + } + + /** + * @param resource $file + */ + protected function setFile($file) + { + $this->file = $file; + } + + /** + * @return int + */ + public function getLockRetries() + { + return $this->lock_retries; + } + + /** + * @return int + */ + public function getRetryInterval() + { + return $this->retry_interval; + } +} \ No newline at end of file diff --git a/src/AtomicFileReader.php b/src/AtomicFileReader.php new file mode 100644 index 0000000..644b8e6 --- /dev/null +++ b/src/AtomicFileReader.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile; + +use PHPComponent\AtomicFile\Exceptions\AtomicFileException; +use PHPComponent\AtomicFile\Exceptions\FileLockException; +use PHPComponent\AtomicFile\Exceptions\FileOperationException; +use PHPComponent\AtomicFile\Exceptions\ReadOnlyFileException; + +/** + * @author František Šitner + */ +class AtomicFileReader extends AtomicFileHandler implements IAtomicFileReader +{ + + const OPEN_READ_ONLY = true; + const OPEN_NOT_READ_ONLY = false; + + /** @var bool */ + private $read_only; + + /** + * @param string $file_path + * @param bool $read_only + * @param int $lock_retries + * @param int $retry_interval + * @throws \InvalidArgumentException + */ + public function __construct($file_path, $read_only = self::OPEN_NOT_READ_ONLY, $lock_retries = self::LOCK_RETRIES, $retry_interval = self::RETRY_INTERVAL) + { + $this->read_only = $read_only; + parent::__construct($file_path, $lock_retries, $retry_interval); + } + + /** + * @return $this + * @throws AtomicFileException + */ + public function openFile() + { + if($this->isFileOpened()) return $this; + $this->checkFile(false); + $this->setFile(fopen($this->getFilePath(), $this->getOpenMode())); + if(!is_resource($this->getFile())) + { + throw FileOperationException::createForFailedToOpenFile($this->getFilePath()); + } + if(!$this->tryLockFile(LOCK_SH, $this->getLockRetries(), $this->getRetryInterval())) + { + throw FileLockException::createForFailedToLockFile($this->getFilePath()); + } + return $this; + } + + /** + * Read the file + * @return string + * @throws FileOperationException + */ + public function readFile() + { + $this->openFile(); + $length = filesize($this->getFilePath()); + if($length === 0) return ''; + $read = fread($this->getFile(), $length); + if($read === false) + { + throw FileOperationException::createForFailedToReadFromFile($this->getFilePath()); + } + return $read; + } + + /** + * Read line from file + * @param null|int $length + * @return bool|string + */ + public function readFileLine($length = null) + { + $this->openFile(); + //gets warning "fgets(): Length parameter must be greater than 0" when parameter is null and is passed to function + if($length === null) + { + $line = fgets($this->getFile()); + } + else + { + $line = fgets($this->getFile(), $length); + } + + return $line; + } + + /** + * Return writer + * @param int $lock_retries + * @param int $retry_interval + * @return IAtomicFileWriter + * @throws ReadOnlyFileException + */ + public function getWriter($lock_retries = self::LOCK_RETRIES, $retry_interval = self::RETRY_INTERVAL) + { + if($this->read_only === self::OPEN_READ_ONLY) + { + throw ReadOnlyFileException::createForReadOnly($this->getFilePath()); + } + $this->openFile(); + $writer = new AtomicFileWriter($this->getFilePath(), false, $lock_retries, $retry_interval); + $this->closeFile(); + return $writer->openFile(); + } + + /** + * @return string + */ + protected function getOpenMode() + { + return 'rb'; + } +} \ No newline at end of file diff --git a/src/AtomicFileWriter.php b/src/AtomicFileWriter.php new file mode 100644 index 0000000..83277f0 --- /dev/null +++ b/src/AtomicFileWriter.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile; + +use PHPComponent\AtomicFile\Exceptions\FileLockException; +use PHPComponent\AtomicFile\Exceptions\FileOperationException; +use PHPComponent\AtomicFile\Exceptions\WriteOnlyFileException; + +/** + * @author František Šitner + */ +class AtomicFileWriter extends AtomicFileHandler implements IAtomicFileWriter +{ + + const OPEN_WRITE_ONLY = true; + const OPEN_NOT_WRITE_ONLY = false; + + /** @var bool */ + private $create_non_existent; + + /** @var bool */ + private $write_only; + + /** + * @param string $file_path + * @param bool $create_non_existent + * @param bool $write_only + * @param int $lock_retries + * @param int $retry_interval + * @throws \InvalidArgumentException + */ + public function __construct( + $file_path, + $create_non_existent = false, + $write_only = self::OPEN_NOT_WRITE_ONLY, + $lock_retries = self::LOCK_RETRIES, + $retry_interval = self::RETRY_INTERVAL + ) + { + $this->create_non_existent = $create_non_existent; + $this->write_only = $write_only; + parent::__construct($file_path, $lock_retries, $retry_interval); + } + + /** + * @return $this + * @throws FileLockException + * @throws FileOperationException + */ + public function openFile() + { + if($this->isFileOpened()) return $this; + if(!$this->isCreateNonExistent()) + { + $this->checkFile(true); + } + + $this->setFile(fopen($this->getFilePath(), $this->getOpenMode())); + if(!is_resource($this->getFile())) + { + throw FileOperationException::createForFailedToOpenFile($this->getFilePath()); + } + if(!$this->tryLockFile(LOCK_EX, $this->getLockRetries(), $this->getRetryInterval())) + { + throw FileLockException::createForFailedToLockFile($this->getFilePath()); + } + return $this; + } + + /** + * @param string|int|bool $text + * @return int + * @throws FileOperationException + * @throws \InvalidArgumentException + */ + public function writeToFile($text) + { + if(!is_scalar($text)) throw new \InvalidArgumentException('Text must be scalar instead of '.gettype($text)); + $this->openFile(); + $write = fwrite($this->getFile(), $text); + if(!$write) + { + throw FileOperationException::createForFailedToWriteToFile($this->getFilePath()); + } + return $write; + } + + /** + * @param int $truncate_size + * @return $this + * @throws FileOperationException + */ + public function truncateFile($truncate_size = 0) + { + $this->openFile(); + $truncate = ftruncate($this->getFile(), $truncate_size); + if(!$truncate) + { + throw FileOperationException::createForFailedToTruncateFile($this->getFilePath()); + } + return $this; + } + + /** + * Return reader + * @param int $lock_retries + * @param int $retry_interval + * @return IAtomicFileReader + * @throws WriteOnlyFileException + */ + public function getReader($lock_retries = self::LOCK_RETRIES, $retry_interval = self::RETRY_INTERVAL) + { + if($this->isWriteOnly()) + { + throw WriteOnlyFileException::createForWriteOnly($this->getFilePath()); + } + $this->openFile(); + $reader = new AtomicFileReader($this->getFilePath(), $lock_retries, $retry_interval); + $this->closeFile(); + return $reader->openFile(); + } + + /** + * @return bool + */ + protected function isCreateNonExistent() + { + return $this->create_non_existent; + } + + /** + * @return bool + */ + protected function isWriteOnly() + { + return $this->write_only; + } + + /** + * @return string + */ + protected function getOpenMode() + { + return 'cb'; + } +} \ No newline at end of file diff --git a/src/Exceptions/AtomicFileException.php b/src/Exceptions/AtomicFileException.php new file mode 100644 index 0000000..b9a7c4b --- /dev/null +++ b/src/Exceptions/AtomicFileException.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Exceptions; + +/** + * @author František Šitner + */ +abstract class AtomicFileException extends \Exception +{ + + /** @var string */ + private $file_path; + + /** + * AtomicFileException constructor. + * @param string $file_path + * @param string $message + * @param int $code + * @param \Exception|null $previous + */ + public function __construct($file_path, $message = "", $code = 0, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); + $this->file_path = $file_path; + } + + /** + * @return string + */ + public function getFilePath() + { + return $this->file_path; + } +} diff --git a/src/Exceptions/FileLockException.php b/src/Exceptions/FileLockException.php new file mode 100644 index 0000000..d7ec0be --- /dev/null +++ b/src/Exceptions/FileLockException.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Exceptions; + +/** + * Thrown when failed to lock or unlock file + * @author František Šitner + */ +class FileLockException extends AtomicFileException +{ + + const LOCK_FILE = 1; + const UNLOCK_FILE = 2; + + /** + * @param string $file_path + * @return FileLockException + */ + public static function createForFailedToLockFile($file_path) + { + return new self($file_path, 'Failed to lock file', self::LOCK_FILE); + } + + /** + * @param string $file_path + * @return FileLockException + */ + public static function createForFailedToUnlockFile($file_path) + { + return new self($file_path, 'Failed to unlock file', self::UNLOCK_FILE); + } +} \ No newline at end of file diff --git a/src/Exceptions/FileOperationException.php b/src/Exceptions/FileOperationException.php new file mode 100644 index 0000000..09aadd4 --- /dev/null +++ b/src/Exceptions/FileOperationException.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Exceptions; + +/** + * @author František Šitner + */ +class FileOperationException extends AtomicFileException +{ + + const OPERATION_OPEN_FILE = 1; + const OPERATION_CLOSE_FILE = 2; + const OPERATION_WRITE_TO_FILE = 3; + const OPERATION_READ_FROM_FILE = 4; + const OPERATION_SEEK_FILE = 5; + const OPERATION_REWIND_FILE = 6; + const OPERATION_TRUNCATE_FILE = 7; + + /** + * @param string $file_path + * @return FileOperationException + */ + public static function createForFailedToRewindFile($file_path) + { + return new self($file_path, 'Failed to rewind file', self::OPERATION_REWIND_FILE); + } + + /** + * @param string $file_path + * @return FileOperationException + */ + public static function createForFailedToSeekFile($file_path) + { + return new self($file_path, 'Failed to seek file', self::OPERATION_SEEK_FILE); + } + + /** + * @param string $file_path + * @return FileOperationException + */ + public static function createForFailedToOpenFile($file_path) + { + return new self($file_path, 'Failed to open file', self::OPERATION_OPEN_FILE); + } + + /** + * @param string $file_path + * @return FileOperationException + */ + public static function createForFailedToCloseFile($file_path) + { + return new self($file_path, 'Failed to close file', self::OPERATION_CLOSE_FILE); + } + + /** + * @param string $file_path + * @return FileOperationException + */ + public static function createForFailedToWriteToFile($file_path) + { + return new self($file_path, 'Failed to write to file', self::OPERATION_WRITE_TO_FILE); + } + + /** + * @param string $file_path + * @return FileOperationException + */ + public static function createForFailedToReadFromFile($file_path) + { + return new self($file_path, 'Failed to read from file', self::OPERATION_READ_FROM_FILE); + } + + /** + * @param string $file_path + * @return FileOperationException + */ + public static function createForFailedToTruncateFile($file_path) + { + return new self($file_path, 'Failed to truncate file', self::OPERATION_TRUNCATE_FILE); + } +} \ No newline at end of file diff --git a/src/Exceptions/NonExistentFileException.php b/src/Exceptions/NonExistentFileException.php new file mode 100644 index 0000000..393d439 --- /dev/null +++ b/src/Exceptions/NonExistentFileException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Exceptions; + +/** + * Thrown when file does not exists + * @author František Šitner + */ +class NonExistentFileException extends AtomicFileException +{ + + /** + * @param string $file_path + * @return NonExistentFileException + */ + public static function createForFileDoesNotExists($file_path) + { + return new self($file_path, 'File does not exists'); + } +} \ No newline at end of file diff --git a/src/Exceptions/NotReadableFileException.php b/src/Exceptions/NotReadableFileException.php new file mode 100644 index 0000000..0bf0c8a --- /dev/null +++ b/src/Exceptions/NotReadableFileException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Exceptions; + +/** + * @author František Šitner + */ +class NotReadableFileException extends AtomicFileException +{ + + /** + * @param string $file_path + * @return NotReadableFileException + */ + public static function createForNotReadable($file_path) + { + return new self($file_path, 'File is note readable'); + } +} \ No newline at end of file diff --git a/src/Exceptions/NotWritableFileException.php b/src/Exceptions/NotWritableFileException.php new file mode 100644 index 0000000..b6398b7 --- /dev/null +++ b/src/Exceptions/NotWritableFileException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Exceptions; + +/** + * @author František Šitner + */ +class NotWritableFileException extends AtomicFileException +{ + + /** + * @param string $file_path + * @return NotWritableFileException + */ + public static function createForNotWritable($file_path) + { + return new self($file_path, 'File is not writable'); + } +} \ No newline at end of file diff --git a/src/Exceptions/ReadOnlyFileException.php b/src/Exceptions/ReadOnlyFileException.php new file mode 100644 index 0000000..f9de622 --- /dev/null +++ b/src/Exceptions/ReadOnlyFileException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Exceptions; + +/** + * Thrown when trying to write to read-only file + * @author František Šitner + */ +class ReadOnlyFileException extends AtomicFileException +{ + + /** + * @param string $file_path + * @return ReadOnlyFileException + */ + public static function createForReadOnly($file_path) + { + return new self($file_path, 'File is opened only for reading'); + } +} \ No newline at end of file diff --git a/src/Exceptions/WriteOnlyFileException.php b/src/Exceptions/WriteOnlyFileException.php new file mode 100644 index 0000000..dcea9b5 --- /dev/null +++ b/src/Exceptions/WriteOnlyFileException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Exceptions; + +/** + * Thrown when trying to read from write-only file + * @author František Šitner + */ +class WriteOnlyFileException extends AtomicFileException +{ + + /** + * @param string $file_path + * @return WriteOnlyFileException + */ + public static function createForWriteOnly($file_path) + { + return new self($file_path, 'File is opened only for writing'); + } +} \ No newline at end of file diff --git a/src/IAtomicFileHandler.php b/src/IAtomicFileHandler.php new file mode 100644 index 0000000..e2b4a33 --- /dev/null +++ b/src/IAtomicFileHandler.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile; + +use PHPComponent\AtomicFile\Exceptions\AtomicFileException; + +/** + * @author František Šitner + */ +interface IAtomicFileHandler +{ + + //Default number of times to try to close the file + const LOCK_RETRIES = 50; + + //Default sleep time after unsuccessful close + const RETRY_INTERVAL = 100; + + /** + * Opens file + * @return $this + */ + public function openFile(); + + /** + * Rewind the file + * @return $this + * @throws AtomicFileException + */ + public function rewindFile(); + + /** + * Move file pointer to desired position + * @param int $offset + * @param int $whence + * @return $this + * @throws AtomicFileException + */ + public function seekFile($offset, $whence = SEEK_SET); + + /** + * @return $this + * @throws AtomicFileException + */ + public function seekFileToEnd(); + + /** + * @return bool + * @throws AtomicFileException + */ + public function isEndOfFile(); + + /** + * Return actual pointer of file resource + * @return int + * @throws AtomicFileException + */ + public function getFilePointer(); + + /** + * @return void + * @throws AtomicFileException + */ + public function closeFile(); + + /** + * Find whether file is empty + * @return bool + * @throws AtomicFileException + */ + public function isFileEmpty(); + + /** + * Returns actual file size + * @return int + */ + public function getFileSize(); +} \ No newline at end of file diff --git a/src/IAtomicFileReader.php b/src/IAtomicFileReader.php new file mode 100644 index 0000000..59c6b44 --- /dev/null +++ b/src/IAtomicFileReader.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile; + +use PHPComponent\AtomicFile\Exceptions\FileOperationException; +use PHPComponent\AtomicFile\Exceptions\ReadOnlyFileException; + +/** + * @author František Šitner + */ +interface IAtomicFileReader extends IAtomicFileHandler +{ + + /** + * Read the file + * @return string + * @throws FileOperationException + */ + public function readFile(); + + /** + * Read line from file + * @param null|int $length + * @return bool|string + */ + public function readFileLine($length = null); + + /** + * Return writer + * @param int $lock_retries + * @param int $retry_interval + * @return IAtomicFileWriter + * @throws ReadOnlyFileException + */ + public function getWriter($lock_retries = self::LOCK_RETRIES, $retry_interval = self::RETRY_INTERVAL); +} \ No newline at end of file diff --git a/src/IAtomicFileWriter.php b/src/IAtomicFileWriter.php new file mode 100644 index 0000000..fc45107 --- /dev/null +++ b/src/IAtomicFileWriter.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile; + +use PHPComponent\AtomicFile\Exceptions\FileOperationException; +use PHPComponent\AtomicFile\Exceptions\WriteOnlyFileException; + +/** + * @author František Šitner + */ +interface IAtomicFileWriter extends IAtomicFileHandler +{ + + /** + * @param string|int|bool $text + * @return int + * @throws FileOperationException + * @throws \InvalidArgumentException + */ + public function writeToFile($text); + + /** + * @param int $truncate_size + * @return $this + * @throws FileOperationException + */ + public function truncateFile($truncate_size = 0); + + /** + * Return reader + * @param int $lock_retries + * @param int $retry_interval + * @return IAtomicFileReader + * @throws WriteOnlyFileException + */ + public function getReader($lock_retries = self::LOCK_RETRIES, $retry_interval = self::RETRY_INTERVAL); +} \ No newline at end of file diff --git a/tests/AtomicFileHandlerTest.php b/tests/AtomicFileHandlerTest.php new file mode 100644 index 0000000..b623f07 --- /dev/null +++ b/tests/AtomicFileHandlerTest.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Test; + +use PHPComponent\AtomicFile\AtomicFileHandler; + +/** + * @author František Šitner + */ +class AtomicFileHandlerTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @dataProvider getInvalidFilePath + * @param $file_path + * @expectedException \InvalidArgumentException + */ + public function testInvalidArguments($file_path) + { + /** @var \PHPUnit_Framework_MockObject_MockObject|AtomicFileHandler $mock */ + $mock = $this->getMockForAbstractClass( + AtomicFileHandler::class, + array($file_path) + ); + } + + public function getInvalidFilePath() + { + return array( + array(''), + array(false), + array(true), + array(0), + array(12.3), + array(null), + ); + } + + public function testRewindFile() + { + /** @var \PHPUnit_Framework_MockObject_MockObject|AtomicFileHandler $mock */ + $mock = $this->getMockForAbstractClass( + AtomicFileHandler::class, + array($this->getFilePath()) + ); + + $this->createTestFile($file); + + $mock->expects($this->at(0)) + ->method('openFile') + ->willReturn($mock); + + $this->invokeSetFile($mock, $file); + + $this->assertSame(11, $mock->getFilePointer()); + $this->assertSame($mock, $mock->rewindFile()); + $this->assertSame(0, $mock->getFilePointer()); + $mock->closeFile(); + } + + public function testIsFileEmpty() + { + /** @var \PHPUnit_Framework_MockObject_MockObject|AtomicFileHandler $mock */ + $mock = $this->getMockForAbstractClass( + AtomicFileHandler::class, + array($this->getFilePath()) + ); + + $this->createTestFile($file, ''); + + $mock->expects($this->at(0)) + ->method('openFile') + ->willReturn($mock); + + $this->invokeSetFile($mock, $file); + + $this->assertTrue($mock->isFileEmpty()); + $mock->closeFile(); + } + + public function testGetFilePointer() + { + /** @var \PHPUnit_Framework_MockObject_MockObject|AtomicFileHandler $mock */ + $mock = $this->getMockForAbstractClass( + AtomicFileHandler::class, + array($this->getFilePath()) + ); + + $string = 'Test string'; + $this->createTestFile($file, $string); + + $mock->expects($this->at(0)) + ->method('openFile') + ->willReturn($mock); + + $this->invokeSetFile($mock, $file); + + $this->assertSame(strlen($string), $mock->getFilePointer()); + $mock->closeFile(); + } + + public function testSeekFile() + { + /** @var \PHPUnit_Framework_MockObject_MockObject|AtomicFileHandler $mock */ + $mock = $this->getMockForAbstractClass( + AtomicFileHandler::class, + array($this->getFilePath()) + ); + + $string = 'Testing string'; + $this->createTestFile($file, $string); + + $mock->expects($this->any()) + ->method('openFile') + ->willReturn($mock); + + $this->invokeSetFile($mock, $file); + + $this->assertSame($mock, $mock->seekFile(2)); + $this->assertSame(2, $mock->getFilePointer()); + $this->assertSame($mock, $mock->seekFile(2, SEEK_CUR)); + $this->assertSame(4, $mock->getFilePointer()); + $this->assertSame($mock, $mock->seekFile(-3, SEEK_END)); + $this->assertSame(strlen($string) - 3, $mock->getFilePointer()); + $mock->closeFile(); + } + + public function testSeekFileToEnd() + { + /** @var \PHPUnit_Framework_MockObject_MockObject|AtomicFileHandler $mock */ + $mock = $this->getMockForAbstractClass( + AtomicFileHandler::class, + array($this->getFilePath()) + ); + + $string = 'Testing string'; + $this->createTestFile($file, $string); + + $mock->expects($this->any()) + ->method('openFile') + ->willReturn($mock); + + $this->invokeSetFile($mock, $file); + + $this->assertSame($mock, $mock->seekFileToEnd()); + $this->assertSame(strlen($string), $mock->getFilePointer()); + $this->assertTrue($mock->isEndOfFile()); + $mock->closeFile(); + } + + protected function tearDown() + { + @unlink($this->getFilePath()); + } + + /** + * @param resource $file + * @param string $string + * @return string + */ + private function createTestFile(&$file, $string = 'Test string') + { + $file = fopen($this->getFilePath(), 'c+b'); + fwrite($file, $string); + return $string; + } + + /** + * @param $mock + * @param $file + */ + private function invokeSetFile($mock, $file) + { + $method_reflection = new \ReflectionMethod($mock, 'setFile'); + $method_reflection->setAccessible(true); + $method_reflection->invoke($mock, $file); + } + + /** + * @return string + */ + private function getFilePath() + { + return dirname(__FILE__).'/tmp/test.txt'; + } +} \ No newline at end of file diff --git a/tests/AtomicFileReaderTest.php b/tests/AtomicFileReaderTest.php new file mode 100644 index 0000000..6962f69 --- /dev/null +++ b/tests/AtomicFileReaderTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Test; + +use PHPComponent\AtomicFile\AtomicFileReader; +use PHPComponent\AtomicFile\AtomicFileWriter; + +/** + * @author František Šitner + */ +class AtomicFileReaderTest extends \PHPUnit_Framework_TestCase +{ + + public function testReadFile() + { + $string = $this->createTestFile(); + $file_reader = new AtomicFileReader($this->getFilePath()); + $this->assertSame($string, $file_reader->readFile()); + $file_reader->closeFile(); + } + + public function testReadFileLine() + { + $string = "foo\nbar\nbaz"; + $this->createTestFile($string); + $file_reader = new AtomicFileReader($this->getFilePath()); + $this->assertSame("foo\n", $file_reader->readFileLine()); + $this->assertSame("ba", $file_reader->readFileLine(3)); + $this->assertSame("r\n", $file_reader->readFileLine()); + $this->assertSame("baz", $file_reader->readFileLine()); + $file_reader->closeFile(); + } + + public function testGetWriter() + { + $this->createTestFile(); + $file_reader = new AtomicFileReader($this->getFilePath()); + $this->assertInstanceOf(AtomicFileWriter::class, $file_reader->getWriter()); + } + + /** + * @expectedException \PHPComponent\AtomicFile\Exceptions\ReadOnlyFileException + */ + public function testGetWriterWhenReadOnly() + { + $this->createTestFile(); + $file_reader = new AtomicFileReader($this->getFilePath(), AtomicFileReader::OPEN_READ_ONLY); + $file_reader->getWriter(); + } + + /** + * @expectedException \PHPComponent\AtomicFile\Exceptions\NonExistentFileException + */ + public function testOpenNonExistentFile() + { + $file_reader = new AtomicFileReader(dirname(__FILE__).'/../tmp/test2.txt'); + $file_reader->openFile(); + } + + public function testOpenFile() + { + $this->createTestFile(); + $file_reader = new AtomicFileReader($this->getFilePath()); + $this->assertInstanceOf(AtomicFileReader::class, $file_reader->openFile()); + } + + protected function tearDown() + { + @unlink($this->getFilePath()); + } + + /** + * @param string $string + * @return string + */ + private function createTestFile($string = 'Test string') + { + $file = fopen($this->getFilePath(), 'c+b'); + fwrite($file, $string); + fclose($file); + return $string; + } + + /** + * @return string + */ + private function getFilePath() + { + return dirname(__FILE__).'/tmp/test.txt'; + } +} \ No newline at end of file diff --git a/tests/AtomicFileWriterTest.php b/tests/AtomicFileWriterTest.php new file mode 100644 index 0000000..d82df91 --- /dev/null +++ b/tests/AtomicFileWriterTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Test; + +use PHPComponent\AtomicFile\AtomicFileReader; +use PHPComponent\AtomicFile\AtomicFileWriter; + +/** + * @author František Šitner + */ +class AtomicFileWriterTest extends \PHPUnit_Framework_TestCase +{ + + /** @var AtomicFileWriter */ + private $file_writer; + + public function setUp() + { + $this->file_writer = new AtomicFileWriter(dirname(__FILE__).'/tmp/test.txt', true); + } + + /** + * @expectedException \PHPComponent\AtomicFile\Exceptions\NonExistentFileException + */ + public function testOpenNonExistentFile() + { + $writer = new AtomicFileWriter(dirname(__FILE__).'/../tmp/test.txt'); + $writer->openFile(); + } + + public function testOpenFile() + { + $this->assertInstanceOf(AtomicFileWriter::class, $this->file_writer->openFile()); + } + + public function testWriteToFile() + { + $this->assertSame(5, $this->file_writer->writeToFile('Hello')); + $this->assertSame('Hello', $this->file_writer->getReader()->readFile()); + } + + public function testTruncateFile() + { + $this->assertSame(5, $this->file_writer->writeToFile('Hello')); + $this->assertInstanceOf(AtomicFileWriter::class, $this->file_writer->truncateFile()); + $this->assertSame(0, $this->file_writer->getFileSize()); + $this->assertSame(5, $this->file_writer->writeToFile('Hello')); + $this->assertInstanceOf(AtomicFileWriter::class, $this->file_writer->truncateFile(3)); + $this->assertSame(3, $this->file_writer->getFileSize()); + } + + public function testGetReader() + { + $this->assertInstanceOf(AtomicFileReader::class, $this->file_writer->getReader()); + } + + protected function tearDown() + { + $this->file_writer->closeFile(); + } +} \ No newline at end of file diff --git a/tests/ParallelRunningTest.php b/tests/ParallelRunningTest.php new file mode 100644 index 0000000..c5a6b50 --- /dev/null +++ b/tests/ParallelRunningTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace PHPComponent\AtomicFile\Test; + +use PHPComponent\AtomicFile\AtomicFileReader; +use PHPComponent\AtomicFile\AtomicFileWriter; + +/** + * @author František Šitner + */ +class ParallelRunningTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @expectedException \PHPComponent\AtomicFile\Exceptions\FileLockException + */ + public function testParallelOpenForWriting() + { + $this->createTestFile(); + $file_reader = new AtomicFileReader($this->getFilePath()); + $file_reader->readFileLine(); + + $file_writer = new AtomicFileWriter($this->getFilePath()); + $file_writer->seekFileToEnd(); + } + + public function testParallelOpenForReading() + { + $testing_string = $this->createTestFile(); + $file_reader = new AtomicFileReader($this->getFilePath()); + $this->assertSame($testing_string, $file_reader->readFileLine()); + + $file_reader_2 = new AtomicFileReader($this->getFilePath()); + $this->assertSame($testing_string, $file_reader_2->readFileLine()); + } + + protected function tearDown() + { + @unlink($this->getFilePath()); + } + + /** + * @param string $string + * @return string + */ + private function createTestFile($string = 'Test string') + { + $file = fopen($this->getFilePath(), 'c+b'); + fwrite($file, $string); + fclose($file); + return $string; + } + + /** + * @return string + */ + private function getFilePath() + { + return dirname(__FILE__).'/tmp/test.txt'; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..c8669ff --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +$dir_name = dirname(__FILE__); +@mkdir($dir_name.'/tmp', 0777, true); +require_once $dir_name.'/../vendor/autoload.php'; + +register_shutdown_function( + function() use($dir_name){ + @unlink($dir_name.'/tmp/test.txt'); + @rmdir($dir_name.'/tmp'); + } +); \ No newline at end of file diff --git a/tests/phpunit.xml b/tests/phpunit.xml new file mode 100644 index 0000000..aae6fcd --- /dev/null +++ b/tests/phpunit.xml @@ -0,0 +1,10 @@ + + + + ./../src + + + \ No newline at end of file