本文目录导读:

在 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) - 加密/解密数据流
- 内存虚拟文件系统
- 自定义数据源封装
使用自定义协议可以创建统一的数据访问接口,使代码更加模块化和可维护。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播