本文目录导读:

在 PHP 中自定义协议通常指的是实现自定义的流包装器(Stream Wrapper),允许你像操作文件一样使用 file_get_contents()、fopen() 等函数来处理自定义的数据源。
以下是完整的实现指南:
基本概念
PHP 流包装器允许你用统一的接口访问各种数据源,如 http://、ftp://、file:// 等。
简单示例:data:// 风格的协议
<?php
class MyProtocol {
private $data;
private $position = 0;
private $content = '';
// 必须实现 StreamWrapper 接口的所有方法
public function stream_open($path, $mode, $options, &$opened_path) {
// 解析协议参数
// myproto://hello/world
$url = parse_url($path);
$this->content = "自定义数据: " . $url['host'] . $url['path'];
return true;
}
public function stream_read($count) {
$data = substr($this->content, $this->position, $count);
$this->position += strlen($data);
return $data;
}
public function stream_write($data) {
$this->content .= $data;
return strlen($data);
}
public function stream_tell() {
return $this->position;
}
public function stream_eof() {
return $this->position >= strlen($this->content);
}
public function stream_seek($offset, $whence) {
switch ($whence) {
case SEEK_SET:
$this->position = $offset;
break;
case SEEK_CUR:
$this->position += $offset;
break;
case SEEK_END:
$this->position = strlen($this->content) + $offset;
break;
}
return true;
}
public function stream_stat() {
return [
'size' => strlen($this->content),
'mode' => 0100777, // 普通文件权限
];
}
}
// 注册协议
stream_wrapper_register('myproto', 'MyProtocol');
// 使用协议
$content = file_get_contents('myproto://hello/world');
echo $content; // 输出: 自定义数据: helloworld
完整功能实现:一个缓冲流协议
<?php
class BufferedStream {
private $buffer = '';
private $position = 0;
private $context;
public $stream_mode = 'rb';
public $stream_options = [];
public function stream_open($path, $mode, $options, &$opened_path) {
$this->buffer = '';
$this->position = 0;
$this->stream_mode = $mode;
return true;
}
public function stream_close() {
// 清理资源
return true;
}
public function stream_read($count) {
if ($this->position + $count > strlen($this->buffer)) {
$count = strlen($this->buffer) - $this->position;
}
$data = substr($this->buffer, $this->position, $count);
$this->position += strlen($data);
return $data;
}
public function stream_write($data) {
$this->buffer .= $data;
return strlen($data);
}
public function stream_tell() {
return $this->position;
}
public function stream_eof() {
return $this->position >= strlen($this->buffer);
}
public function stream_seek($offset, $whence) {
switch ($whence) {
case SEEK_SET:
$new_pos = $offset;
break;
case SEEK_CUR:
$new_pos = $this->position + $offset;
break;
case SEEK_END:
$new_pos = strlen($this->buffer) + $offset;
break;
default:
return false;
}
if ($new_pos < 0) return false;
$this->position = $new_pos;
return true;
}
public function stream_flush() {
// 将缓冲区内容写入某处
return true;
}
public function stream_stat() {
return [
'size' => strlen($this->buffer),
'mode' => 0100777,
];
}
public function stream_lock($operation) {
return true;
}
public function url_stat($path, $flags) {
return [
'size' => strlen($this->buffer),
'mode' => 0100777,
];
}
public function stream_set_option($option, $arg1, $arg2) {
return false;
}
}
// 注册协议
stream_wrapper_register('buffered', 'BufferedStream');
// 测试
$fp = fopen('buffered://test', 'w');
fwrite($fp, "Hello World");
fclose($fp);
// 读取
$fp = fopen('buffered://test', 'r');
$content = stream_get_contents($fp);
fclose($fp);
echo $content; // 输出: Hello World
使用 context 选项
<?php
class ContextAwareStream {
public $context; // 必须持有 context
public function stream_open($path, $mode, $options, &$opened_path) {
// 获取 context 选项
$context = stream_context_get_options($this->context);
if (isset($context['myproto'])) {
$options = $context['myproto'];
if (isset($options['prefix'])) {
// 根据配置初始化
}
}
return true;
}
}
// 创建 context 选项
$opts = [
'myproto' => [
'prefix' => '自定义前缀'
]
];
$context = stream_context_create($opts);
// 使用 context
$fp = fopen('myproto://data', 'r', false, $context);
完整的数据压缩协议示例
<?php
class CompressedStream {
private $data;
private $position = 0;
private $compressed = false;
public function stream_open($path, $mode, $options, &$opened_path) {
// 解析路径: compress://filename.gz
$url = parse_url($path);
$this->compressed = strtolower($url['host']) === 'gz';
return true;
}
public function stream_read($count) {
// 如果需要,解压数据
if ($this->compressed && !$this->data) {
$this->data = gzdecode($this->compressData());
}
return substr($this->data, $this->position, $count);
}
private function compressData() {
return "压缩还原的数据";
}
public function stream_eof() {
return $this->position >= strlen($this->data);
}
public function stream_write($data) {
if ($this->compressed) {
$data = gzencode($data);
}
$this->data .= $data;
return strlen($data);
}
}
stream_wrapper_register('compress', 'CompressedStream');
注册和检查协议
<?php
// 注册协议
stream_wrapper_register('myproto', 'MyProtocol');
// 检查协议是否已注册
if (in_array('myproto', stream_get_wrappers())) {
echo "协议已注册";
}
// 列出所有已注册的协议
print_r(stream_get_wrappers());
// 注销协议
stream_wrapper_unregister('myproto');
注意事项
- 必须实现的方法:所有
stream_开头的方法都必须实现,否则某些函数可能无法正常工作 - SEO 支持:PHP 8.0+ 引入了
stream_wrapper_register()方法的改进 - 错误处理:当读取或写入失败时,返回
false - 缓存安全:
url_stat()用于支持stat()和is_file()等函数
实际应用场景
- 访问数据库数据(如
db://table/record) - 访问 API 服务(如
api://users/123) - 加密/解密数据流
- 内存虚拟文件系统
- 自定义数据源封装
使用自定义协议可以创建统一的数据访问接口,使代码更加模块化和可维护。