流上下文选项
概述
流上下文(Stream Context)是 PHP 流系统的重要组成部分,用于为流操作提供配置参数。stream_context_create() 创建上下文,stream_context_set_option() 修改选项。不同的包装器支持不同的上下文选项,如 HTTP 的请求头和超时设置、Socket 的绑定地址、SSL 的证书配置等。
适用场景
- 自定义 HTTP 请求头
- 配置 SSL/TLS 选项
- 设置 Socket 连接参数
- FTP 覆盖写入配置
基础概念
核心函数
| 函数 | 功能 |
|---|---|
stream_context_create() | 创建流上下文 |
stream_context_set_option() | 设置上下文选项 |
stream_context_get_options() | 获取上下文选项 |
stream_context_get_default() | 获取默认上下文 |
stream_context_set_default() | 设置默认上下文 |
上下文传递
流上下文可以通过 fopen()、file_get_contents() 等函数的第三个参数传递。
语法与代码示例
创建 HTTP 上下文
php
<?php
// 创建 HTTP 请求上下文
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => [
'Content-Type: application/json',
'Accept: application/json',
'Authorization: Bearer abc123',
],
'content' => json_encode(['name' => 'test']),
'timeout' => 30,
'ignore_errors' => true,
'follow_location' => 5,
'max_redirects' => 5,
'protocol_version' => '1.1',
],
]);
$response = file_get_contents('https://api.example.com/data', false, $context);Socket 上下文
php
<?php
// Socket 上下文:绑定源 IP
$context = stream_context_create([
'socket' => [
'bindto' => '192.168.1.100:0', // 绑定指定 IP
'backlog' => 128,
'verify_peer' => false,
],
]);
// 绑定指定网卡发起连接
$fp = stream_socket_client('tcp://example.com:80', $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
// 绑定到指定 IPv6
$context = stream_context_create([
'socket' => [
'bindto' => '[::1]:0',
],
]);SSL/TLS 上下文
php
<?php
// SSL 安全配置
$context = stream_context_create([
'ssl' => [
// 证书验证
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
// CA 证书路径
'cafile' => '/etc/ssl/certs/ca-certificates.crt',
'capath' => '/etc/ssl/certs/',
// 协议版本
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
// SNI(Server Name Indication)
'peer_name' => 'example.com',
// 证书和密钥(客户端认证)
'local_cert' => '/path/to/client.crt',
'local_pk' => '/path/to/client.key',
// 密码套件
'ciphers' => 'HIGH:!aNULL:!MD5',
],
]);
// 发起 HTTPS 请求
$handle = fopen('https://example.com/api', 'rb', false, $context);FTP 上下文
php
<?php
// FTP 上下文选项
$context = stream_context_create([
'ftp' => [
'overwrite' => true, // 覆盖远程文件
'resume_pos' => 0, // 断点续传位置
],
'http' => [
'proxy' => 'tcp://proxy.example.com:8080',
'request_fulluri' => true,
],
]);
// FTP 上传
file_put_contents(
'ftp://user:pass@ftp.example.com/file.txt',
'Hello FTP',
0,
$context
);修改和获取上下文选项
php
<?php
// 创建上下文
$context = stream_context_create();
// 动态设置选项
stream_context_set_option($context, [
'http' => [
'method' => 'GET',
'header' => 'Accept: application/json',
'timeout' => 15,
],
]);
// 获取所有选项
$options = stream_context_get_options($context);
print_r($options);
// 设置默认上下文(后续所有流操作使用此默认值)
$defaultContext = stream_context_set_default([
'http' => [
'timeout' => 10,
'header' => 'User-Agent: MyApp/1.0',
],
]);
// 之后的 file_get_contents 自动使用默认上下文
$data = file_get_contents('https://api.example.com/data');实战示例
带代理的 HTTP 上下文
php
<?php
declare(strict_types=1);
class ProxyHttpClient
{
private string $proxyHost;
private int $proxyPort;
private ?string $proxyUser;
private ?string $proxyPass;
public function __construct(
string $proxyHost,
int $proxyPort,
?string $proxyUser = null,
?string $proxyPass = null
) {
$this->proxyHost = $proxyHost;
$this->proxyPort = $proxyPort;
$this->proxyUser = $proxyUser;
$this->proxyPass = $proxyPass;
}
public function get(string $url, int $timeout = 30): string
{
$proxyAuth = '';
if ($this->proxyUser && $this->proxyPass) {
$proxyAuth = base64_encode($this->proxyUser . ':' . $this->proxyPass);
}
$headers = ['User-Agent: PHPProxyClient/1.0'];
if ($proxyAuth) {
$headers[] = "Proxy-Authorization: Basic {$proxyAuth}";
}
$context = stream_context_create([
'http' => [
'method' => 'GET',
'timeout' => $timeout,
'header' => $headers,
'proxy' => "tcp://{$this->proxyHost}:{$this->proxyPort}",
'request_fulluri' => true,
'ignore_errors' => true,
],
]);
$response = @file_get_contents($url, false, $context);
if ($response === false) {
throw new RuntimeException("通过代理请求失败: {$url}");
}
return $response;
}
}
// 使用
$client = new ProxyHttpClient('proxy.example.com', 8080, 'user', 'pass');
$data = $client->get('https://api.example.com/data');SSL 双向认证
php
<?php
declare(strict_types=1);
// mTLS(双向 TLS 认证)
$context = stream_context_create([
'ssl' => [
// 客户端证书
'local_cert' => '/path/to/client.pem',
'local_pk' => '/path/to/client-key.pem',
// 服务器证书验证
'verify_peer' => true,
'verify_peer_name' => true,
'cafile' => '/path/to/ca.pem',
// SNI
'peer_name' => 'secure.example.com',
// 协议
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
],
]);
$fp = stream_socket_client(
'ssl://secure.example.com:443',
$errno,
$errstr,
30,
STREAM_CLIENT_CONNECT,
$context
);
if (!$fp) {
throw new RuntimeException("SSL 连接失败: [{$errno}] {$errstr}");
}
fwrite($fp, "GET /api HTTP/1.1\r\nHost: secure.example.com\r\n\r\n");
$response = stream_get_contents($fp);
fclose($fp);注意事项
上下文生命周期
php
<?php
// 上下文是资源类型,可以被多个流共享
$context = stream_context_create([
'http' => ['timeout' => 10],
]);
// 多个请求复用同一个上下文
$data1 = file_get_contents('https://api1.example.com', false, $context);
$data2 = file_get_contents('https://api2.example.com', false, $context);
$data3 = file_get_contents('https://api3.example.com', false, $context);默认上下文影响范围
php
<?php
// stream_context_set_default 会影响所有未显式指定上下文的流操作
// 使用后记得清理或在需要的地方显式传递上下文
$oldDefault = stream_context_set_default([
'http' => ['timeout' => 5],
]);
// ... 使用默认超时的操作
// 恢复旧的默认上下文(PHP 没有直接恢复的 API,需要保存引用)最佳实践
1. 封装上下文创建
php
<?php
class StreamContextFactory
{
public static function createHttp(array $options = []): resource
{
$defaults = [
'http' => [
'timeout' => 30,
'ignore_errors' => true,
'follow_location' => 5,
'header' => ['User-Agent: MyApp/1.0'],
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
];
return stream_context_create(
array_merge_recursive($defaults, $options)
);
}
}2. 超时配置
php
<?php
// HTTP 总超时(连接 + 传输)
$context = stream_context_create([
'http' => ['timeout' => 10],
]);
// Socket 连接超时
$fp = stream_socket_client('tcp://example.com:80', $errno, $errstr, 10);
// 流读写超时
$fp = fopen('https://example.com', 'r');
stream_set_timeout($fp, 5);进阶用法
调试与测试技巧
php
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
php
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
php
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
php
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');