Add directory support to wrappers

This commit is contained in:
Robin Appelman 2014-08-27 15:36:58 +02:00
commit 53cfe7f4f1
5 changed files with 121 additions and 28 deletions

View file

@ -17,6 +17,7 @@ namespace Icewind\Streams;
* 'read' => function($count){} (optional) * 'read' => function($count){} (optional)
* 'write' => function($data){} (optional) * 'write' => function($data){} (optional)
* 'close' => function(){} (optional) * 'close' => function(){} (optional)
* 'readdir' => function(){} (optional)
* ] * ]
* ] * ]
* *
@ -38,6 +39,11 @@ class CallbackWrapper extends Wrapper {
*/ */
protected $closeCallback; protected $closeCallback;
/**
* @var callable
*/
protected $readDirCallBack;
/** /**
* Wraps a stream with the provided callbacks * Wraps a stream with the provided callbacks
* *
@ -45,31 +51,25 @@ class CallbackWrapper extends Wrapper {
* @param callable $read (optional) * @param callable $read (optional)
* @param callable $write (optional) * @param callable $write (optional)
* @param callable $close (optional) * @param callable $close (optional)
* @param callable $readDir (optional)
* @return resource * @return resource
* *
* @throws \BadMethodCallException * @throws \BadMethodCallException
*/ */
public static function wrap($source, $read = null, $write = null, $close = null) { public static function wrap($source, $read = null, $write = null, $close = null, $readDir = null) {
$context = stream_context_create(array( $context = stream_context_create(array(
'callback' => array( 'callback' => array(
'source' => $source, 'source' => $source,
'read' => $read, 'read' => $read,
'write' => $write, 'write' => $write,
'close' => $close 'close' => $close,
'readDir' => $readDir
) )
)); ));
stream_wrapper_register('callback', '\Icewind\Streams\CallbackWrapper'); return Wrapper::wrapSource($source, $context, '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) { protected function open() {
$context = $this->loadContext('callback'); $context = $this->loadContext('callback');
if (is_callable($context['read'])) { if (is_callable($context['read'])) {
@ -81,9 +81,20 @@ class CallbackWrapper extends Wrapper {
if (is_callable($context['close'])) { if (is_callable($context['close'])) {
$this->closeCallback = $context['close']; $this->closeCallback = $context['close'];
} }
if (is_callable($context['readDir'])) {
$this->readDirCallBack = $context['readDir'];
}
return true; return true;
} }
public function dir_opendir($path, $options) {
return $this->open();
}
public function stream_open($path, $mode, $options, &$opened_path) {
return $this->open();
}
public function stream_read($count) { public function stream_read($count) {
$result = parent::stream_read($count); $result = parent::stream_read($count);
if ($this->readCallback) { if ($this->readCallback) {
@ -107,4 +118,12 @@ class CallbackWrapper extends Wrapper {
} }
return $result; return $result;
} }
public function dir_readdir() {
$result = parent::dir_readdir();
if ($this->readDirCallBack) {
call_user_func($this->readDirCallBack);
}
return $result;
}
} }

View file

@ -24,19 +24,16 @@ class NullWrapper extends Wrapper {
'null' => array( 'null' => array(
'source' => $source) 'source' => $source)
)); ));
stream_wrapper_register('null', '\Icewind\Streams\NullWrapper'); return Wrapper::wrapSource($source, $context, '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) { public function stream_open($path, $mode, $options, &$opened_path) {
$this->loadContext('null'); $this->loadContext('null');
return true; return true;
} }
public function dir_opendir($path, $options) {
$this->loadContext('null');
return true;
}
} }

View file

@ -12,7 +12,7 @@ namespace Icewind\Streams;
* *
* This wrapper itself doesn't implement any functionality but is just a base class for other wrappers to extend * This wrapper itself doesn't implement any functionality but is just a base class for other wrappers to extend
*/ */
abstract class Wrapper implements File { abstract class Wrapper implements File, Directory {
/** /**
* @var resource * @var resource
*/ */
@ -25,6 +25,22 @@ abstract class Wrapper implements File {
*/ */
protected $source; protected $source;
protected static function wrapSource($source, $context, $protocol, $class) {
try {
stream_wrapper_register($protocol, $class);
if (@rewinddir($source) === false) {
$wrapped = fopen($protocol . '://', 'r+', false, $context);
} else {
$wrapped = opendir($protocol . '://', $context);
}
} catch (\BadMethodCallException $e) {
stream_wrapper_unregister($protocol);
throw $e;
}
stream_wrapper_unregister($protocol);
return $wrapped;
}
/** /**
* Load the source from the stream context and return the context options * Load the source from the stream context and return the context options
* *
@ -107,4 +123,17 @@ abstract class Wrapper implements File {
public function stream_close() { public function stream_close() {
return fclose($this->source); return fclose($this->source);
} }
public function dir_readdir() {
return readdir($this->source);
}
public function dir_closedir() {
closedir($this->source);
return true;
}
public function dir_rewinddir() {
return rewind($this->source);
}
} }

View file

@ -14,10 +14,11 @@ class CallbackWrapper extends Wrapper {
* @param callable $read * @param callable $read
* @param callable $write * @param callable $write
* @param callable $close * @param callable $close
* @param callable $readDir
* @return resource * @return resource
*/ */
protected function wrapSource($source, $read = null, $write = null, $close = null) { protected function wrapSource($source, $read = null, $write = null, $close = null, $readDir = null) {
return \Icewind\Streams\CallbackWrapper::wrap($source, $read, $write, $close); return \Icewind\Streams\CallbackWrapper::wrap($source, $read, $write, $close, $readDir);
} }
/** /**
@ -69,4 +70,17 @@ class CallbackWrapper extends Wrapper {
fclose($wrapped); fclose($wrapped);
$this->assertTrue($called); $this->assertTrue($called);
} }
public function testReadDirCallback() {
$called = false;
$callBack = function () use (&$called) {
$called = true;
};
$source = opendir(sys_get_temp_dir());
$wrapped = $this->wrapSource($source, null, null, null, $callBack);
readdir($wrapped);
$this->assertTrue($called);
}
} }

View file

@ -102,4 +102,38 @@ abstract class Wrapper extends \PHPUnit_Framework_TestCase {
stream_set_timeout($wrapped, 1, 0); stream_set_timeout($wrapped, 1, 0);
stream_set_write_buffer($wrapped, 0); stream_set_write_buffer($wrapped, 0);
} }
public function testReadDir() {
$source = opendir(__DIR__);
$content = array();
while (($name = readdir($source)) !== false) {
$content[] = $name;
}
closedir($source);
$source = opendir(__DIR__);
$wrapped = $this->wrapSource($source);
$wrappedContent = array();
while (($name = readdir($wrapped)) !== false) {
$wrappedContent[] = $name;
}
$this->assertEquals($content, $wrappedContent);
}
public function testRewindDir() {
$source = opendir(__DIR__);
$content = array();
while (($name = readdir($source)) !== false) {
$content[] = $name;
}
closedir($source);
$source = opendir(__DIR__);
$wrapped = $this->wrapSource($source);
$this->assertEquals($content[0], readdir($wrapped));
$this->assertEquals($content[1], readdir($wrapped));
$this->assertEquals($content[2], readdir($wrapped));
rewinddir($wrapped);
$this->assertEquals($content[0], readdir($wrapped));
}
} }