Let Icinga\Util\File extend from SplFileObject

We should extend SplFileObject as it provides already some of the
functionality Icinga\Util\File had and adds even more on top of that.
This commit is contained in:
Johannes Meyer 2014-06-26 15:53:14 +02:00
parent a91bbd3a75
commit 3b191d36c4
2 changed files with 128 additions and 128 deletions

View File

@ -4,176 +4,147 @@
namespace Icinga\Util;
use Exception;
use Icinga\Exception\ProgrammingError;
use SplFileObject;
use RuntimeException;
use Icinga\Exception\NotWritableError;
/**
* File
*
* A class to ease opening files and reading/writing to them.
*/
class File
class File extends SplFileObject
{
/**
* The location of the file
* The mode used to open the file
*
* @var string
*/
protected $path;
protected $openMode;
/**
* The file resource
*
* @var resource
* @see SplFileObject::__construct()
*/
protected $handle;
/**
* The file access mode to set
*
* Gets set/updated after the file has been closed.
*
* @var int
*/
protected $accessMode;
/**
* Open a file
*
* @param string $path The location of the file
* @param int $openMode The open mode to use
*/
protected function __construct($path, $openMode)
public function __construct($filename, $openMode = 'r', $useIncludePath = false, $context = null)
{
$this->setupErrorHandler();
$this->handle = fopen($path, $openMode);
$this->resetErrorHandler();
}
/**
* Open a file
*
* @param string $path The location of the file
* @param int $openMode The open mode to use
*
* @return File
*/
public static function open($path, $openMode = 'r')
{
return new static($path, $openMode);
}
/**
* Read contents of file
*
* @param int|null $length Read up to $length bytes or until EOF if null
*
* @return string
*/
public function read($length = null)
{
$this->setupErrorHandler();
$content = stream_get_contents($this->handle, $length !== null ? $length : -1);
$this->resetErrorHandler();
return $content;
}
/**
* Read file line by line
*
* @return array
*/
public function readlines()
{
$lines = array();
while (($line = fgets($this->handle)) !== false) {
$lines[] = $line;
$this->openMode = $openMode;
if ($context === null) {
parent::__construct($filename, $openMode, $useIncludePath);
} else {
parent::__construct($filename, $openMode, $useIncludePath, $context);
}
return $lines;
}
/**
* Write contents to file
*
* @param string $bytes The contents to write
*
* @return self
*/
public function write($bytes)
{
$this->setupErrorHandler();
$written = 0;
while ($bytes) {
$justWritten = fwrite($this->handle, $bytes);
if ($justWritten === 0) {
throw new Exception('Failed to write to open file.');
}
$bytes = substr($bytes, $justWritten);
$written += $justWritten;
}
$this->resetErrorHandler();
return $this;
}
/**
* Change access mode of file
*
* Note that the access mode cannot be changed until the file is still open.
* Create a file with an access mode
*
* @param string $path The path to the file
* @param int $accessMode The access mode to set
*
* @return self
* @throws RuntimeException In case the file cannot be created or the access mode cannot be set
*/
public function chmod($accessMode)
public static function create($path, $accessMode)
{
$this->accessMode = $accessMode;
return $this;
}
/**
* Close the file
*
* @throws ProgrammingError In case the file is already closed or its resource became invalid
*/
public function close()
{
if (!is_resource($this->handle)) {
throw new ProgrammingError('Tried to close an invalid file resource or an already closed file');
if (!@touch($path)) {
throw new RuntimeException('Cannot create file "' . $path . '" with acces mode "' . $accessMode . '"');
}
fclose($this->handle);
if ($this->accessMode !== null) {
$this->setupErrorHandler();
chmod($this->path, $this->accessMode);
$this->resetErrorHandler();
if (!@chmod($path, $accessMode)) {
throw new RuntimeException('Cannot set access mode "' . $accessMode . '" on file "' . $path . '"');
}
}
/**
* Setup an error handler that throws an exception for every emitted E_WARNING
* @see SplFileObject::fwrite()
*/
public function fwrite($str, $length = null)
{
$this->assertOpenForWriting();
$this->setupErrorHandler();
$retVal = $length === null ? parent::fwrite($str) : parent::fwrite($str, $length);
restore_error_handler();
return $retVal;
}
/**
* @see SplFileObject::ftruncate()
*/
public function ftruncate($size)
{
$this->assertOpenForWriting();
$this->setupErrorHandler();
$retVal = parent::ftruncate($size);
restore_error_handler();
return $retVal;
}
/**
* @see SplFileObject::ftell()
*/
public function ftell()
{
$this->setupErrorHandler();
$retVal = parent::ftell();
restore_error_handler();
return $retVal;
}
/**
* @see SplFileObject::flock()
*/
public function flock($operation, &$wouldblock = null)
{
$this->setupErrorHandler();
$retVal = parent::flock($operation, $wouldblock);
restore_error_handler();
return $retVal;
}
/**
* @see SplFileObject::fgetc()
*/
public function fgetc()
{
$this->setupErrorHandler();
$retVal = parent::fgetc();
restore_error_handler();
return $retVal;
}
/**
* @see SplFileObject::fflush()
*/
public function fflush()
{
$this->setupErrorHandler();
$retVal = parent::fflush();
restore_error_handler();
return $retVal;
}
/**
* Setup an error handler that throws a RuntimeException for every emitted E_WARNING
*/
protected function setupErrorHandler()
{
set_error_handler(
function ($errno, $errstr) {
restore_error_handler(); // Should we call resetErrorHandler() here? (Requires it to be public)
throw new Exception($errstr);
restore_error_handler();
throw new RuntimeException($errstr);
},
E_WARNING
);
}
/**
* Reset the error handler to the system default
* Assert that the file was opened for writing and throw an exception otherwise
*
* @throws NotWritableError In case the file was not opened for writing
*/
protected function resetErrorHandler()
protected function assertOpenForWriting()
{
restore_error_handler();
if (!preg_match('@w|a|\+@', $this->openMode)) {
throw new NotWritableError('File not open for writing');
}
}
}

View File

@ -0,0 +1,29 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\Util;
use Icinga\Util\File;
use Icinga\Test\BaseTestCase;
class FileTest extends BaseTestCase
{
/**
* @expectedException \Icinga\Exception\NotWritableError
*/
public function testWhetherWritingToNonWritableFilesThrowsAnException()
{
$file = new File('/dev/null');
$file->fwrite('test');
}
/**
* @expectedException \Icinga\Exception\NotWritableError
*/
public function testWhetherTruncatingNonWritableFilesThrowsAnException()
{
$file = new File('/dev/null');
$file->ftruncate(0);
}
}