stream_wrapper_register

(PHP 4 >= 4.3.2, PHP 5, PHP 7, PHP 8)

stream_wrapper_register注册作为 PHP 类实现的 URL 包装器

说明

stream_wrapper_register(string $protocol, string $class, int $flags = 0): bool

允许您实现自己的协议处理程序和流,以便与所有其他文件系统函数(例如 fopen()fread() 等)一起使用。

参数

protocol

要注册的包装器名称。有效的协议名称必须仅包含字母数字、点 (.)、加号 (+) 或连字符 (-)。

class

实现 protocol 的类名。

flags

如果 protocol 是 URL 协议,则应设置为 STREAM_IS_URL。默认值为 0,本地流。

返回值

成功时返回 true,失败时返回 false

如果 protocol 已经有一个处理程序,则 stream_wrapper_register() 将返回 false

示例

示例 #1 如何注册流包装器

<?php
$existed
= in_array("var", stream_get_wrappers());
if (
$existed) {
stream_wrapper_unregister("var");
}
stream_wrapper_register("var", "VariableStream");
$myvar = "";

$fp = fopen("var://myvar", "r+");

fwrite($fp, "line1\n");
fwrite($fp, "line2\n");
fwrite($fp, "line3\n");

rewind($fp);
while (!
feof($fp)) {
echo
fgets($fp);
}
fclose($fp);
var_dump($myvar);

if (
$existed) {
stream_wrapper_restore("var");
}

?>

上面的示例将输出

line1
line2
line3
string(18) "line1
line2
line3
"

参见

添加注释

用户贡献的注释 13 个注释

cellog at php dot net
19 年前
如果您计划在 require_once 中使用您的包装器,则需要定义 stream_stat()。如果您计划允许任何其他测试,如 is_file()/is_dir(),则必须定义 url_stat()。

stream_stat() 必须定义文件的大小,否则它永远不会被包含。url_stat() 必须定义模式,否则 is_file()/is_dir()/is_executable(),以及受 clearstatcache() 影响的任何这些函数将无法正常工作。

它没有在文档中说明,但目录必须是像 040777(八进制)这样的模式,而文件必须是像 0100666 这样的模式。如果您希望文件可执行,请使用 7s 而不是 6s。最后 3 位数字与您传递给 chmod 的内容完全相同。040000 定义目录,而 0100000 定义文件。将此添加到官方手册中将非常有帮助!
fordiman at gmail dot com
17 年前
已更新。我认为,如果我们进行间接引用,则无需存储变量名称;我们只需存储指针,而无需在每个函数中进行间接引用,以简洁起见。

此外,我添加了断言,即流是一个字符串,因为我们基本上需要像它必须是那样进行操作,并且我将名称更改为 GlobalStream 和 global://,因为这比 VariableName/var:// 更具描述性。
<?php
class GlobalStream {
private
$pos;
private
$stream;
public function
stream_open($path, $mode, $options, &$opened_path) {
$url = parse_url($path);
$this->stream = &$GLOBALS[$url["host"]];
$this->pos = 0;
if (!
is_string($this->stream)) return false;
return
true;
}
public function
stream_read($count) {
$p=&$this->pos;
$ret = substr($this->stream, $this->pos, $count);
$this->pos += strlen($ret);
return
$ret;
}
public function
stream_write($data){
$l=strlen($data);
$this->stream =
substr($this->stream, 0, $this->pos) .
$data .
substr($this->stream, $this->pos += $l);
return
$l;
}
public function
stream_tell() {
return
$this->pos;
}
public function
stream_eof() {
return
$this->pos >= strlen($this->stream);
}
public function
stream_seek($offset, $whence) {
$l=strlen($this->stream);
switch (
$whence) {
case
SEEK_SET: $newPos = $offset; break;
case
SEEK_CUR: $newPos = $this->pos + $offset; break;
case
SEEK_END: $newPos = $l + $offset; break;
default: return
false;
}
$ret = ($newPos >=0 && $newPos <=$l);
if (
$ret) $this->pos=$newPos;
return
$ret;
}
}
stream_wrapper_register('global', 'GlobalStream') or die('Failed to register protocol global://');

$myvar = "";

$fp = fopen("global://myvar", "r+");

fwrite($fp, "line1\n");
fwrite($fp, "line2\n");
fwrite($fp, "line3\n");

rewind($fp);
while (!
feof($fp)) {
echo
fgets($fp);
}
fclose($fp);
var_dump($myvar);
?>
fordiman at gmail dot com
17 年前
更新为 PHP 5,并针对可读性、低行数和最小内存使用进行了优化

<?php
VariableStream {
private
$position;
private
$varname;
public function
stream_open($path, $mode, $options, &$opened_path) {
$url = parse_url($path);
$this->varname = $url["host"];
$this->position = 0;
return
true;
}
public function
stream_read($count) {
$p=&$this->position;
$ret = substr($GLOBALS[$this->varname], $p, $count);
$p += strlen($ret);
return
$ret;
}
public function
stream_write($data){
$v=&$GLOBALS[$this->varname];
$l=strlen($data);
$p=&$this->position;
$v = substr($v, 0, $p) . $data . substr($v, $p += $l);
return
$l;
}
public function
stream_tell() {
return
$this->position;
}
public function
stream_eof() {
return
$this->position >= strlen($GLOBALS[$this->varname]);
}
public function
stream_seek($offset, $whence) {
$l=strlen(&$GLOBALS[$this->varname]);
$p=&$this->position;
switch (
$whence) {
case
SEEK_SET: $newPos = $offset; break;
case
SEEK_CUR: $newPos = $p + $offset; break;
case
SEEK_END: $newPos = $l + $offset; break;
default: return
false;
}
$ret = ($newPos >=0 && $newPos <=$l);
if (
$ret) $p=$newPos;
return
$ret;
}
}
stream_wrapper_register("var", "VariableStream")
or die(
"Failed to register protocol");

$myvar = "";

$fp = fopen("var://myvar", "r+");

fwrite($fp, "line1\n");
fwrite($fp, "line2\n");
fwrite($fp, "line3\n");

rewind($fp);
while (!
feof($fp)) {
echo
fgets($fp);
}
fclose($fp);
var_dump($myvar);
?>
phpnet at povaddict dot com dot ar
17 年前
作为对 Coward dot com 上的 Anonymouse 的回复

手册中写道:“读取操作将在读取了最多 length 字节后停止,[...] 或者(在打开用户空间流后)读取了 8192 字节后停止,以先发生的为准。”

我测试了一下,fread($filehandle, 4096) 返回了 4096 字节,所以它按手册中的说明工作。你说“8192 字节总是作为 count 传递给 stream_read”,你说得对,但这并不意味着 fread 会返回 8192 字节。如果你用 length 为 4096 调用 fread 两次,PHP 会在第一次 fread 时调用 stream_read 并传递 8192 作为 count,而不会在第二次 fread 时调用它。在这两种情况下,fread 都返回了正确数量的字节。

<?php

VariableStream {
var
$position;
var
$varname;

function
stream_open($path, $mode, $options, &$opened_path)
{
$url = parse_url($path);
$this->varname = $url["host"];
$this->position = 0;

return
true;
}

function
stream_read($count)
{
echo
"stream_read called asking for $count bytes\n";
$ret = substr($GLOBALS[$this->varname], $this->position, $count);
$this->position += strlen($ret);
return
$ret;
}

function
stream_eof()
{
return
$this->position >= strlen($GLOBALS[$this->varname]);
}

}

stream_wrapper_register("var", "VariableStream")
or die(
"Failed to register protocol");

$myvar = "";
$l=range('a','z');
for(
$i=0;$i<65536;$i++) {
$myvar .= $l[array_rand($l)];
}

$fp = fopen("var://myvar", "r+");

while (!
feof($fp)) {
$out = fread($fp,1000);
echo
"fread returned ",strlen($out)," bytes\n";
}

fclose($fp);

?>
phrank
16 年前
实际上,我不知道除了这个方法之外,还有没有更好的方法来判断 stream_read() 是被 fgets() 还是其他方法(例如 fread())调用了。

public function stream_read($count)
{
$bt = debug_backtrace();
if(($bt[0]['function'] == 'stream_read') &&
($bt[1]['function'] == 'fgets'))
{
$pos = strpos($GLOBALS[$this->varname], "\n", $this->position);
$retval = substr($GLOBALS[$this->varname], $this->position, ($pos > $count) ? $count : $pos+1);

}
else
{
$retval = substr($GLOBALS[$this->varname], $this->position, $count);

}
$this->position += strlen($retval);
return (string)$retval;
}
none at your dot biz
17 年前
如果你也像我一样开始挠头,你应该把 VariableStream::stream_eof() 函数改成类似于下面的样子。

function stream_eof()
{
$eof = ($this->position >= strlen($GLOBALS[$this->varname]));
if(version_compare(PHP_VERSION,'5.0','>=') && version_compare(PHP_VERSION,'5.1','<'))
{
$eof = !$eof;
}
return $eof;
}

PHP 5.0 引入了一个 bug,直到 5.1 才修复。
jhannus at php dot net
19 年前
值得注意的是,如果你的包装器支持 stream_flush(),那么当你调用 flcose() 关闭你的流时,这个函数将在关闭流之前被调用。
yeiniel at gmail dot com
16 年前
在 PHP5 上使用 dir_opendir 时,确保你不会在成功时返回一个资源对象。资源对象与 false 不同,但 php 会将 dir_opendir 返回值强制转换为 bool 类型,并将你的资源的值修改为 1。

例子

class myclass{
private $mysqlHandler;
public function dir_opendir(....)
{
$this->mysqlHandler = mysql_connect(....);
return $this->mysqlHandler; // 这部分代码有误,请查看下一段
// 在下次使用
//$this->mysqlHandler
// 它的值是 1
}
}
dmarjos at gmail dot com
10 年前
请注意,即使 stream_wrapper_register 不会失败,open_basedir 也会影响该类的功能。
simon at firepages dot org
20 年前
使用流来使用非常有用的 fgetcsv() 函数在一个 explode() 无效的变量上(否则需要正则表达式,但那可能更容易;))

$explode_this="yak, llama, 'big llama', 'wobmat, with a comma in it', bandycoot";

<?php
class csvstream{
var
$position;
var
$varname;
function
stream_open($path, $mode, $options, &$opened_path){
$url = parse_url($path);
$this->varname = $url['host'] ;
$this->position = 0;
return
true;
}
function
stream_read($count){
$ret = substr($GLOBALS[$this->varname], $this->position, $count);
$this->position += strlen($ret);
return
$ret;
}
function
stream_eof(){
return
$this->position >= strlen($GLOBALS[$this->varname]);
}
function
stream_tell(){
return
$this->position;
}
}

stream_wrapper_register("csvstr", "csvstream") ;
$str="yak, llama, 'big llama', 'wobmat, with a comma in it', bandycoot";

$fp = fopen("csvstr://str", "r+");
print_r(fgetcsv($fp,100,",","'"));

?>
Anonymous
15 年前
"用于所有其他文件系统函数"

"所有" 不准确。不幸的是,zip_open() 和可能的其他函数会忽略自定义的流包装器。
Hayley Watson
19 年前
当前的 URL 标准是 RFC 3986 - 可在 www.ietf.org/rfc/rfc3986.txt 获取
Anonymouse at Coward dot com
17 年前
在使用 fread 与流包装器编写代码时要谨慎,因为 fread 的行为与普通文件操作“不一致”,这是由于 PHP(>= 5.0.5 IIRC)使用的 8192 字节内部缓冲区造成的。

例如

fread($filehandle, filesize($filename))

如果文件大小超过 8KB,则无法正常工作,它只会获取前 8192 字节。此外,似乎

fread($filehandle, 4096)

仍然会返回 8KB(如果文件大小超过 8KB),因为始终将 8192 字节作为计数传递给 stream_read。

这使得在不特别注意的情况下,几乎无法将流直接“插入”到原本使用文件的地方。

是的,如果您仔细阅读文档,它确实提到了这一点,但我个人花了一些时间才弄清楚,从错误跟踪器来看,我不是唯一一个。开发人员说这种不一致性是一个特性,即使它确实让流包装器在“开箱即用”的情况下几乎无用。

不过,file_get_contents 和 stream_get_contents 似乎工作正常。
To Top