ZetCode

PHP 流

最后修改于 2025 年 3 月 13 日

PHP 中的流提供了一种统一的方式来处理输入和输出操作,例如从文件读取、写入网络套接字或处理来自外部源的数据。本教程涵盖了 PHP 流的基础知识,包括文件处理、网络通信和自定义流包装器。

基本文件流

PHP 提供了 fopenfreadfclose 等函数来处理文件流。这些函数允许您从文件读取和写入文件。

basic_file_stream.php
<?php

$logFile = fopen("app_logs.txt", "a");

if ($logFile) {

    $timestamp = date("Y-m-d H:i:s");
    fwrite($logFile, "[$timestamp] User logged in\n");
    fclose($logFile);
    echo "Log entry added.\n";
} else {
    echo "Failed to open log file.\n";
}

此示例模拟将日志条目追加到应用程序中的文件中。fopen 函数以追加模式 ("a") 打开 app_logs.txt,如果该文件不存在则创建它。这是跟踪用户活动或错误的系统中常见的任务。

如果流成功打开,则使用 fwrite 写入带时间戳的消息,并使用 fclose 关闭流以释放资源。输出确认该操作。流的这种实际应用演示了在真实世界的日志记录场景中基本的文件 I/O。

从文件流读取

您可以使用 freadfgets 从文件流读取数据。

read_file_stream.php
<?php

$configFile = fopen("config.ini", "r");

if ($configFile) {

    $settings = [];

    while (($line = fgets($configFile)) !== false) {

        $trimmed = trim($line);

        if ($trimmed && strpos($trimmed, "=") !== false) {
            [$key, $value] = explode("=", $trimmed, 2);
            $settings[$key] = $value;
        }
    }

    fclose($configFile);
    print_r($settings);
} else {
    echo "Failed to open config file.\n";
}

此示例读取配置文件 (config.ini) 以解析键值对,这是应用程序设置中的常见任务。假设 config.ini 包含类似 host=localhostport=8080 的行。该文件以读取模式 ("r") 打开。

fgets 函数读取每一行,代码跳过空行或没有等号的行。每行有效的行都被拆分为键和值,并存储在一个数组中。关闭流后,将打印设置。这显示了流如何实际处理结构化文件读取。

网络流

PHP 流也可用于网络通信,例如从远程 URL 读取或连接到套接字。

network_stream.php
<?php

$apiUrl = "https://api.openweathermap.org/data/2.5/weather?q=London&appid=YOUR_API_KEY";
$weatherStream = fopen($apiUrl, "r");

if ($weatherStream) {

    $response = stream_get_contents($weatherStream);
    $data = json_decode($response, true);
    echo "Temperature in London: " . ($data["main"]["temp"] - 273.15) . "°C\n";
    fclose($weatherStream);
} else {
    echo "Failed to fetch weather data.\n";
}

此示例从 OpenWeatherMap API 提取天气数据,这是网络流的实际应用。fopen 函数打开到 API URL 的流(将 YOUR_API_KEY 替换为有效的密钥)。这模拟了与外部服务的真实世界集成。

如果成功,stream_get_contents 读取整个 JSON 响应,并将其解码为数组。温度(以开尔文为单位)转换为摄氏度并显示。然后关闭流。这演示了流处理 HTTP 请求,这是 Web 应用程序中的常见要求。

流上下文

流上下文允许您配置流的选项,例如 HTTP 标头或 SSL 设置。

stream_context.php
<?php

$options = [
    "http" => [
        "method" => "POST",
        "header" => "Content-Type: application/json\r\n",
        "content" => json_encode(["user_id" => 123, "action" => "login"])
    ]
];

$context = stream_context_create($options);
$endpoint = "https://api.example.com/auth";
$stream = fopen($endpoint, "r", false, $context);

if ($stream) {
    $response = stream_get_contents($stream);
    echo "Server response: $response\n";
    fclose($stream);
} else {
    echo "Failed to send request.\n";
}

此示例向身份验证 API 发送 POST 请求,这是用户登录系统的真实场景。流上下文使用 stream_context_create 配置 HTTP 方法、标头和 JSON 负载。这种自定义对于与现代 API 交互至关重要。

fopen 函数使用上下文打开流,stream_get_contents 检索服务器的响应(例如,令牌)。此后关闭流。这说明了上下文如何增强网络通信的流,提供了超越简单 GET 请求的灵活性。

自定义流包装器

PHP 允许您创建自定义流包装器来处理自定义协议或数据源。

custom_wrapper.php
<?php

class MemoryLogger {
    private array $logs = [];
    private int $position = 0;

    public function stream_open(string $path, string $mode): bool {
        return true;
    }

    public function stream_write(string $data): int {
        $this->logs[] = $data;
        return strlen($data);
    }

    public function stream_read(int $count): string {
        if ($this->stream_eof()) {
            return "";
        }
        $data = implode("", $this->logs);
        $ret = substr($data, $this->position, $count);
        $this->position += strlen($ret);
        return $ret;
    }

    public function stream_eof(): bool {
        return $this->position >= strlen(implode("", $this->logs));
    }
}

stream_wrapper_register("memorylog", "MemoryLogger");

$logger = fopen("memorylog://debug", "w+");
fwrite($logger, "Error: Invalid input\n");
rewind($logger);
echo fread($logger, 1024);
fclose($logger);

此示例定义了一个 MemoryLogger 自定义流包装器,用于内存日志记录,这对于无需文件 I/O 的调试很有用。包装器将日志消息存储在数组中,模拟类似流的接口。它注册为 memorylog://

stream_write 方法附加数据,而 stream_read 从连接的日志中读取,跟踪位置。写入错误消息后,rewind 重置位置,而 fread 检索它。这种实用的包装器显示了流如何扩展到传统文件或网络之外。

流过滤器

流过滤器允许您在数据通过流时处理数据。PHP 提供了内置过滤器,如 string.toupperstring.tolower

stream_filter.php
<?php
$file = fopen("user_data.csv", "r");

if ($file) {
    stream_filter_append($file, "convert.iconv.UTF-8/ISO-8859-1");
    $header = fgets($file); // e.g., "name,email"
    while (($line = fgets($file)) !== false) {
        echo "Processed: $line";
    }
    fclose($file);
} else {
    echo "Failed to open CSV file.\n";
}

此示例使用字符编码过滤器处理 CSV 文件 (user_data.csv),这是从不同来源导入数据时的实际需求。假设该文件采用 UTF-8 编码,我们将其转换为 ISO-8859-1 以与旧系统兼容。

convert.iconv.UTF-8/ISO-8859-1 过滤器附加到流,在读取时转换数据。首先读取标头,然后处理并回显每一行。这演示了过滤器如何动态处理数据,避免了手动转换步骤。

流元数据

您可以使用 stream_get_meta_data 函数检索有关流的元数据。

stream_metadata.php
<?php

$backupFile = fopen("backup.tar.gz", "rb");

if ($backupFile) {
    $metadata = stream_get_meta_data($backupFile);
    echo "Stream wrapper: " . $metadata["wrapper_type"] . "\n";
    echo "File size: " . filesize("backup.tar.gz") . " bytes\n";
    fclose($backupFile);
} else {
    echo "Failed to open backup file.\n";
}

此示例检查压缩备份文件 (backup.tar.gz) 的元数据,该文件以二进制读取模式 ("rb") 打开。这对于在存档或传输任务中验证流很有用。

stream_get_meta_data 函数返回详细信息,例如包装器类型(例如,plainfile)和模式。在这里,我们显示包装器类型,并使用 filesize 对其进行补充以提供上下文。关闭流可确保资源清理。这显示了元数据如何帮助流管理。

写入压缩流

PHP 通过像 compress.zlib 这样的包装器支持压缩流。

compress_stream.php
<?php

$archive = fopen("compress.zlib://data.gz", "wb");

if ($archive) {
    fwrite($archive, "Sensitive data: User IDs and emails\n");
    fclose($archive);
    echo "Data compressed and saved.\n";
} else {
    echo "Failed to open compressed stream.\n";
}

此示例使用 compress.zlib 包装器写入 gzip 压缩文件,这是一种有效存储日志或数据的实用方法。文件 data.gz 以二进制写入模式 ("wb") 打开,在写入时压缩数据。

如果流打开,fwrite 添加一个字符串,该字符串会自动压缩,而 fclose 完成该文件。结果是一个比纯文本更小的文件,非常适合备份或传输。这利用流进行内置压缩,无需外部工具。

流式传输大文件

流通过分块读取或写入来有效处理大文件。

large_file_stream.php
<?php

$source = fopen("large_video.mp4", "rb");
$dest = fopen("video_copy.mp4", "wb");

if ($source && $dest) {

    while (!feof($source)) {
        $chunk = fread($source, 8192); // 8KB chunks
        fwrite($dest, $chunk);
    }
    fclose($source);
    fclose($dest);
    echo "Video file copied successfully.\n";
} else {
    echo "Failed to open files.\n";
}

此示例将大视频文件 (large_video.mp4) 复制到一个新文件,这是媒体处理中的常见任务。源和目标都以二进制模式 ("rb""wb") 打开,以保留数据完整性。

while (!feof($source)) 循环使用 fread 读取 8KB 的块,将每个块写入目标流。这避免了将整个文件加载到内存中,使其对千兆字节大小的文件高效。完成后,关闭两个流,并确认成功。

套接字流

流可以处理套接字通信,用于实时应用程序。

socket_stream.php
<?php

$socket = stream_socket_client("tcp://:8080", $errno, $errstr, 30);

if ($socket) {

    fwrite($socket, "GET /status HTTP/1.1\r\nHost: localhost\r\n\r\n");
    $response = stream_get_contents($socket);
    echo "Server response: $response\n";
    fclose($socket);
} else {
    echo "Failed to connect: $errstr ($errno)\n";
}

此示例连接到本地 TCP 服务器,端口为 8080,模拟网络应用程序中的简单客户端(例如,状态检查)。stream_socket_client 函数建立套接字流,超时时间为 30 秒。

如果成功,则通过 fwrite 发送 HTTP GET 请求,stream_get_contents 读取响应(假设服务器回复状态)。此后关闭流。这演示了流在实时通信中的应用,这是聊天或监控系统的关键特性。

流的最佳实践

来源

PHP 流文档

在本教程中,我们探讨了如何在 PHP 中使用流,包括文件处理、网络通信、自定义流包装器和流过滤器。流提供了一种强大而灵活的方式来处理 PHP 中的输入和输出。

作者

我叫 Jan Bodnar,是一名充满激情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我撰写了 1400 多篇文章和 8 本电子书。我拥有超过八年的编程教学经验。

列出 所有 PHP 教程