diff --git a/common/framework/storage.php b/common/framework/storage.php index 897cc8729..daee9dd9e 100644 --- a/common/framework/storage.php +++ b/common/framework/storage.php @@ -148,17 +148,27 @@ class Storage * Get the content of a file. * * This method returns the content if it exists and is readable. - * Otherwise, it returns false. + * If $stream is true, it will return the content as a stream instead of + * loading the entire content in memory. This may be useful for large files. + * If the file cannot be opened, this method returns false. * * @param string $filename - * @return string|false + * @param bool $stream (optional) + * @return string|resource|false */ - public static function read($filename) + public static function read($filename, $stream = false) { $filename = rtrim($filename, '/\\'); if (self::exists($filename) && @is_file($filename) && @is_readable($filename)) { - return @file_get_contents($filename); + if ($stream) + { + return @fopen($filename, 'r'); + } + else + { + return @file_get_contents($filename); + } } else { @@ -190,10 +200,12 @@ class Storage /** * Write $content to a file. * + * If $content is a stream, this method will copy it to the target file + * without loading the entire content in memory. This may be useful for large files. * This method returns true on success and false on failure. * * @param string $filename - * @param string $content + * @param string|resource $content * @param string $mode (optional) * @param int $perms (optional) * @return string|false @@ -211,9 +223,26 @@ class Storage } } - $flags = ($mode === 'a') ? (\FILE_APPEND | \LOCK_EX) : (\LOCK_EX); - $write_success = @file_put_contents($filename, $content, $flags); - $result = ($write_success === strlen($content)) ? true : false; + if ($fp = fopen($filename, $mode)) + { + flock($fp, \LOCK_EX); + if (is_resource($content)) + { + $result = stream_copy_to_stream($content, $fp) ? true : false; + } + else + { + $result = fwrite($fp, $content) ? true : false; + } + fflush($fp); + flock($fp, \LOCK_UN); + fclose($fp); + } + else + { + return false; + } + @chmod($filename, ($perms === null ? (0666 & ~umask()) : $perms)); if (function_exists('opcache_invalidate') && substr($filename, -4) === '.php') { diff --git a/tests/unit/framework/StorageTest.php b/tests/unit/framework/StorageTest.php index 9cc69fd4b..83bbe8429 100644 --- a/tests/unit/framework/StorageTest.php +++ b/tests/unit/framework/StorageTest.php @@ -116,20 +116,54 @@ class StorageTest extends \Codeception\TestCase\Test public function testRead() { + // Simple read test $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('/dev/nonexistent')); + + // Stream read test + $fp = Rhymix\Framework\Storage::read(__FILE__, true); + $this->assertTrue(is_resource($fp)); + $this->assertEquals(file_get_contents(__FILE__), fread($fp, filesize(__FILE__))); + fclose($fp); } public function testWrite() { $testfile = \RX_BASEDIR . 'tests/_output/subdirectory/testfile.txt'; + $copyfile = \RX_BASEDIR . 'tests/_output/subdirectory/copyfile.txt'; - $this->assertFalse(file_exists($testfile)); + // Simple write test $this->assertTrue(Rhymix\Framework\Storage::write($testfile, 'foobarbazzjazz')); $this->assertTrue(file_exists($testfile)); $this->assertEquals('foobarbazzjazz', file_get_contents($testfile)); + $this->assertEquals(0666 & ~umask(), fileperms($testfile) & 0777); + + // Append test + $this->assertTrue(Rhymix\Framework\Storage::write($testfile, 'rhymix', 'a', 0666)); + $this->assertTrue(file_exists($testfile)); + $this->assertEquals('foobarbazzjazzrhymix', file_get_contents($testfile)); + $this->assertEquals(0666, fileperms($testfile) & 0777); + + // Stream copy test + $stream = fopen($testfile, 'r'); + $this->assertTrue(Rhymix\Framework\Storage::write($copyfile, $stream)); + $this->assertEquals('foobarbazzjazzrhymix', file_get_contents($copyfile)); + fclose($stream); + + // Stream append test + $stream = fopen($testfile, 'r'); + $this->assertTrue(Rhymix\Framework\Storage::write($copyfile, $stream, 'a')); + $this->assertEquals('foobarbazzjazzrhymixfoobarbazzjazzrhymix', file_get_contents($copyfile)); + fclose($stream); + + // Partial stream append test + $stream = fopen($testfile, 'r'); + fseek($stream, 14); + $this->assertTrue(Rhymix\Framework\Storage::write($copyfile, $stream, 'a')); + $this->assertEquals('foobarbazzjazzrhymixfoobarbazzjazzrhymixrhymix', file_get_contents($copyfile)); + fclose($stream); } public function testReadWritePHPData()