<?php
/**
* This file is part of the PHP Generics package.
*
* @package Generics
*/
namespace Generics\Streams;
use Generics\FileExistsException;
use Generics\NoAccessException;
use Generics\LockException;
use Generics\Lockable;
use Generics\ResetException;
use Exception;
/**
* This class provides a file output stream.
*
* @author Maik
*/
class FileOutputStream implements OutputStream, Lockable
{
/**
* The file handle
*
* @var resource
*/
private $handle;
/**
* The absolute file path and name
*
* @var string
*/
private $fileName;
/**
* Whether resource is locked
*
* @var bool
*/
private $locked;
/**
* Whether to append
*
* @var bool
*/
private $append;
/**
* Create a new FileOutputStream
*
* @param string $file
* The absolute (or relative) path to file to write into.
* @param boolean $append
* Whether to append the data to an existing file.
* @throws FileExistsException will be thrown in case of file exists and append is set to false.
* @throws NoAccessException will be thrown in case of it is not possible to write to file.
*/
public function __construct($file, $append = false)
{
$this->open($file, $append);
}
private function open($file, $append)
{
$this->locked = false;
$mode = "wb";
if (file_exists($file)) {
if (! $append) {
throw new FileExistsException("File $file already exists!");
}
if (! is_writable($file)) {
throw new NoAccessException("Cannot write to file $file");
}
$mode = "ab";
} else {
if (! is_writable(dirname($file))) {
throw new NoAccessException("Cannot write to file {file}", array(
'file' => $file
));
}
}
$this->handle = fopen($file, $mode);
if (! $this->ready()) {
throw new StreamException("Could not open {file} for writing", array(
'file' => $file
));
}
$this->fileName = $file;
$this->append = $append;
}
/**
* Cleanup (e.g.
* release lock)
*/
public function __destruct()
{
try {
if ($this->locked) {
$this->unlock();
}
} catch (\Generics\GenericsException $ex) {
// Do nothing
}
}
/**
*
* {@inheritdoc}
* @see \Generics\Streams\Stream::ready()
*/
public function ready(): bool
{
return is_resource($this->handle);
}
/**
*
* {@inheritdoc}
* @see \Generics\Streams\OutputStream::write()
*/
public function write($buffer)
{
if (! $this->ready()) {
throw new StreamException("Stream is not open!");
}
if ($buffer instanceof InputStream) {
$in = clone $buffer;
assert($in instanceof InputStream);
while ($in->ready()) {
$buf = $in->read(1024);
if (fwrite($this->handle, $buf) != strlen($buf)) {
throw new StreamException("Could not write memory stream into file");
}
}
} else {
$buffer = strval($buffer);
if (fwrite($this->handle, $buffer) != strlen($buffer)) {
throw new StreamException("Could not write buffer into file");
}
fflush($this->handle);
}
}
/**
*
* {@inheritdoc}
* @see \Generics\Streams\Stream::close()
*/
public function close()
{
if (is_resource($this->handle)) {
fclose($this->handle);
$this->handle = null;
}
}
/**
*
* {@inheritdoc}
* @see \Countable::count()
*/
public function count(): int
{
if (! $this->ready()) {
throw new StreamException("Stream is not open!");
}
return ftell($this->handle);
}
/**
* Retrieve the file path and name
*
* @return string
*/
public function __toString(): string
{
return realpath($this->fileName);
}
/**
*
* {@inheritdoc}
* @see \Generics\Streams\OutputStream::isWriteable()
*/
public function isWriteable(): bool
{
return $this->ready();
}
/**
*
* {@inheritdoc}
* @see \Generics\Streams\OutputStream::flush()
*/
public function flush()
{
if (! $this->ready()) {
throw new StreamException("Stream is not open!");
}
fflush($this->handle);
}
/**
*
* {@inheritdoc}
* @see \Generics\Lockable::lock()
*/
public function lock()
{
if ($this->locked || flock($this->handle, LOCK_EX) === false) {
throw new LockException("Could not acquire lock");
}
$this->locked = true;
}
/**
*
* {@inheritdoc}
* @see \Generics\Lockable::unlock()
*/
public function unlock()
{
if (! $this->locked || flock($this->handle, LOCK_UN) === false) {
throw new LockException("Could not release lock");
}
$this->locked = false;
}
/**
*
* {@inheritdoc}
* @see \Generics\Lockable::isLocked()
*/
public function isLocked(): bool
{
return $this->locked;
}
/**
*
* {@inheritdoc}
* @see \Generics\Streams\Stream::isOpen()
*/
public function isOpen(): bool
{
return is_resource($this->handle);
}
/**
*
* {@inheritdoc}
* @see \Generics\Resettable::reset()
*/
public function reset()
{
try {
$this->close();
$this->open($this->fileName, $this->append);
} catch (Exception $ex) {
throw new ResetException($ex->getMessage(), array(), $ex->getCode(), $ex);
}
}
}
|