mirror of
https://codeberg.org/icewind/streams.git
synced 2026-06-04 00:54:08 +02:00
Add UrlCallBack wrapper that provides callbacks for fopen, unlink, rename, etc
This commit is contained in:
parent
53cfe7f4f1
commit
e5854bfd06
4 changed files with 421 additions and 0 deletions
104
src/Path.php
Normal file
104
src/Path.php
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
|
||||||
|
* This file is licensed under the Licensed under the MIT license:
|
||||||
|
* http://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Icewind\Streams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string-like object that automatically registers a stream wrapper when used and removes the stream wrapper when no longer used
|
||||||
|
*
|
||||||
|
* Can optionally pass context options to the stream wrapper
|
||||||
|
*/
|
||||||
|
class Path {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $registered = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $contextOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $class
|
||||||
|
* @param array $contextOptions
|
||||||
|
*/
|
||||||
|
public function __construct($class, $contextOptions = array()) {
|
||||||
|
$this->class = $class;
|
||||||
|
$this->contextOptions = $contextOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProtocol() {
|
||||||
|
if (!$this->protocol) {
|
||||||
|
$this->protocol = 'auto' . uniqid();
|
||||||
|
}
|
||||||
|
return $this->protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function wrapPath($path) {
|
||||||
|
return $this->getProtocol() . '://' . $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function register() {
|
||||||
|
if (!$this->registered) {
|
||||||
|
$this->appendDefaultContent($this->getProtocol(), $this->contextOptions);
|
||||||
|
stream_wrapper_register($this->getProtocol(), $this->class);
|
||||||
|
$this->registered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function unregister() {
|
||||||
|
stream_wrapper_unregister($this->getProtocol());
|
||||||
|
$this->unsetDefaultContent($this->getProtocol());
|
||||||
|
$this->registered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add values to the default stream context
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param array $values
|
||||||
|
*/
|
||||||
|
protected function appendDefaultContent($key, $values) {
|
||||||
|
$context = stream_context_get_default();
|
||||||
|
$defaults = stream_context_get_options($context);
|
||||||
|
$defaults[$key] = $values;
|
||||||
|
stream_context_set_default($defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove values from the default stream context
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
*/
|
||||||
|
protected function unsetDefaultContent($key) {
|
||||||
|
$context = stream_context_get_default();
|
||||||
|
$defaults = stream_context_get_options($context);
|
||||||
|
unset($defaults[$key]);
|
||||||
|
stream_context_set_default($defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString() {
|
||||||
|
$this->register();
|
||||||
|
return $this->protocol . '://';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
$this->unregister();
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/Url.php
Normal file
64
src/Url.php
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
|
||||||
|
* This file is licensed under the Licensed under the MIT license:
|
||||||
|
* http://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Icewind\Streams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for stream wrappers that implement url functions such as unlink, stat
|
||||||
|
*/
|
||||||
|
interface Url {
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param array $options
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function dir_opendir($path, $options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 $path
|
||||||
|
* @param int $mode
|
||||||
|
* @param int $options
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function mkdir($path, $mode, $options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $source
|
||||||
|
* @param string $target
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function rename($source, $target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param int $options
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function rmdir($path, $options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function unlink($path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @param int $flags
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function url_stat($path, $flags);
|
||||||
|
}
|
||||||
121
src/UrlCallBack.php
Normal file
121
src/UrlCallBack.php
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
|
||||||
|
* This file is licensed under the Licensed under the MIT license:
|
||||||
|
* http://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Icewind\Streams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper that provides callbacks for url actions such as fopen, unlink, rename
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* $path = UrlCallBack('/path/so/source', function(){
|
||||||
|
* echo 'fopen';
|
||||||
|
* }, function(){
|
||||||
|
* echo 'opendir';
|
||||||
|
* }, function(){
|
||||||
|
* echo 'mkdir';
|
||||||
|
* }, function(){
|
||||||
|
* echo 'rename';
|
||||||
|
* }, function(){
|
||||||
|
* echo 'rmdir';
|
||||||
|
* }, function(){
|
||||||
|
* echo 'unlink';
|
||||||
|
* }, function(){
|
||||||
|
* echo 'stat';
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* mkdir($path);
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* All callbacks are called after the operation is executed on the source stream
|
||||||
|
*/
|
||||||
|
class UrlCallback extends Wrapper implements Url {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $source
|
||||||
|
* @param callable $fopen
|
||||||
|
* @param callable $opendir
|
||||||
|
* @param callable $mkdir
|
||||||
|
* @param callable $rename
|
||||||
|
* @param callable $rmdir
|
||||||
|
* @param callable $unlink
|
||||||
|
* @param callable $stat
|
||||||
|
* @return \Icewind\Streams\Path
|
||||||
|
*
|
||||||
|
* @throws \BadMethodCallException
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public static function wrap($source, $fopen = null, $opendir = null, $mkdir = null, $rename = null, $rmdir = null,
|
||||||
|
$unlink = null, $stat = null) {
|
||||||
|
$options = array(
|
||||||
|
'source' => $source,
|
||||||
|
'fopen' => $fopen,
|
||||||
|
'opendir' => $opendir,
|
||||||
|
'mkdir' => $mkdir,
|
||||||
|
'rename' => $rename,
|
||||||
|
'rmdir' => $rmdir,
|
||||||
|
'unlink' => $unlink,
|
||||||
|
'stat' => $stat
|
||||||
|
);
|
||||||
|
return new Path('\Icewind\Streams\UrlCallBack', $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadContext($url) {
|
||||||
|
list($protocol) = explode('://', $url);
|
||||||
|
$options = stream_context_get_options($this->context);
|
||||||
|
return $options[$protocol];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function callCallBack($context, $callback) {
|
||||||
|
if (is_callable($context[$callback])) {
|
||||||
|
call_user_func($context[$callback]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||||
|
$context = $this->loadContext($path);
|
||||||
|
$this->callCallBack($context, 'fopen');
|
||||||
|
$this->setSourceStream(fopen($context['source'], $mode));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dir_opendir($path, $options) {
|
||||||
|
$context = $this->loadContext($path);
|
||||||
|
$this->callCallBack($context, 'opendir');
|
||||||
|
$this->setSourceStream(opendir($context['source']));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mkdir($path, $mode, $options) {
|
||||||
|
$context = $this->loadContext($path);
|
||||||
|
$this->callCallBack($context, 'mkdir');
|
||||||
|
return mkdir($context['source'], $mode, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rmdir($path, $options) {
|
||||||
|
$context = $this->loadContext($path);
|
||||||
|
$this->callCallBack($context, 'rmdir');
|
||||||
|
return rmdir($context['source']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rename($source, $target) {
|
||||||
|
$context = $this->loadContext($source);
|
||||||
|
$this->callCallBack($context, 'rename');
|
||||||
|
list(, $target) = explode('://', $target);
|
||||||
|
return rename($context['source'], $target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unlink($path) {
|
||||||
|
$context = $this->loadContext($path);
|
||||||
|
$this->callCallBack($context, 'unlink');
|
||||||
|
return unlink($context['source']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url_stat($path, $flags) {
|
||||||
|
throw new \Exception('stat is not supported due to php bug 50526');
|
||||||
|
}
|
||||||
|
}
|
||||||
132
tests/UrlCallBack.php
Normal file
132
tests/UrlCallBack.php
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
|
||||||
|
* This file is licensed under the Licensed under the MIT license:
|
||||||
|
* http://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Icewind\Streams\Tests;
|
||||||
|
|
||||||
|
class UrlCallBack extends \PHPUnit_Framework_TestCase {
|
||||||
|
protected $tempDirs = array();
|
||||||
|
|
||||||
|
protected function getTempDir() {
|
||||||
|
$dir = sys_get_temp_dir() . '/streams_' . uniqid();
|
||||||
|
mkdir($dir);
|
||||||
|
$this->tempDirs[] = $dir;
|
||||||
|
return $dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
foreach ($this->tempDirs as $dir) {
|
||||||
|
$this->rmdir($dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function rmdir($path) {
|
||||||
|
$directory = new \RecursiveDirectoryIterator($path);
|
||||||
|
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::CHILD_FIRST);
|
||||||
|
/**
|
||||||
|
* @var \SplFileInfo $file
|
||||||
|
*/
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if (in_array($file->getBasename(), array('.', '..'))) {
|
||||||
|
continue;
|
||||||
|
} elseif ($file->isDir()) {
|
||||||
|
rmdir($file->getPathname());
|
||||||
|
} elseif ($file->isFile() || $file->isLink()) {
|
||||||
|
unlink($file->getPathname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFOpenCallBack() {
|
||||||
|
$called = false;
|
||||||
|
$callback = function () use (&$called) {
|
||||||
|
$called = true;
|
||||||
|
};
|
||||||
|
$path = \Icewind\Streams\UrlCallBack::wrap('php://temp', $callback);
|
||||||
|
$fh = fopen($path, 'r');
|
||||||
|
fclose($fh);
|
||||||
|
$this->assertTrue($called);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOpenDirCallBack() {
|
||||||
|
$called = false;
|
||||||
|
$callback = function () use (&$called) {
|
||||||
|
$called = true;
|
||||||
|
};
|
||||||
|
$path = \Icewind\Streams\UrlCallBack::wrap($this->getTempDir(), null, $callback);
|
||||||
|
$fh = opendir($path);
|
||||||
|
closedir($fh);
|
||||||
|
$this->assertTrue($called);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMKDirCallBack() {
|
||||||
|
$called = false;
|
||||||
|
$callback = function () use (&$called) {
|
||||||
|
$called = true;
|
||||||
|
};
|
||||||
|
$dir = $this->getTempDir() . '/test';
|
||||||
|
$path = \Icewind\Streams\UrlCallBack::wrap($dir, null, null, $callback);
|
||||||
|
mkdir($path);
|
||||||
|
$this->assertTrue(file_exists($dir));
|
||||||
|
$this->assertTrue($called);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRMDirCallBack() {
|
||||||
|
$called = false;
|
||||||
|
$callback = function () use (&$called) {
|
||||||
|
$called = true;
|
||||||
|
};
|
||||||
|
$dir = $this->getTempDir() . '/test';
|
||||||
|
mkdir($dir);
|
||||||
|
$path = \Icewind\Streams\UrlCallBack::wrap($dir, null, null, null, null, $callback);
|
||||||
|
rmdir($path);
|
||||||
|
$this->assertFalse(file_exists($dir));
|
||||||
|
$this->assertTrue($called);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRenameCallBack() {
|
||||||
|
$called = false;
|
||||||
|
$callback = function () use (&$called) {
|
||||||
|
$called = true;
|
||||||
|
};
|
||||||
|
$source = $this->getTempDir() . '/test';
|
||||||
|
touch($source);
|
||||||
|
$path = \Icewind\Streams\UrlCallBack::wrap($source, null, null, null, $callback);
|
||||||
|
$target = $path->wrapPath($source . '_rename');
|
||||||
|
rename($path, $target);
|
||||||
|
$this->assertTrue(file_exists($source . '_rename'));
|
||||||
|
$this->assertTrue($called);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnlinkCallBack() {
|
||||||
|
$called = false;
|
||||||
|
$callback = function () use (&$called) {
|
||||||
|
$called = true;
|
||||||
|
};
|
||||||
|
$file = $this->getTempDir() . '/test';
|
||||||
|
touch($file);
|
||||||
|
$path = \Icewind\Streams\UrlCallBack::wrap($file, null, null, null, null, null, $callback);
|
||||||
|
unlink($path);
|
||||||
|
$this->assertFalse(file_exists($file));
|
||||||
|
$this->assertTrue($called);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStatCallBack() {
|
||||||
|
$called = false;
|
||||||
|
$callback = function () use (&$called) {
|
||||||
|
$called = true;
|
||||||
|
};
|
||||||
|
$file = $this->getTempDir() . '/test';
|
||||||
|
touch($file);
|
||||||
|
$path = \Icewind\Streams\UrlCallBack::wrap($file, null, null, null, null, null, null, $callback);
|
||||||
|
try {
|
||||||
|
stat($path);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->markTestSkipped('url_stat doesn\'t receive the context parameter, see php bug 50526');
|
||||||
|
}
|
||||||
|
$this->assertTrue($called);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue