自定义流包装器允许用文件操作函数处理非文件资源,通过继承StreamWrapper类并实现如stream_open、stream_read等方法,再使用stream_wrapper_register注册协议,即可实现如内存数据、远程API等统一文件式访问。

PHP自定义流包装器,说白了,就是让你能用
fopen()
file_get_contents()
要创建和使用自定义的PHP流包装器,我们主要需要做两件事:定义一个实现特定接口的类,然后将这个类注册成一个流协议。
我们先从定义类开始。这个类需要实现
php_user_stream_wrapper
StreamWrapper
StreamWrapper
这个自定义的流包装器类,它的核心职责是模拟文件操作的行为。这意味着,当PHP尝试对你的自定义协议资源执行
open
read
write
close
seek
立即学习“PHP免费学习笔记(深入)”;
例如,一个最基础的流包装器,至少需要实现
stream_open
stream_read
stream_write
stream_close
stream_open
myproto://data
r
w
a
true
false
stream_read
stream_write
stream_close
stream_stat
url_stat
stream_stat
url_stat
实现完这个类后,下一步就是使用
stream_wrapper_register()
myproto
<?php
class MyCustomStream
{
private $position;
private $data;
private $mode;
// stream_open 负责打开流,初始化资源
public function stream_open($path, $mode, $options, &$opened_path)
{
// 假设我们的协议是 myproto://<some_data>
// path 会是 myproto://hello_world 或 myproto://some_key
$resourceName = substr($path, strpos($path, '://') + 3);
$this->mode = $mode;
$this->position = 0;
// 这里可以根据 $resourceName 从数据库、API、内存中获取数据
// 简单起见,我们直接用 $resourceName 作为数据
if (strpos($mode, 'w') !== false || strpos($mode, 'a') !== false) {
// 写入模式,初始化为空
$this->data = '';
} else {
// 读取模式,假设数据就是资源名本身
$this->data = "Hello from custom stream: " . $resourceName;
}
return true; // 成功打开
}
// stream_read 负责从流中读取数据
public function stream_read($count)
{
$ret = substr($this->data, $this->position, $count);
$this->position += strlen($ret);
return $ret;
}
// stream_write 负责向流中写入数据
public function stream_write($data)
{
if (strpos($this->mode, 'w') !== false || strpos($this->mode, 'a') !== false) {
$left = substr($this->data, 0, $this->position);
$right = substr($this->data, $this->position + strlen($data)); // 如果是覆盖写
$this->data = $left . $data . $right;
$this->position += strlen($data);
return strlen($data);
}
return 0; // 不支持写入
}
// stream_tell 报告当前位置
public function stream_tell()
{
return $this->position;
}
// stream_eof 检查是否到达文件末尾
public function stream_eof()
{
return $this->position >= strlen($this->data);
}
// stream_seek 移动文件指针
public function stream_seek($offset, $whence = SEEK_SET)
{
switch ($whence) {
case SEEK_SET:
if ($offset >= 0 && $offset <= strlen($this->data)) {
$this->position = $offset;
return true;
}
break;
case SEEK_CUR:
if ($this->position + $offset >= 0 && $this->position + $offset <= strlen($this->data)) {
$this->position += $offset;
return true;
}
break;
case SEEK_END:
if (strlen($this->data) + $offset >= 0 && strlen($this->data) + $offset <= strlen($this->data)) {
$this->position = strlen($this->data) + $offset;
return true;
}
break;
}
return false;
}
// stream_stat 获取流的统计信息
public function stream_stat()
{
// 这是一个简化的 stat 数组,实际应用中需要更完整
return [
'size' => strlen($this->data),
'mode' => 0100644, // 默认文件模式
// 其他信息根据需要填充
];
}
// url_stat 获取URL的统计信息(在流打开之前)
public function url_stat($path, $flags)
{
// 这里可以根据 $path 判断资源是否存在,并返回其统计信息
// 简单起见,我们假设所有资源都存在且可读
return [
'size' => 100, // 示例大小
'mode' => 0100644,
];
}
// stream_close 关闭流
public function stream_close()
{
// 清理资源,例如断开数据库连接
// echo "Stream closed for " . $this->data . PHP_EOL;
}
}
// 注册我们的自定义流包装器
if (stream_wrapper_register("myproto", "MyCustomStream")) {
echo "Custom stream 'myproto' registered successfully.\n";
// 使用 file_get_contents 读取自定义流
$content = file_get_contents("myproto://test_resource");
echo "Content from myproto://test_resource: " . $content . PHP_EOL;
// 使用 fopen 和 fread 读取
$handle = fopen("myproto://another_resource", "r");
if ($handle) {
echo "Reading from myproto://another_resource: ";
while (!feof($handle)) {
echo fread($handle, 8); // 每次读8字节
}
echo PHP_EOL;
fclose($handle);
}
// 尝试写入(如果 stream_write 支持)
$writeHandle = fopen("myproto://writable_data", "w");
if ($writeHandle) {
fwrite($writeHandle, "This is some custom data.");
fclose($writeHandle);
// 重新打开读取,看看是否写入成功
$readWritten = file_get_contents("myproto://writable_data");
echo "Content from myproto://writable_data after write: " . $readWritten . PHP_EOL;
}
} else {
echo "Failed to register custom stream 'myproto'.\n";
}
// 可以选择注销
// stream_wrapper_unregister("myproto");
?>这个例子展示了一个非常基础的内存流包装器,它将资源名本身作为数据。实际应用中,
$this->data
在我看来,自定义流包装器最迷人的地方在于它提供了一种“统一接口”的抽象能力。它能把各种异构的数据源,都伪装成文件系统资源,从而让你能用一套熟悉的文件操作函数去处理它们。这不仅仅是代码整洁的问题,更是架构层面上的一种解耦和简化。
比如,我们经常会遇到这样的场景:
s3://
httpapi://
file_get_contents('s3://my-bucket/path/to/file.txt')mem://
file_put_contents('mem://temp_data', $largeString)mem://temp_data
encrypt://
compress://
db://
fopen('db://users/123')这些场景都体现了流包装器强大的抽象能力,它让复杂的问题变得简单,让不同的数据源拥有了统一的接口。
在实现自定义流包装器时,有些方法是基石,它们构成了流操作的核心逻辑。理解并正确实现它们至关重要。
stream_open(string $path, string $mode, int $options, string &$opened_path)
$path
$mode
'r'
'w'
'a'
'x'
$path
$mode
false
$opened_path
$path
true
false
stream_read(int $count)
$count
$this->position
''
false
''
stream_write(string $data)
$data
'w'
'a'
'x'
$data
0
stream_close()
stream_eof()
$this->position >= strlen($this->data)
true
false
feof()
stream_seek(int $offset, int $whence = SEEK_SET)
$whence
SEEK_SET
SEEK_CUR
SEEK_END
true
false
fseek()
stream_tell()
ftell()
除了这些核心方法,
stream_stat()
url_stat()
filesize()
stat()
注册和注销流包装器是使用它的最后一步,也是一个需要注意细节的地方。这不仅仅是调用函数那么简单,还需要考虑程序的生命周期和潜在的冲突。
注册流包装器:stream_wrapper_register(string $protocol, string $classname, int $flags = 0)
$protocol
myproto
file
http
ftp
php
$classname
MyCustomStream
$flags
STREAM_WRAPPER_REGISTER_URL_HACK
url_stat
unlink
file_exists()
is_readable()
in_array($protocol, stream_get_wrappers())
$classname
stream_wrapper_register()
// 示例:安全注册
$protocol = "myproto";
$className = "MyCustomStream";
if (!in_array($protocol, stream_get_wrappers())) {
if (stream_wrapper_register($protocol, $className, STREAM_WRAPPER_REGISTER_URL_HACK)) {
echo "Stream wrapper '$protocol' registered successfully.\n";
} else {
echo "Failed to register stream wrapper '$protocol'.\n";
}
} else {
echo "Stream wrapper '$protocol' is already registered.\n";
}注销流包装器:stream_wrapper_unregister(string $protocol)
true
false
// 示例:注销
if (in_array($protocol, stream_get_wrappers())) {
if (stream_wrapper_unregister($protocol)) {
echo "Stream wrapper '$protocol' unregistered successfully.\n";
} else {
echo "Failed to unregister stream wrapper '$protocol'.\n";
}
} else {
echo "Stream wrapper '$protocol' is not registered.\n";
}恢复内置流包装器:stream_wrapper_restore(string $protocol)
stream_wrapper_unregister()
file
http
以上就是php如何创建和使用自定义的流包装器 php自定义Stream Wrapper开发指南的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号