pax_global_header00006660000000000000000000000064123663017070014517gustar00rootroot0000000000000052 comment=5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a Streams-0.2/000077500000000000000000000000001236630170700127765ustar00rootroot00000000000000Streams-0.2/.gitignore000066400000000000000000000000331236630170700147620ustar00rootroot00000000000000.idea vendor composer.lock Streams-0.2/.travis.yml000066400000000000000000000006411236630170700151100ustar00rootroot00000000000000language: php php: - 5.3 - 5.4 - 5.5 - hhvm matrix: allow_failures: - php: hhvm # due to facebook/hhvm#3321 env: global: - CURRENT_DIR=`pwd` install: - composer install --dev --no-interaction script: - mkdir -p build/logs - cd tests - phpunit --coverage-clover ../build/logs/clover.xml --configuration phpunit.xml after_script: - cd $CURRENT_DIR - php vendor/bin/coveralls -v Streams-0.2/README.md000066400000000000000000000026111236630170700142550ustar00rootroot00000000000000#Streams# [![Build Status](https://travis-ci.org/icewind1991/Streams.svg?branch=master)](https://travis-ci.org/icewind1991/Streams) [![Coverage Status](https://img.shields.io/coveralls/icewind1991/Streams.svg)](https://coveralls.io/r/icewind1991/Streams?branch=master) Generic stream wrappers for php. ##CallBackWrapper## A `CallBackWrapper` can be used to register callbacks on read, write and closing of the stream, it wraps an existing stream and can thus be used for any stream in php The callbacks are passed in the stream context along with the source stream and can be any valid [php callable](http://php.net/manual/en/language.types.callable.php) ###Example### ```php =5.3" }, "require-dev" : { "satooshi/php-coveralls": "dev-master" }, "autoload" : { "psr-4": { "Icewind\\Streams\\Tests\\": "tests/", "Icewind\\Streams\\": "src/" } } } Streams-0.2/src/000077500000000000000000000000001236630170700135655ustar00rootroot00000000000000Streams-0.2/src/CallbackWrapper.php000066400000000000000000000052331236630170700173360ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams; /** * Wrapper that provides callbacks for write, read and close * * The following options should be passed in the context when opening the stream * [ * 'callback' => [ * 'source' => resource * 'read' => function($count){} (optional) * 'write' => function($data){} (optional) * 'close' => function(){} (optional) * ] * ] * * All callbacks are called after the operation is executed on the source stream */ class CallbackWrapper extends Wrapper { /** * @var callable */ protected $readCallback; /** * @var callable */ protected $writeCallback; /** * @var callable */ protected $closeCallback; /** * Wraps a stream with the provided callbacks * * @param resource $source * @param callable $read (optional) * @param callable $write (optional) * @param callable $close (optional) * @return resource * * @throws \BadMethodCallException */ public static function wrap($source, $read = null, $write = null, $close = null) { $context = stream_context_create(array( 'callback' => array( 'source' => $source, 'read' => $read, 'write' => $write, 'close' => $close ) )); stream_wrapper_register('callback', '\Icewind\Streams\CallbackWrapper'); try { $wrapped = fopen('callback://', 'r+', false, $context); } catch (\BadMethodCallException $e) { stream_wrapper_unregister('callback'); throw $e; } stream_wrapper_unregister('callback'); return $wrapped; } public function stream_open($path, $mode, $options, &$opened_path) { $context = $this->loadContext('callback'); if (isset($context['read']) and is_callable($context['read'])) { $this->readCallback = $context['read']; } if (isset($context['write']) and is_callable($context['write'])) { $this->writeCallback = $context['write']; } if (isset($context['close']) and is_callable($context['close'])) { $this->closeCallback = $context['close']; } return true; } public function stream_read($count) { $result = parent::stream_read($count); if ($this->readCallback) { call_user_func($this->readCallback, $count); } return $result; } public function stream_write($data) { $result = parent::stream_write($data); if ($this->writeCallback) { call_user_func($this->writeCallback, $data); } return $result; } public function stream_close() { $result = parent::stream_close(); if ($this->closeCallback) { call_user_func($this->closeCallback); } return $result; } } Streams-0.2/src/Directory.php000066400000000000000000000011421236630170700162400ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams; /** * Interface for stream wrappers that implements a directory */ interface Directory { /** * @param string $path * @param array $options * @return bool */ public function dir_opendir($path, $options); /** * @return string */ public function dir_readdir(); /** * @return bool */ public function dir_closedir(); /** * @return bool */ public function dir_rewinddir(); } Streams-0.2/src/File.php000066400000000000000000000026451236630170700151640ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams; /** * Interface for stream wrappers that implements a file */ interface File { /** * @param string $path * @param string $mode * @param int $options * @param string &$opened_path * @return bool */ public function stream_open($path, $mode, $options, &$opened_path); /** * @param string $offset * @param int $whence * @return bool */ public function stream_seek($offset, $whence = SEEK_SET); /** * @return int */ public function stream_tell(); /** * @param int $count * @return string */ public function stream_read($count); /** * @param string $data * @return int */ public function stream_write($data); /** * @param int $option * @param int $arg1 * @param int $arg2 * @return bool */ public function stream_set_option($option, $arg1, $arg2); /** * @param int $size * @return bool */ public function stream_truncate($size); /** * @return array */ public function stream_stat(); /** * @param int $operation * @return bool */ public function stream_lock($operation); /** * @return bool */ public function stream_flush(); /** * @return bool */ public function stream_eof(); /** * @return bool */ public function stream_close(); } Streams-0.2/src/IteratorDirectory.php000066400000000000000000000054451236630170700177640ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams; /** * Create a directory handle from an iterator or array * * The following options should be passed in the context when opening the stream * [ * 'dir' => [ * 'array' => string[] * 'iterator' => \Iterator * ] * ] * * Either 'array' or 'iterator' need to be set, if both are set, 'iterator' takes preference */ class IteratorDirectory implements Directory { /** * @var resource */ public $context; /** * @var \Iterator */ protected $iterator; /** * Load the source from the stream context and return the context options * * @param string $name * @return array * @throws \Exception */ protected function loadContext($name) { $context = stream_context_get_options($this->context); if (isset($context[$name])) { $context = $context[$name]; } else { throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set'); } if (isset($context['iterator']) and $context['iterator'] instanceof \Iterator) { $this->iterator = $context['iterator']; } else if (isset($context['array']) and is_array($context['array'])) { $this->iterator = new \ArrayIterator($context['array']); } else { throw new \BadMethodCallException('Invalid context, iterator or array not set'); } return $context; } /** * @param string $path * @param array $options * @return bool */ public function dir_opendir($path, $options) { $this->loadContext('dir'); return true; } /** * @return string */ public function dir_readdir() { if ($this->iterator->valid()) { $result = $this->iterator->current(); $this->iterator->next(); return $result; } else { return false; } } /** * @return bool */ public function dir_closedir() { return true; } /** * @return bool */ public function dir_rewinddir() { $this->iterator->rewind(); return true; } /** * Creates a directory handle from the provided array or iterator * * @param \Iterator | array $source * @return resource * * @throws \BadMethodCallException */ public static function wrap($source) { if ($source instanceof \Iterator) { $context = stream_context_create(array( 'dir' => array( 'iterator' => $source) )); } else if (is_array($source)) { $context = stream_context_create(array( 'dir' => array( 'array' => $source) )); } else { throw new \BadMethodCallException('$source should be an Iterator or array'); } stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory'); $wrapped = opendir('iterator://', $context); stream_wrapper_unregister('iterator'); return $wrapped; } } Streams-0.2/src/NullWrapper.php000066400000000000000000000017411236630170700165540ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams; /** * Stream wrapper that does nothing, used for tests */ class NullWrapper extends Wrapper { /** * Wraps a stream with the provided callbacks * * @param resource $source * @return resource * * @throws \BadMethodCallException */ public static function wrap($source) { $context = stream_context_create(array( 'null' => array( 'source' => $source) )); stream_wrapper_register('null', '\Icewind\Streams\NullWrapper'); try { $wrapped = fopen('null://', 'r+', false, $context); } catch (\BadMethodCallException $e) { stream_wrapper_unregister('null'); throw $e; } stream_wrapper_unregister('null'); return $wrapped; } public function stream_open($path, $mode, $options, &$opened_path) { $this->loadContext('null'); return true; } } Streams-0.2/src/Wrapper.php000066400000000000000000000047171236630170700157270ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams; /** * Base class for stream wrappers, wraps an existing stream * * This wrapper itself doesn't implement any functionality but is just a base class for other wrappers to extend */ abstract class Wrapper implements File { /** * @var resource */ public $context; /** * The wrapped stream * * @var resource */ protected $source; /** * Load the source from the stream context and return the context options * * @param string $name * @return array * @throws \Exception */ protected function loadContext($name) { $context = stream_context_get_options($this->context); if (isset($context[$name])) { $context = $context[$name]; } else { throw new \BadMethodCallException('Invalid context, "callable" options not set'); } if (isset($context['source']) and is_resource($context['source'])) { $this->setSourceStream($context['source']); } else { throw new \BadMethodCallException('Invalid context, source not set'); } return $context; } /** * @param resource $source */ protected function setSourceStream($source) { $this->source = $source; } public function stream_seek($offset, $whence = SEEK_SET) { $result = fseek($this->source, $offset, $whence); return $result == 0 ? true : false; } public function stream_tell() { return ftell($this->source); } public function stream_read($count) { return fread($this->source, $count); } public function stream_write($data) { return fwrite($this->source, $data); } public function stream_set_option($option, $arg1, $arg2) { switch ($option) { case STREAM_OPTION_BLOCKING: stream_set_blocking($this->source, $arg1); break; case STREAM_OPTION_READ_TIMEOUT: stream_set_timeout($this->source, $arg1, $arg2); break; case STREAM_OPTION_WRITE_BUFFER: stream_set_write_buffer($this->source, $arg1); } } public function stream_truncate($size) { return ftruncate($this->source, $size); } public function stream_stat() { return fstat($this->source); } public function stream_lock($mode) { return flock($this->source, $mode); } public function stream_flush() { return fflush($this->source); } public function stream_eof() { return feof($this->source); } public function stream_close() { return fclose($this->source); } } Streams-0.2/tests/000077500000000000000000000000001236630170700141405ustar00rootroot00000000000000Streams-0.2/tests/CallbackWrapper.php000066400000000000000000000032321236630170700177060ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams\Tests; class CallbackWrapper extends Wrapper { /** * @param resource $source * @param callable $read * @param callable $write * @param callable $close * @return resource */ protected function wrapSource($source, $read = null, $write = null, $close = null) { return \Icewind\Streams\CallbackWrapper::wrap($source, $read, $write, $close); } /** * @expectedException \BadMethodCallException */ public function testWrapInvalidSource() { $this->wrapSource('foo'); } public function testReadCallback() { $called = false; $callBack = function () use (&$called) { $called = true; }; $source = fopen('php://temp', 'r+'); fwrite($source, 'foobar'); rewind($source); $wrapped = $this->wrapSource($source, $callBack); $this->assertEquals('foo', fread($wrapped, 3)); $this->assertTrue($called); } public function testWriteCallback() { $lastData = ''; $callBack = function ($data) use (&$lastData) { $lastData = $data; }; $source = fopen('php://temp', 'r+'); $wrapped = $this->wrapSource($source, null, $callBack); fwrite($wrapped, 'foobar'); $this->assertEquals('foobar', $lastData); } public function testCloseCallback() { $called = false; $callBack = function () use (&$called) { $called = true; }; $source = fopen('php://temp', 'r+'); fwrite($source, 'foobar'); rewind($source); $wrapped = $this->wrapSource($source, null, null, $callBack); fclose($wrapped); $this->assertTrue($called); } } Streams-0.2/tests/IteratorDirectory.php000066400000000000000000000050471236630170700203350ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams\Tests; class IteratorDirectory extends \PHPUnit_Framework_TestCase { /** * @param \Iterator | array $source * @return resource */ protected function wrapSource($source) { return \Icewind\Streams\IteratorDirectory::wrap($source); } /** * @expectedException \BadMethodCallException */ public function testNoContext() { $context = stream_context_create(array()); stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory'); try { opendir('iterator://', $context); stream_wrapper_unregister('iterator'); } catch (\Exception $e) { stream_wrapper_unregister('iterator'); throw $e; } } /** * @expectedException \BadMethodCallException */ public function testInvalidSource() { $context = stream_context_create(array( 'dir' => array( 'array' => 2 ) )); stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory'); try { opendir('iterator://', $context); stream_wrapper_unregister('iterator'); } catch (\Exception $e) { stream_wrapper_unregister('iterator'); throw $e; } } /** * @expectedException \BadMethodCallException */ public function testWrapInvalidSource() { $this->wrapSource(2); } public function fileListProvider() { $longList = array_fill(0, 500, 'foo'); return array( array( array( 'foo', 'bar', 'qwerty' ) ), array( array( 'with spaces', 'under_scores', '日本語', 'character %$_', '.', '0', 'double "quotes"', "single 'quotes'" ) ), array( array( 'single item' ) ), array( $longList ), array( array() ) ); } protected function basicTest($fileList, $dh) { $result = array(); while (($file = readdir($dh)) !== false) { $result[] = $file; } $this->assertEquals($fileList, $result); rewinddir($dh); if (count($fileList)) { $this->assertEquals($fileList[0], readdir($dh)); } else { $this->assertFalse(readdir($dh)); } } /** * @dataProvider fileListProvider */ public function testBasicIterator($fileList) { $iterator = new \ArrayIterator($fileList); $dh = $this->wrapSource($iterator); $this->basicTest($fileList, $dh); } /** * @dataProvider fileListProvider */ public function testBasicArray($fileList) { $dh = $this->wrapSource($fileList); $this->basicTest($fileList, $dh); } } Streams-0.2/tests/NullWrapper.php000066400000000000000000000024441236630170700171300ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams\Tests; class NullWrapper extends Wrapper { /** * @param resource $source * @return resource */ protected function wrapSource($source) { return \Icewind\Streams\NullWrapper::wrap($source); } /** * @expectedException \BadMethodCallException */ public function testNoContext() { stream_wrapper_register('null', '\Icewind\Streams\NullWrapper'); $context = stream_context_create(array()); try { fopen('null://', 'r+', false, $context); stream_wrapper_unregister('null'); } catch (\Exception $e) { stream_wrapper_unregister('null'); throw $e; } } /** * @expectedException \BadMethodCallException */ public function testNoSource() { stream_wrapper_register('null', '\Icewind\Streams\NullWrapper'); $context = stream_context_create(array( 'null' => array( 'source' => 'bar' ) )); try { fopen('null://', 'r+', false, $context); } catch (\Exception $e) { stream_wrapper_unregister('null'); throw $e; } } /** * @expectedException \BadMethodCallException */ public function testWrapInvalidSource() { $this->wrapSource('foo'); } } Streams-0.2/tests/Wrapper.php000066400000000000000000000052031236630170700162710ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ namespace Icewind\Streams\Tests; abstract class Wrapper extends \PHPUnit_Framework_TestCase { /** * @param resource $source * @return resource */ abstract protected function wrapSource($source); public function testRead() { $source = fopen('php://temp', 'r+'); fwrite($source, 'foobar'); rewind($source); $wrapped = $this->wrapSource($source); $this->assertEquals('foo', fread($wrapped, 3)); $this->assertEquals('bar', fread($wrapped, 3)); $this->assertEquals('', fread($wrapped, 3)); } public function testWrite() { $source = fopen('php://temp', 'r+'); rewind($source); $wrapped = $this->wrapSource($source); $this->assertEquals(6, fwrite($wrapped, 'foobar')); rewind($source); $this->assertEquals('foobar', stream_get_contents($source)); } public function testClose() { $source = fopen('php://temp', 'r+'); rewind($source); $wrapped = $this->wrapSource($source); fclose($wrapped); $this->assertFalse(is_resource($source)); } public function testSeekTell() { $source = fopen('php://temp', 'r+'); fwrite($source, 'foobar'); rewind($source); $wrapped = $this->wrapSource($source); $this->assertEquals(0, ftell($wrapped)); fseek($wrapped, 2); $this->assertEquals(2, ftell($source)); $this->assertEquals(2, ftell($wrapped)); fseek($wrapped, 2, SEEK_CUR); $this->assertEquals(4, ftell($source)); $this->assertEquals(4, ftell($wrapped)); fseek($wrapped, -1, SEEK_END); $this->assertEquals(5, ftell($source)); $this->assertEquals(5, ftell($wrapped)); } public function testStat() { $source = fopen(__FILE__, 'r+'); $wrapped = $this->wrapSource($source); $this->assertEquals(stat(__FILE__), fstat($wrapped)); } public function testTruncate() { if (version_compare(phpversion(), '5.4.0', '<')) { $this->markTestSkipped('php <5.4 doesn\'t support truncate for stream wrappers'); } $source = fopen('php://temp', 'r+'); fwrite($source, 'foobar'); rewind($source); $wrapped = $this->wrapSource($source); ftruncate($wrapped, 2); $this->assertEquals('fo', fread($wrapped, 10)); } public function testLock() { $source = tmpfile(); $wrapped = $this->wrapSource($source); if (!flock($wrapped, LOCK_EX)) { $this->fail('Unable to acquire lock'); } } public function testStreamOptions() { $source = fopen('php://temp', 'r+'); $wrapped = $this->wrapSource($source); stream_set_blocking($wrapped, 0); stream_set_timeout($wrapped, 1, 0); stream_set_write_buffer($wrapped, 0); } } Streams-0.2/tests/bootstrap.php000066400000000000000000000004101236630170700166610ustar00rootroot00000000000000 * This file is licensed under the Licensed under the MIT license: * http://opensource.org/licenses/MIT */ date_default_timezone_set('UTC'); require_once __DIR__ . '/../vendor/autoload.php'; Streams-0.2/tests/phpunit.xml000066400000000000000000000002521236630170700163500ustar00rootroot00000000000000 ./