From f718b5a3e4dd02b105f1fd37b7c59e191b33c008 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Fri, 18 Mar 2016 18:11:57 +0900 Subject: [PATCH] Add Storage class and unit tests --- common/framework/storage.php | 471 +++++++++++++++++++++++++++ tests/unit/framework/StorageTest.php | 260 +++++++++++++++ 2 files changed, 731 insertions(+) create mode 100644 tests/unit/framework/StorageTest.php diff --git a/common/framework/storage.php b/common/framework/storage.php index 4fee32b08..e8de90d7b 100644 --- a/common/framework/storage.php +++ b/common/framework/storage.php @@ -7,5 +7,476 @@ namespace Rhymix\Framework; */ class Storage { + /** + * Check if a path really exists. + * + * @param string $path + * @return bool + */ + public static function exists($path) + { + $path = rtrim($path, '/\\'); + clearstatcache(true, $path); + return @file_exists($path); + } + /** + * Check if the given path is a file. + * + * @param string $path + * @return bool + */ + public static function isFile($path) + { + $path = rtrim($path, '/\\'); + return @self::exists($path) && @is_file($path); + } + + /** + * Check if the given path is an empty file. + * + * @param string $path + * @return bool + */ + public static function isEmptyFile($path) + { + $path = rtrim($path, '/\\'); + return @self::exists($path) && @is_file($path) && (@filesize($path) == 0); + } + + + /** + * Check if the given path is a directory. + * + * @param string $path + * @return bool + */ + public static function isDirectory($path) + { + $path = rtrim($path, '/\\'); + return @self::exists($path) && @is_dir($path); + } + + /** + * Check if the given path is an empty directory. + * + * @param string $path + * @return bool + */ + public static function isEmptyDirectory($path) + { + $path = rtrim($path, '/\\'); + if (!self::isDirectory($path)) + { + return false; + } + + $iterator = new \FilesystemIterator($path, \FilesystemIterator::SKIP_DOTS); + return (iterator_count($iterator)) === 0 ? true : false; + } + + /** + * Check if the given path is a symbolic link. + * + * @param string $path + * @return bool + */ + public static function isSymlink($path) + { + $path = rtrim($path, '/\\'); + return @is_link($path); + } + + /** + * Check if the given path is a valid symbolic link. + * + * @param string $path + * @return bool + */ + public static function isValidSymlink($path) + { + $path = rtrim($path, '/\\'); + return @is_link($path) && ($target = @readlink($path)) !== false && self::exists($target); + } + + /** + * Check if the given path is readable. + * + * @param string $path + * @return bool + */ + public static function isReadable($path) + { + $path = rtrim($path, '/\\'); + return @self::exists($path) && @is_readable($path); + } + + /** + * Check if the given path is writable. + * + * @param string $path + * @return bool + */ + public static function isWritable($path) + { + $path = rtrim($path, '/\\'); + return @self::exists($path) && @is_writable($path); + } + + /** + * Get the size of a file. + * + * This method returns the size of a file, or false on error. + * + * @param string $filename + * @return int|false + */ + public static function getSize($filename) + { + $filename = rtrim($filename, '/\\'); + if (self::exists($filename) && @is_file($filename) && @is_readable($filename)) + { + return @filesize($filename); + } + else + { + return false; + } + } + + /** + * Get the content of a file. + * + * This method returns the content if it exists and is readable. + * Otherwise, it returns false. + * + * @param string $filename + * @return string|false + */ + public static function read($filename) + { + $filename = rtrim($filename, '/\\'); + if (self::exists($filename) && @is_file($filename) && @is_readable($filename)) + { + return @file_get_contents($filename); + } + else + { + return false; + } + } + + /** + * Write $content to a file. + * + * This method returns true on success and false on failure. + * + * @param string $filename + * @param string $content + * @param string $mode (optional) + * @param int $perms (optional) + * @return string|false + */ + public static function write($filename, $content, $mode = 'w', $perms = null) + { + $filename = rtrim($filename, '/\\'); + $destination_dir = dirname($filename); + if (!self::exists($destination_dir)) + { + $mkdir_success = self::createDirectory($destination_dir); + if (!$mkdir_success) + { + return false; + } + } + + $flags = ($mode === 'a') ? (\FILE_APPEND | \LOCK_EX) : (\LOCK_EX); + $write_success = @file_put_contents($filename, $content, $flags); + $result = ($write_success === strlen($content)) ? true : false; + @chmod($filename, ($perms === null ? (0666 & ~umask()) : $perms)); + if (function_exists('opcache_invalidate') && substr($filename, -4) === '.php') + { + @opcache_invalidate($filename, true); + } + return $result; + } + + /** + * Copy $source to $destination. + * + * This method returns true on success and false on failure. + * If the destination permissions are not given, they will be copied from the source. + * + * @param string $source + * @param string $destination + * @param int $destination_perms + * @return bool + */ + public static function copy($source, $destination, $destination_perms = null) + { + $source = rtrim($source, '/\\'); + $destination = rtrim($destination, '/\\'); + if (!self::exists($source)) + { + return false; + } + + $destination_dir = dirname($destination); + if (!self::exists($destination_dir) && !self::createDirectory($destination_dir)) + { + return false; + } + elseif (self::isDirectory($destination)) + { + $destination = $destination . '/' . basename($source); + } + + $copy_success = @copy($source, $destination); + if (!$copy_success) + { + return false; + } + + if ($destination_perms === null) + { + @chmod($destination, 0777 & @fileperms($source)); + } + else + { + @chmod($destination, $destination_perms); + } + return true; + } + + /** + * Move $source to $destination. + * + * This method returns true on success and false on failure. + * + * @param string $source + * @param string $destination + * @return bool + */ + public static function move($source, $destination) + { + $source = rtrim($source, '/\\'); + $destination = rtrim($destination, '/\\'); + if (!self::exists($source)) + { + return false; + } + + $destination_dir = dirname($destination); + if (!self::exists($destination_dir) && !self::createDirectory($destination_dir)) + { + return false; + } + elseif (self::isDirectory($destination)) + { + $destination = $destination . '/' . basename($source); + } + + $result = @rename($source, $destination); + if (function_exists('opcache_invalidate') && substr($source, -4) === '.php') + { + @opcache_invalidate($source, true); + } + return $result; + } + + /** + * Delete a file. + * + * This method returns true if the file exists and has been successfully + * deleted, and false on any kind of failure. + * + * @param string $filename + * @return bool + */ + public static function delete($filename) + { + $filename = rtrim($filename, '/\\'); + $result = @self::exists($filename) && @is_file($filename) && @unlink($filename); + if (function_exists('opcache_invalidate') && substr($filename, -4) === '.php') + { + @opcache_invalidate($filename, true); + } + return $result; + } + + /** + * Create a directory. + * + * @param string $dirname + * @return bool + */ + public static function createDirectory($dirname, $mode = null) + { + $dirname = rtrim($dirname, '/\\'); + if ($mode === null) + { + $mode = 0777 & ~umask(); + } + return @mkdir($dirname, $mode, true); + } + + /** + * Read the list of files in a directory. + * + * @param string $dirname + * @param bool $full_path (optional) + * @param bool $skip_dotfiles (optional) + * @param bool $skip_subdirs (optional) + * @return array|false + */ + public static function readDirectory($dirname, $full_path = true, $skip_dotfiles = true, $skip_subdirs = true) + { + $dirname = rtrim($dirname, '/\\'); + if (!self::isDirectory($dirname)) + { + return false; + } + + try + { + $iterator = new \FilesystemIterator($dirname, \FilesystemIterator::CURRENT_AS_PATHNAME); + } + catch (\UnexpectedValueException $e) + { + return false; + } + + $result = array(); + foreach ($iterator as $fileinfo) + { + if (!$skip_subdirs || !is_dir($fileinfo)) + { + $basename = basename($fileinfo); + if (!$skip_dotfiles || $basename[0] !== '.') + { + $result[] = $full_path ? $fileinfo : $basename; + } + } + } + sort($result); + return $result; + } + + /** + * Copy a directory recursively. + * + * @param string $source + * @param string $destination + * @param string $exclude_regexp (optional) + * @return bool + */ + public static function copyDirectory($source, $destination, $exclude_regexp = null) + { + $source = rtrim($source, '/\\'); + $destination = rtrim($destination, '/\\'); + if (!self::isDirectory($source)) + { + return false; + } + if (!self::isDirectory($destination) && !self::createDirectory($destination)) + { + return false; + } + + $rdi_options = \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS; + $rii_options = \RecursiveIteratorIterator::CHILD_FIRST; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, $rdi_options), $rii_options); + + foreach ($iterator as $path) + { + $path_source = $path->getPathname(); + if (strpos($path_source, $source) !== 0) + { + continue; + } + if ($exclude_regexp && preg_match($exclude_regexp, $path_source)) + { + continue; + } + + $path_destination = $destination . substr($path_source, strlen($source)); + if ($path->isDir()) + { + $status = self::isDirectory($path_destination) || self::createDirectory($path_destination, $path->getPerms()); + if (!$status) + { + return false; + } + } + else + { + $status = self::copy($path_source, $path_destination, $path->getPerms()); + if (!$status) + { + return false; + } + } + } + + return true; + } + + /** + * Move a directory. + * + * @param string $source + * @param string $destination + * @return bool + */ + public static function moveDirectory($source, $destination) + { + return self::move($source, $destination); + } + + /** + * Delete a directory recursively. + * + * @param string $dirname + * @param bool $delete_self (optional) + * @return bool + */ + public static function deleteDirectory($dirname, $delete_self = true) + { + $dirname = rtrim($dirname, '/\\'); + if (!self::isDirectory($dirname)) + { + return false; + } + + $rdi_options = \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS; + $rii_options = \RecursiveIteratorIterator::CHILD_FIRST; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dirname, $rdi_options), $rii_options); + + foreach ($iterator as $path) + { + if ($path->isDir()) + { + if (!@rmdir($path->getPathname())) + { + return false; + } + } + else + { + if (!@unlink($path->getPathname())) + { + return false; + } + } + } + + if ($delete_self) + { + return @rmdir($dirname); + } + else + { + return true; + } + } } diff --git a/tests/unit/framework/StorageTest.php b/tests/unit/framework/StorageTest.php new file mode 100644 index 000000000..b2d66e381 --- /dev/null +++ b/tests/unit/framework/StorageTest.php @@ -0,0 +1,260 @@ +assertTrue(Rhymix\Framework\Storage::exists(__FILE__)); + $this->assertTrue(Rhymix\Framework\Storage::exists(__DIR__)); + $this->assertFalse(Rhymix\Framework\Storage::exists(__FILE__ . '.nonexistent.suffix')); + $this->assertFalse(Rhymix\Framework\Storage::exists(__DIR__ . '/nonexistent.subdirectory')); + } + + public function testIsFile() + { + $this->assertTrue(Rhymix\Framework\Storage::isFile(__FILE__)); + $this->assertFalse(Rhymix\Framework\Storage::isFile(__DIR__)); + $this->assertFalse(Rhymix\Framework\Storage::isFile(__FILE__ . '.nonexistent.suffix')); + $this->assertFalse(Rhymix\Framework\Storage::isFile(__DIR__ . '/nonexistent.subdirectory')); + } + + public function testIsEmptyFile() + { + $emptyfile = \RX_BASEDIR . 'tests/_output/emptyfile.txt'; + file_put_contents($emptyfile, ''); + + $this->assertTrue(Rhymix\Framework\Storage::isEmptyFile($emptyfile)); + $this->assertFalse(Rhymix\Framework\Storage::isEmptyFile($emptyfile . '.nonexistent.suffix')); + $this->assertFalse(Rhymix\Framework\Storage::isEmptyFile(__FILE__)); + $this->assertFalse(Rhymix\Framework\Storage::isEmptyFile(__DIR__)); + } + + public function testIsDirectory() + { + $this->assertTrue(Rhymix\Framework\Storage::isDirectory(__DIR__)); + $this->assertFalse(Rhymix\Framework\Storage::isDirectory(__FILE__)); + $this->assertFalse(Rhymix\Framework\Storage::isDirectory(__FILE__ . '.nonexistent.suffix')); + $this->assertFalse(Rhymix\Framework\Storage::isDirectory(__DIR__ . '/nonexistent.subdirectory')); + } + + public function testIsEmptyDirectory() + { + $emptydir = \RX_BASEDIR . 'tests/_output/emptydir'; + mkdir($emptydir); + + $this->assertTrue(Rhymix\Framework\Storage::isEmptyDirectory($emptydir)); + $this->assertFalse(Rhymix\Framework\Storage::isEmptyDirectory($emptydir . '/nonexistent.subdirectory')); + $this->assertFalse(Rhymix\Framework\Storage::isEmptyDirectory(__FILE__)); + $this->assertFalse(Rhymix\Framework\Storage::isEmptyDirectory(__DIR__)); + } + + public function testIsSymlink() + { + if (strncasecmp(\PHP_OS, 'Win', 3) === 0) + { + return; + } + + $symlink_source = \RX_BASEDIR . 'tests/_output/link.source.txt'; + $symlink_target = \RX_BASEDIR . 'tests/_output/link.target.txt'; + file_put_contents($symlink_target, 'foobar'); + symlink($symlink_target, $symlink_source); + + $this->assertTrue(Rhymix\Framework\Storage::isSymlink($symlink_source)); + $this->assertFalse(Rhymix\Framework\Storage::isSymlink($symlink_target)); + unlink($symlink_target); + + $this->assertTrue(Rhymix\Framework\Storage::isSymlink($symlink_source)); + $this->assertFalse(Rhymix\Framework\Storage::isValidSymlink($symlink_source)); + $this->assertFalse(Rhymix\Framework\Storage::isSymlink($symlink_target)); + $this->assertFalse(Rhymix\Framework\Storage::isValidSymlink($symlink_target)); + } + + public function testIsReadable() + { + $this->assertTrue(Rhymix\Framework\Storage::isReadable(__FILE__)); + $this->assertTrue(Rhymix\Framework\Storage::isReadable(__DIR__)); + $this->assertFalse(Rhymix\Framework\Storage::isReadable(__FILE__ . '.nonexistent.suffix')); + $this->assertFalse(Rhymix\Framework\Storage::isReadable('/etc/shadow')); + } + + public function testIsWritable() + { + $testfile = \RX_BASEDIR . 'tests/_output/testfile.txt'; + file_put_contents($testfile, 'foobar'); + + $this->assertTrue(Rhymix\Framework\Storage::isWritable(__FILE__)); + $this->assertTrue(Rhymix\Framework\Storage::isWritable(__DIR__)); + $this->assertTrue(Rhymix\Framework\Storage::isWritable($testfile)); + $this->assertTrue(Rhymix\Framework\Storage::isWritable(dirname($testfile))); + $this->assertFalse(Rhymix\Framework\Storage::isWritable($testfile . '.nonexistent.suffix')); + $this->assertFalse(Rhymix\Framework\Storage::isWritable('/dev/zero')); + } + + public function testGetSize() + { + $this->assertEquals(filesize(__FILE__), Rhymix\Framework\Storage::getSize(__FILE__)); + $this->assertFalse(Rhymix\Framework\Storage::getSize(__DIR__)); + $this->assertFalse(Rhymix\Framework\Storage::getSize(__FILE__ . '.nonexistent.suffix')); + $this->assertFalse(Rhymix\Framework\Storage::getSize('/dev/null')); + } + + public function testRead() + { + $this->assertEquals(file_get_contents(__FILE__), Rhymix\Framework\Storage::read(__FILE__)); + $this->assertFalse(Rhymix\Framework\Storage::read(__FILE__ . '.nonexistent.suffix')); + $this->assertFalse(Rhymix\Framework\Storage::read(__DIR__)); + $this->assertFalse(Rhymix\Framework\Storage::read('/etc/shadow')); + } + + public function testWrite() + { + $testfile = \RX_BASEDIR . 'tests/_output/subdirectory/testfile.txt'; + + $this->assertFalse(file_exists($testfile)); + $this->assertTrue(Rhymix\Framework\Storage::write($testfile, 'foobarbazzjazz')); + $this->assertTrue(file_exists($testfile)); + $this->assertEquals('foobarbazzjazz', file_get_contents($testfile)); + } + + public function testCopy() + { + $source = \RX_BASEDIR . 'tests/_output/copy.source.txt'; + $target = \RX_BASEDIR . 'tests/_output/copy.target.txt'; + file_put_contents($source, 'foobarbaz'); + chmod($source, 0646); + + $this->assertTrue(Rhymix\Framework\Storage::copy($source, $target)); + $this->assertTrue(file_exists($target)); + $this->assertTrue(file_get_contents($target) === 'foobarbaz'); + + if (strncasecmp(\PHP_OS, 'Win', 3) !== 0) + { + $this->assertEquals(0646, fileperms($target) & 0777); + $this->assertTrue(Rhymix\Framework\Storage::copy($source, $target, 0755)); + $this->assertEquals(0755, fileperms($target) & 0777); + } + } + + public function testMove() + { + $source = \RX_BASEDIR . 'tests/_output/move.source.txt'; + $target = \RX_BASEDIR . 'tests/_output/move.target.txt'; + file_put_contents($source, 'foobarbaz'); + + $this->assertTrue(Rhymix\Framework\Storage::move($source, $target)); + $this->assertTrue(file_exists($target)); + $this->assertTrue(file_get_contents($target) === 'foobarbaz'); + $this->assertFalse(file_exists($source)); + } + + public function testDelete() + { + $testfile = \RX_BASEDIR . 'tests/_output/testfile.txt'; + file_put_contents($testfile, 'foobar'); + + $this->assertTrue(Rhymix\Framework\Storage::delete($testfile)); + $this->assertFalse(file_exists($testfile)); + } + + public function testCreateDirectory() + { + $emptydir = \RX_BASEDIR . 'tests/_output/emptydir'; + + $this->assertTrue(Rhymix\Framework\Storage::createDirectory($emptydir)); + $this->assertTrue(file_exists($emptydir) && is_dir($emptydir)); + } + + public function testReadDirectory() + { + $testdir = \RX_BASEDIR . 'tests/_output/testdir'; + mkdir($testdir); + mkdir($testdir . '/subdir'); + file_put_contents($testdir . '/.dotfile', ''); + file_put_contents($testdir . '/foo', 'foo'); + file_put_contents($testdir . '/bar', 'bar'); + file_put_contents($testdir . '/baz', 'baz'); + + $this->assertEquals(array($testdir . '/bar', $testdir . '/baz', $testdir . '/foo'), Rhymix\Framework\Storage::readDirectory($testdir)); + $this->assertEquals(array('bar', 'baz', 'foo'), Rhymix\Framework\Storage::readDirectory($testdir, false)); + $this->assertEquals(array('bar', 'baz', 'foo', 'subdir'), Rhymix\Framework\Storage::readDirectory($testdir, false, true, false)); + $this->assertEquals(array('.dotfile', 'bar', 'baz', 'foo'), Rhymix\Framework\Storage::readDirectory($testdir, false, false, true)); + $this->assertEquals(array('.dotfile', 'bar', 'baz', 'foo', 'subdir'), Rhymix\Framework\Storage::readDirectory($testdir, false, false, false)); + $this->assertFalse(Rhymix\Framework\Storage::readDirectory('/opt/nonexistent.foobar')); + } + + public function testCopyDirectory() + { + $sourcedir = \RX_BASEDIR . 'tests/_output/sourcedir'; + mkdir($sourcedir); + mkdir($sourcedir . '/subdir'); + file_put_contents($sourcedir . '/bar', 'bar'); + file_put_contents($sourcedir . '/subdir/baz', 'baz'); + $targetdir = \RX_BASEDIR . 'tests/_output/targetdir'; + + $this->assertTrue(Rhymix\Framework\Storage::copyDirectory($sourcedir, $targetdir)); + $this->assertTrue(file_exists($targetdir . '/bar')); + $this->assertTrue(file_exists($targetdir . '/subdir/baz')); + $this->assertFalse(Rhymix\Framework\Storage::copyDirectory($sourcedir, '/opt/nonexistent.foobar')); + } + + public function testMoveDirectory() + { + $sourcedir = \RX_BASEDIR . 'tests/_output/sourcedir'; + mkdir($sourcedir); + mkdir($sourcedir . '/subdir'); + file_put_contents($sourcedir . '/bar', 'bar'); + file_put_contents($sourcedir . '/subdir/baz', 'baz'); + $targetdir = \RX_BASEDIR . 'tests/_output/targetdir'; + + $this->assertTrue(Rhymix\Framework\Storage::moveDirectory($sourcedir, $targetdir)); + $this->assertTrue(file_exists($targetdir . '/bar')); + $this->assertTrue(file_exists($targetdir . '/subdir/baz')); + $this->assertFalse(file_exists($sourcedir)); + } + + public function testDeleteDirectory() + { + $sourcedir = \RX_BASEDIR . 'tests/_output/sourcedir'; + mkdir($sourcedir); + mkdir($sourcedir . '/subdir'); + file_put_contents($sourcedir . '/bar', 'bar'); + file_put_contents($sourcedir . '/subdir/baz', 'baz'); + $nonexistent = \RX_BASEDIR . 'tests/_output/targetdir'; + + $this->assertTrue(Rhymix\Framework\Storage::deleteDirectory($sourcedir)); + $this->assertFalse(file_exists($sourcedir . '/subdir/baz')); + $this->assertFalse(file_exists($sourcedir)); + $this->assertFalse(Rhymix\Framework\Storage::deleteDirectory($nonexistent)); + } + + public function testDeleteDirectoryKeepRoot() + { + $sourcedir = \RX_BASEDIR . 'tests/_output/sourcedir'; + mkdir($sourcedir); + mkdir($sourcedir . '/subdir'); + file_put_contents($sourcedir . '/bar', 'bar'); + file_put_contents($sourcedir . '/subdir/baz', 'baz'); + $nonexistent = \RX_BASEDIR . 'tests/_output/targetdir'; + + $this->assertTrue(Rhymix\Framework\Storage::deleteDirectory($sourcedir, false)); + $this->assertFalse(file_exists($sourcedir . '/subdir/baz')); + $this->assertTrue(file_exists($sourcedir)); + $this->assertFalse(Rhymix\Framework\Storage::deleteDirectory($nonexistent)); + } +}