PHP 怎么自定义协议

wen PHP项目 2

本文目录导读:

PHP 怎么自定义协议

  1. 基本概念
  2. 简单示例:data:// 风格的协议
  3. 完整功能实现:一个缓冲流协议
  4. 使用 context 选项
  5. 完整的数据压缩协议示例
  6. 注册和检查协议
  7. 注意事项
  8. 实际应用场景

在 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');

注意事项

  1. 必须实现的方法:所有 stream_ 开头的方法都必须实现,否则某些函数可能无法正常工作
  2. SEO 支持:PHP 8.0+ 引入了 stream_wrapper_register() 方法的改进
  3. 错误处理:当读取或写入失败时,返回 false
  4. 缓存安全url_stat() 用于支持 stat()is_file() 等函数

实际应用场景

  • 访问数据库数据(如 db://table/record
  • 访问 API 服务(如 api://users/123
  • 加密/解密数据流
  • 内存虚拟文件系统
  • 自定义数据源封装

使用自定义协议可以创建统一的数据访问接口,使代码更加模块化和可维护。

抱歉,评论功能暂时关闭!