From 1dd20b3478754fc1352b8d59905aa7ebcc515328 Mon Sep 17 00:00:00 2001 From: Maxime Veber Date: Thu, 20 Dec 2018 19:10:12 +0100 Subject: [PATCH] Add temporary file & directory helper classes These classes are completely different from other tools of this library. That's why they are moved to a new namespace called "utils". --- README.md | 75 +++++++++- src/Utils/Exception/LogicException.php | 16 +++ .../Exception/NeklandExceptionInterface.php | 15 ++ src/Utils/Exception/RuntimeException.php | 16 +++ .../Exception/CannotCreateFileException.php | 17 +++ .../ImpossibleToCreateDirectoryException.php | 21 +++ .../ImpossibleToUpdateFileException.php | 17 +++ .../Exception/InvalidArgumentException.php | 18 +++ .../TemporaryFileAlreadyRemovedException.php | 22 +++ src/Utils/Tempfile/TemporaryDirectory.php | 115 ++++++++++++++++ src/Utils/Tempfile/TemporaryFile.php | 128 ++++++++++++++++++ .../Utils/Tempfile/TemporaryDirectoryTest.php | 39 ++++++ .../Utils/Tempfile/TemporaryFileTest.php | 30 ++++ 13 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 src/Utils/Exception/LogicException.php create mode 100644 src/Utils/Exception/NeklandExceptionInterface.php create mode 100644 src/Utils/Exception/RuntimeException.php create mode 100644 src/Utils/Tempfile/Exception/CannotCreateFileException.php create mode 100644 src/Utils/Tempfile/Exception/ImpossibleToCreateDirectoryException.php create mode 100644 src/Utils/Tempfile/Exception/ImpossibleToUpdateFileException.php create mode 100644 src/Utils/Tempfile/Exception/InvalidArgumentException.php create mode 100644 src/Utils/Tempfile/Exception/TemporaryFileAlreadyRemovedException.php create mode 100644 src/Utils/Tempfile/TemporaryDirectory.php create mode 100644 src/Utils/Tempfile/TemporaryFile.php create mode 100644 tests/Nekland/Utils/Tempfile/TemporaryDirectoryTest.php create mode 100644 tests/Nekland/Utils/Tempfile/TemporaryFileTest.php diff --git a/README.md b/README.md index 282e3da..8339974 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Nekland Tools [![Build Status](https://travis-ci.org/Nekland/Tools.svg?branch=master)](https://travis-ci.org/Nekland/Tools) -Just some classes helping to code with PHP in general. +Just some classes helping to code with PHP in general. No dependencies. High quality code. **This repository follows semver.** @@ -24,6 +24,8 @@ Here is the list of tools it provides: - [ArrayTools](#arraytools-class) - [EqualableInterface](#equalableinterface) - [DateTimeComparator](#datetimecomparator-class) +- [TemporaryFile](#temporary-file-management) +- [TemporaryDirectory](#temporary-directory-management) ### StringTools class @@ -149,3 +151,74 @@ DateTimeComparator::lowest($dateTime1, $dateTime2) : ?\DateTimeInterface * `$dateTime1` The first \DateTimeInterface or null * `$dateTime2` The second \DateTimeInterface or null + +### Temporary file management + +The class `TemporaryFile` helps you to create temporary file with ease. + +#### ::__construct() + +```php +TemporaryFile::__construct(TemporaryDirectory $dir = null, string $prefix = '') +``` + +**Examples:** + +```php +// Create a file in temporary folder +$file = new TemporaryFile(); + +// Create a file inside a temporary directory (see TemporaryDirectory class) +$file = new TemporaryFile($temporaryDir); + +// Create a file in a temporary folder with php_ prefix. +$file = new TemporaryFile(null, 'php_'); +``` + +#### ::setContent & ::getContent + +```php +TemporaryFile::setContent(string $content) +TemporaryFile::getContent(): string +``` + +#### ::remove() + +Removes the file from filesystem. + +```php +TemporaryFile::remove() +``` + +### Temporary directory management + +#### ::__construct() + +```php +TemporaryDirectory::__construct(string $dir = null, string $prefix = 'phpgenerated') +``` + +**Examples:** + +```php +// create a new directory +$directory = new TemporaryDirectory(); +``` + +#### ::getTemporaryFile() + +Create a `TemporaryFile` from the directory generated. + +```php +TemporaryDirectory::getTemporaryFile(): TemporaryFile +``` + +#### ::remove() + +Removes the directory. + +```php +TemporaryDirectory::remove(bool $force = false): void +``` + +_If `force` is specified to true, it will remove the directory even if it has files inside._ diff --git a/src/Utils/Exception/LogicException.php b/src/Utils/Exception/LogicException.php new file mode 100644 index 0000000..7937ab2 --- /dev/null +++ b/src/Utils/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Exception; + +class LogicException extends \LogicException implements NeklandExceptionInterface +{ + +} diff --git a/src/Utils/Exception/NeklandExceptionInterface.php b/src/Utils/Exception/NeklandExceptionInterface.php new file mode 100644 index 0000000..cfd94db --- /dev/null +++ b/src/Utils/Exception/NeklandExceptionInterface.php @@ -0,0 +1,15 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Exception; + +interface NeklandExceptionInterface +{ +} diff --git a/src/Utils/Exception/RuntimeException.php b/src/Utils/Exception/RuntimeException.php new file mode 100644 index 0000000..be6b14b --- /dev/null +++ b/src/Utils/Exception/RuntimeException.php @@ -0,0 +1,16 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Exception; + +class RuntimeException extends \RuntimeException implements NeklandExceptionInterface +{ + +} diff --git a/src/Utils/Tempfile/Exception/CannotCreateFileException.php b/src/Utils/Tempfile/Exception/CannotCreateFileException.php new file mode 100644 index 0000000..c39880f --- /dev/null +++ b/src/Utils/Tempfile/Exception/CannotCreateFileException.php @@ -0,0 +1,17 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Tempfile\Exception; + +use Nekland\Utils\Exception\RuntimeException; + +class CannotCreateFileException extends RuntimeException +{ +} diff --git a/src/Utils/Tempfile/Exception/ImpossibleToCreateDirectoryException.php b/src/Utils/Tempfile/Exception/ImpossibleToCreateDirectoryException.php new file mode 100644 index 0000000..0f90db1 --- /dev/null +++ b/src/Utils/Tempfile/Exception/ImpossibleToCreateDirectoryException.php @@ -0,0 +1,21 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Tempfile\Exception; + +use Nekland\Utils\Exception\RuntimeException; + +class ImpossibleToCreateDirectoryException extends RuntimeException +{ + public function __construct($directory = "", $code = 0, $previous = null) + { + parent::__construct(sprintf('Impossible to create directory "%s".', $directory), $code, $previous); + } +} diff --git a/src/Utils/Tempfile/Exception/ImpossibleToUpdateFileException.php b/src/Utils/Tempfile/Exception/ImpossibleToUpdateFileException.php new file mode 100644 index 0000000..4644c66 --- /dev/null +++ b/src/Utils/Tempfile/Exception/ImpossibleToUpdateFileException.php @@ -0,0 +1,17 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Tempfile\Exception; + +use Nekland\Utils\Exception\RuntimeException; + +class ImpossibleToUpdateFileException extends RuntimeException +{ +} diff --git a/src/Utils/Tempfile/Exception/InvalidArgumentException.php b/src/Utils/Tempfile/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..3120e6c --- /dev/null +++ b/src/Utils/Tempfile/Exception/InvalidArgumentException.php @@ -0,0 +1,18 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Tempfile\Exception; + +use Nekland\Utils\Exception\LogicException; + +class InvalidArgumentException extends LogicException +{ + +} diff --git a/src/Utils/Tempfile/Exception/TemporaryFileAlreadyRemovedException.php b/src/Utils/Tempfile/Exception/TemporaryFileAlreadyRemovedException.php new file mode 100644 index 0000000..1bbb637 --- /dev/null +++ b/src/Utils/Tempfile/Exception/TemporaryFileAlreadyRemovedException.php @@ -0,0 +1,22 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Tempfile\Exception; + +use Nekland\Utils\Exception\LogicException; + +class TemporaryFileAlreadyRemovedException extends LogicException +{ + public function __construct($path = "", $code = 0, $previous = null) + { + $message = \sprintf('The file "%s" has already been removed.', $path); + parent::__construct($message, $code, $previous); + } +} diff --git a/src/Utils/Tempfile/TemporaryDirectory.php b/src/Utils/Tempfile/TemporaryDirectory.php new file mode 100644 index 0000000..ebf64f4 --- /dev/null +++ b/src/Utils/Tempfile/TemporaryDirectory.php @@ -0,0 +1,115 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Tempfile; + +use Nekland\Utils\Tempfile\Exception\ImpossibleToCreateDirectoryException; + +class TemporaryDirectory +{ + /** @var bool */ + private $wasAlreadyExisting; + + /** @var string */ + private $dir; + + /** @var bool */ + private $removed; + + /** + * TemporaryDirectory constructor. + * + * @param null|string $dir + * @param string $prefix + */ + public function __construct($dir = null, $prefix = 'phpgenerated') + { + $this->removed = false; + $this->wasAlreadyExisting = false; + if (null !== $dir && \is_dir($dir)) { + $this->wasAlreadyExisting = true; + $this->dir = $dir; + } + + if (null === $this->dir) { + $this->dir = \sys_get_temp_dir() . '/' . $prefix . '_' . \uniqid(); + } + + if (\mkdir($this->dir) === false) { + throw new ImpossibleToCreateDirectoryException($this->dir); + } + } + + /** + * Generates a TemporaryFile from the current TemporaryDirectory + * + * @return TemporaryFile + */ + public function getTemporaryFile() + { + return $this->files[] = new TemporaryFile($this); + } + + /** + * @return string + */ + public function getPathname() + { + return $this->dir; + } + + /** + * Removes directory. In case the directory contains files, it will not be removed. + * Unless you specify $force to true. + * + * @param bool $force + */ + public function remove($force = false) + { + $isEmpty = $this->isEmpty(); + if ($force && !$isEmpty) { + $it = new \RecursiveDirectoryIterator($this->dir, \RecursiveDirectoryIterator::SKIP_DOTS); + $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST); + foreach($files as $file) { + if ($file->isDir()){ + \rmdir($file->getRealPath()); + } else { + \unlink($file->getRealPath()); + } + } + + \rmdir($this->dir); + $this->removed = true; + + return; + } + + if ($isEmpty) { + \rmdir($this->dir); + $this->removed = true; + } + } + + /** + * @return bool + */ + public function hasBeenRemoved() + { + return $this->removed; + } + + /** + * @return bool + */ + public function isEmpty() + { + return !(new \FilesystemIterator($this->dir))->valid(); + } +} diff --git a/src/Utils/Tempfile/TemporaryFile.php b/src/Utils/Tempfile/TemporaryFile.php new file mode 100644 index 0000000..6f5d7a0 --- /dev/null +++ b/src/Utils/Tempfile/TemporaryFile.php @@ -0,0 +1,128 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\Utils\Tempfile; + +use Nekland\Utils\Exception\RuntimeException; +use Nekland\Utils\Tempfile\Exception\CannotCreateFileException; +use Nekland\Utils\Tempfile\Exception\ImpossibleToUpdateFileException; +use Nekland\Utils\Tempfile\Exception\TemporaryFileAlreadyRemovedException; + +class TemporaryFile +{ + /** + * Tmp dir path. Do not use this var directly, uses `getTmpDirectory`. + * @var TemporaryDirectory + */ + private static $tmpDirectory; + + /** @var string */ + private $file; + + /** @var TemporaryDirectory */ + private $tmpDir; + + /** @var bool */ + private $removed; + + public function __construct(TemporaryDirectory $dir = null, $prefix = '') + { + $this->removed = false; + $this->tmpDir = $dir; + + if (null === $this->tmpDir) { + $this->tmpDir = self::getTmpDirectory(); + } + $path = $this->tmpDir->getPathname() . DIRECTORY_SEPARATOR . $prefix . \uniqid(); + + if (\touch($path) === false) { + throw new CannotCreateFileException(\sprintf( + 'Impossible to write new file in "%s".', + $path + )); + } + + $this->file = $path; + } + + /** + * @return string + */ + public function getPathname() + { + return $this->file; + } + + /** + * @param string $content + */ + public function setContent($content) + { + if ($this->removed) { + throw new TemporaryFileAlreadyRemovedException($this->file); + } + if (!\file_put_contents($this->file, $content)) { + throw new ImpossibleToUpdateFileException(sprintf( + 'Impossible to put contents in file "%s"', + $this->file + )); + } + } + + /** + * @return bool + */ + public function hasBeenRemoved() + { + return $this->removed; + } + + /** + * @return string + */ + public function getContent() + { + $content = \file_get_contents($this->file); + + if ($content === false) { + throw new RuntimeException(sprintf( + 'Impossible to retrieve content of file "%s".', + $this->file + )); + } + + return $content; + } + + /** + * Removes the temporary file. Please consider unset the object instead. + */ + public function remove() + { + if ($this->removed) { + return; + } + + if (!\unlink($this->file)) { + throw new RuntimeException(sprintf('Impossible to remove file "%s"', $this->file)); + } + + $this->removed = true; + } + + private static function getTmpDirectory() + { + if (null === self::$tmpDirectory) { + return self::$tmpDirectory = new TemporaryDirectory(); + } + + return self::$tmpDirectory; + } +} diff --git a/tests/Nekland/Utils/Tempfile/TemporaryDirectoryTest.php b/tests/Nekland/Utils/Tempfile/TemporaryDirectoryTest.php new file mode 100644 index 0000000..4f68c08 --- /dev/null +++ b/tests/Nekland/Utils/Tempfile/TemporaryDirectoryTest.php @@ -0,0 +1,39 @@ +assertTrue(file_exists($directory->getPathname())); + } + + public function testItGeneratesTemporaryFile() + { + $directory = new TemporaryDirectory(); + $this->assertInstanceOf(TemporaryFile::class, $directory->getTemporaryFile()); + $this->assertFalse($directory->isEmpty()); + } + + public function testItDoesNotRemoveDirectoryIfFileInside() + { + $directory = new TemporaryDirectory(); + $this->assertInstanceOf(TemporaryFile::class, $directory->getTemporaryFile()); + $directory->remove(); + $this->assertFalse($directory->hasBeenRemoved()); + } + + public function testItRemoveNonEmptyDirectoryIfIForceIt() + { + $directory = new TemporaryDirectory(); + $directory->getTemporaryFile(); + $directory->remove(true); + $this->assertTrue($directory->hasBeenRemoved()); + } +} diff --git a/tests/Nekland/Utils/Tempfile/TemporaryFileTest.php b/tests/Nekland/Utils/Tempfile/TemporaryFileTest.php new file mode 100644 index 0000000..a1d038e --- /dev/null +++ b/tests/Nekland/Utils/Tempfile/TemporaryFileTest.php @@ -0,0 +1,30 @@ +assertFileExists($file->getPathname()); + } + + public function testItRemovesFile() + { + $file = new TemporaryFile(); + $file->remove(); + + $this->assertTrue($file->hasBeenRemoved()); + } + + public function testItSetContentsAndRetrieveIt() + { + $file = new TemporaryFile(); + $file->setContent('foobar'); + $this->assertEquals('foobar', $file->getContent()); + } +}