From 4f667f1dda9a696ed2812674ad9dce9900ad4e9e Mon Sep 17 00:00:00 2001 From: PJ Dietz Date: Sun, 26 Apr 2015 20:49:59 -0400 Subject: [PATCH] Streams throw exceptions instead of returning false. --- src/Message/NullStream.php | 32 ++++++---- src/Message/Stream.php | 70 ++++++++++++++++++---- test/tests/unit/Message/NullStreamTest.php | 16 +++-- test/tests/unit/Message/StreamTest.php | 36 +++++++++++ 4 files changed, 125 insertions(+), 29 deletions(-) diff --git a/src/Message/NullStream.php b/src/Message/NullStream.php index ac3c650..17ac8f6 100644 --- a/src/Message/NullStream.php +++ b/src/Message/NullStream.php @@ -14,6 +14,10 @@ class NullStream implements StreamInterface * * Warning: This could attempt to load a large amount of data into memory. * + * This method MUST NOT raise an exception in order to conform with PHP's + * string casting operations. + * + * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring * @return string */ public function __toString() @@ -58,7 +62,7 @@ class NullStream implements StreamInterface */ public function tell() { - return false; + return 0; } /** @@ -91,27 +95,26 @@ class NullStream implements StreamInterface * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to * offset bytes SEEK_CUR: Set position to current location plus offset * SEEK_END: Set position to end-of-stream plus offset. - * @return bool Returns TRUE on success or FALSE on failure. + * @throws \RuntimeException on failure. */ public function seek($offset, $whence = SEEK_SET) { - return false; + throw new \RuntimeException("Unable to seek to position."); } /** * Seek to the beginning of the stream. * - * If the stream is not seekable, this method will return FALSE, indicating - * failure; otherwise, it will perform a seek(0), and return the status of - * that operation. + * If the stream is not seekable, this method will raise an exception; + * otherwise, it will perform a seek(0). * * @see seek() * @link http://www.php.net/manual/en/function.fseek.php - * @return bool Returns TRUE on success or FALSE on failure. + * @throws \RuntimeException on failure. */ public function rewind() { - return false; + throw new \RuntimeException("Unable to rewind srream."); } /** @@ -128,12 +131,12 @@ class NullStream implements StreamInterface * Write data to the stream. * * @param string $string The string that is to be written. - * @return int|bool Returns the number of bytes written to the stream on - * success or FALSE on failure. + * @return int Returns the number of bytes written to the stream. + * @throws \RuntimeException on failure. */ public function write($string) { - return false; + throw new \RuntimeException("Unable to write to stream."); } /** @@ -152,8 +155,9 @@ class NullStream implements StreamInterface * @param int $length Read up to $length bytes from the object and return * them. Fewer than $length bytes may be returned if underlying stream * call returns fewer bytes. - * @return string|false Returns the data read from the stream, false if - * unable to read or if an error occurs. + * @return string Returns the data read from the stream, or an empty string + * if no bytes are available. + * @throws \RuntimeException if an error occurs. */ public function read($length) { @@ -164,6 +168,8 @@ class NullStream implements StreamInterface * Returns the remaining contents in a string * * @return string + * @throws \RuntimeException if unable to read or an error occurs while + * reading. */ public function getContents() { diff --git a/src/Message/Stream.php b/src/Message/Stream.php index f17a390..b0429ff 100644 --- a/src/Message/Stream.php +++ b/src/Message/Stream.php @@ -41,6 +41,10 @@ class Stream implements StreamInterface * * Warning: This could attempt to load a large amount of data into memory. * + * This method MUST NOT raise an exception in order to conform with PHP's + * string casting operations. + * + * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring * @return string */ public function __toString() @@ -87,11 +91,18 @@ class Stream implements StreamInterface /** * Returns the current position of the file read/write pointer * - * @return int|bool Position of the file pointer or false on error. + * @return int Position of the file pointer + * @throws \RuntimeException on error. */ public function tell() { - return ftell($this->resource); + $position = ftell($this->resource); + if ($position === false) { + // @codeCoverageIgnoreStart + throw new \RuntimeException("Unable to retrieve current position of file pointer."); + // @codeCoverageIgnoreEnd + } + return $position; } /** @@ -124,11 +135,19 @@ class Stream implements StreamInterface * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to * offset bytes SEEK_CUR: Set position to current location plus offset * SEEK_END: Set position to end-of-stream plus offset. - * @return bool Returns TRUE on success or FALSE on failure. + * @throws \RuntimeException on failure. */ public function seek($offset, $whence = SEEK_SET) { - fseek($this->resource, $offset, $whence); + $result = -1; + if ($this->isSeekable()) { + $result = fseek($this->resource, $offset, $whence); + } + if ($result === -1) { + // @codeCoverageIgnoreStart + throw new \RuntimeException("Unable to seek to position."); + // @codeCoverageIgnoreEnd + } } /** @@ -144,7 +163,15 @@ class Stream implements StreamInterface */ public function rewind() { - rewind($this->resource); + $result = false; + if ($this->isSeekable()) { + $result = rewind($this->resource); + } + if ($result === false) { + // @codeCoverageIgnoreStart + throw new \RuntimeException("Unable to seek to position."); + // @codeCoverageIgnoreEnd + } } /** @@ -162,12 +189,19 @@ class Stream implements StreamInterface * Write data to the stream. * * @param string $string The string that is to be written. - * @return int|bool Returns the number of bytes written to the stream on - * success or FALSE on failure. + * @return int Returns the number of bytes written to the stream. + * @throws \RuntimeException on failure. */ public function write($string) { - return fwrite($this->resource, $string); + $result = false; + if ($this->isWritable()) { + $result = fwrite($this->resource, $string); + } + if ($result === false) { + throw new \RuntimeException("Unable to write to stream."); + } + return $result; } /** @@ -192,17 +226,33 @@ class Stream implements StreamInterface */ public function read($length) { - return fread($this->resource, $length); + $result = false; + if ($this->isReadable()) { + $result = fread($this->resource, $length); + } + if ($result === false) { + throw new \RuntimeException("Unable to read from stream."); + } + return $result; } /** * Returns the remaining contents in a string * * @return string + * @throws \RuntimeException if unable to read or an error occurs while + * reading. */ public function getContents() { - return stream_get_contents($this->resource); + $result = false; + if ($this->isReadable()) { + $result = stream_get_contents($this->resource); + } + if ($result === false) { + throw new \RuntimeException("Unable to read from stream."); + } + return $result; } /** diff --git a/test/tests/unit/Message/NullStreamTest.php b/test/tests/unit/Message/NullStreamTest.php index d8fef82..11e61b4 100644 --- a/test/tests/unit/Message/NullStreamTest.php +++ b/test/tests/unit/Message/NullStreamTest.php @@ -20,6 +20,7 @@ class NullStreamTest extends \PHPUnit_Framework_TestCase $stream = new \WellRESTed\Message\NullStream(); $this->assertNull($stream->close()); } + /** * @covers WellRESTed\Message\NullStream::detach() * @uses WellRESTed\Message\Stream @@ -43,10 +44,10 @@ class NullStreamTest extends \PHPUnit_Framework_TestCase /** * @covers WellRESTed\Message\NullStream::tell */ - public function testTellReturnsFalse() + public function testTellReturnsZero() { $stream = new \WellRESTed\Message\NullStream(); - $this->assertFalse($stream->tell()); + $this->assertEquals(0, $stream->tell()); } /** @@ -70,20 +71,22 @@ class NullStreamTest extends \PHPUnit_Framework_TestCase /** * @covers WellRESTed\Message\NullStream::seek + * @expectedException \RuntimeException */ public function testSeekReturnsFalse() { $stream = new NullStream(); - $this->assertFalse($stream->seek(10)); + $stream->seek(10); } /** * @covers WellRESTed\Message\NullStream::rewind + * @expectedException \RuntimeException */ public function testRewindReturnsFalse() { $stream = new \WellRESTed\Message\NullStream(); - $this->assertFalse($stream->rewind()); + $stream->rewind(); } /** @@ -97,11 +100,12 @@ class NullStreamTest extends \PHPUnit_Framework_TestCase /** * @covers WellRESTed\Message\NullStream::write + * @expectedException \RuntimeException */ - public function testWriteReturnsFalse() + public function testWriteThrowsException() { $stream = new \WellRESTed\Message\NullStream(); - $this->assertFalse($stream->write("")); + $stream->write(""); } /** diff --git a/test/tests/unit/Message/StreamTest.php b/test/tests/unit/Message/StreamTest.php index f96e119..17a7358 100644 --- a/test/tests/unit/Message/StreamTest.php +++ b/test/tests/unit/Message/StreamTest.php @@ -172,6 +172,30 @@ class StreamTest extends \PHPUnit_Framework_TestCase $this->assertEquals($this->content . $message, (string) $stream); } + /** + * @covers WellRESTed\Message\Stream::write + * @expectedException \RuntimeException + */ + public function testThrowsExceptionOnErrorWriting() + { + $filename = tempnam(sys_get_temp_dir(), "php"); + $handle = fopen($filename, "r"); + $stream = new \WellRESTed\Message\Stream($handle); + $stream->write("Hello, world!"); + } + + /** + * @covers WellRESTed\Message\Stream::read + * @expectedException \RuntimeException + */ + public function testThrowsExceptionOnErrorReading() + { + $filename = tempnam(sys_get_temp_dir(), "php"); + $handle = fopen($filename, "w"); + $stream = new \WellRESTed\Message\Stream($handle); + $stream->read(10); + } + /** * @covers WellRESTed\Message\Stream::read */ @@ -183,6 +207,18 @@ class StreamTest extends \PHPUnit_Framework_TestCase $this->assertEquals("world", $string); } + /** + * @covers WellRESTed\Message\Stream::getContents + * @expectedException \RuntimeException + */ + public function testThrowsExceptionOnErrorReadingToEnd() + { + $filename = tempnam(sys_get_temp_dir(), "php"); + $handle = fopen($filename, "w"); + $stream = new \WellRESTed\Message\Stream($handle); + $stream->getContents(); + } + /** * @covers WellRESTed\Message\Stream::getContents */