请注意,当流(至少)是 socket 类型时,回调函数永远不会被调用。对于 file:// 协议可能是相同的,尽管我没有进行进一步的测试。
我正在使用 php 5.3.3。
(PHP 5 >= 5.2.0, PHP 7, PHP 8)
stream_notification_callback — 用于 notification
上下文参数的回调函数
$notification_code
,$severity
,$message
,$message_code
,$bytes_transferred
,$bytes_max
一个 callable 函数,由 notification 上下文参数 使用,在事件期间调用。
注意:
这不是一个真正的函数,只是一个函数应该如何工作的原型。
notification_code
STREAM_NOTIFY_*
通知常量之一。
severity
STREAM_NOTIFY_SEVERITY_*
通知常量之一。
message
如果事件有描述性消息,则传递。
message_code
如果事件有描述性消息代码,则传递。
此值的含义取决于使用的特定包装器。
bytes_transferred
如果适用,将填充 bytes_transferred
。
bytes_max
如果适用,将填充 bytes_max
。
不返回值。
示例 #1 stream_notification_callback() 示例
<?php
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
switch($notification_code) {
case STREAM_NOTIFY_RESOLVE:
case STREAM_NOTIFY_AUTH_REQUIRED:
case STREAM_NOTIFY_COMPLETED:
case STREAM_NOTIFY_FAILURE:
case STREAM_NOTIFY_AUTH_RESULT:
var_dump($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max);
/* 忽略 */
break;
case STREAM_NOTIFY_REDIRECTED:
echo "正在重定向到: ", $message;
break;
case STREAM_NOTIFY_CONNECT:
echo "已连接...";
break;
case STREAM_NOTIFY_FILE_SIZE_IS:
echo "获取文件大小: ", $bytes_max;
break;
case STREAM_NOTIFY_MIME_TYPE_IS:
echo "找到 MIME 类型: ", $message;
break;
case STREAM_NOTIFY_PROGRESS:
echo "已取得进展,已下载 ", $bytes_transferred, " 迄今为止";
break;
}
echo "\n";
}
$ctx = stream_context_create();
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
file_get_contents("https://php.net/contact", false, $ctx);
?>
上面的示例将输出类似以下内容
Connected... Found the mime-type: text/html; charset=utf-8 Being redirected to: http://no.php.net/contact Connected... Got the filesize: 0 Found the mime-type: text/html; charset=utf-8 Being redirected to: http://no.php.net/contact.php Connected... Got the filesize: 4589 Found the mime-type: text/html;charset=utf-8 Made some progress, downloaded 0 so far Made some progress, downloaded 0 so far Made some progress, downloaded 0 so far Made some progress, downloaded 1440 so far Made some progress, downloaded 2880 so far Made some progress, downloaded 4320 so far Made some progress, downloaded 5760 so far Made some progress, downloaded 6381 so far Made some progress, downloaded 7002 so far
示例 #2 命令行下载客户端的简单进度条
<?php
function usage($argv) {
echo "Usage:\n";
printf("\tphp %s <http://example.com/file> <localfile>\n", $argv[0]);
exit(1);
}
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
static $filesize = null;
switch($notification_code) {
case STREAM_NOTIFY_RESOLVE:
case STREAM_NOTIFY_AUTH_REQUIRED:
case STREAM_NOTIFY_COMPLETED:
case STREAM_NOTIFY_FAILURE:
case STREAM_NOTIFY_AUTH_RESULT:
/* Ignore */
break;
case STREAM_NOTIFY_REDIRECTED:
echo "Being redirected to: ", $message, "\n";
break;
case STREAM_NOTIFY_CONNECT:
echo "Connected...\n";
break;
case STREAM_NOTIFY_FILE_SIZE_IS:
$filesize = $bytes_max;
echo "Filesize: ", $filesize, "\n";
break;
case STREAM_NOTIFY_MIME_TYPE_IS:
echo "Mime-type: ", $message, "\n";
break;
case STREAM_NOTIFY_PROGRESS:
if ($bytes_transferred > 0) {
if (!isset($filesize)) {
printf("\rUnknown filesize.. %2d kb done..", $bytes_transferred/1024);
} else {
$length = (int)(($bytes_transferred/$filesize)*100);
printf("\r[%-100s] %d%% (%2d/%2d kb)", str_repeat("=", $length). ">", $length, ($bytes_transferred/1024), $filesize/1024);
}
}
break;
}
}
isset($argv[1], $argv[2]) or usage($argv);
$ctx = stream_context_create();
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
$fp = fopen($argv[1], "r", false, $ctx);
if (is_resource($fp) && file_put_contents($argv[2], $fp)) {
echo "\nDone!\n";
exit(0);
}
$err = error_get_last();
echo "\nErrrrrorr..\n", $err["message"], "\n";
exit(1);
?>
使用以下命令执行上面的示例:php -n fetch.php http://no2.php.net/get/php-5-LATEST.tar.bz2/from/this/mirror php-latest.tar.bz2
将输出类似的内容
Connected... Mime-type: text/html; charset=utf-8 Being redirected to: http://no2.php.net/distributions/php-5.2.5.tar.bz2 Connected... Filesize: 7773024 Mime-type: application/octet-stream [========================================> ] 40% (3076/7590 kb)
请注意,当流(至少)是 socket 类型时,回调函数永远不会被调用。对于 file:// 协议可能是相同的,尽管我没有进行进一步的测试。
我正在使用 php 5.3.3。
关于此处示例的一些说明(我正在使用 PHP 7.0.18)
1) fopen() 没有为我提供 STREAM_NOTIFY_PROGRESS,但 `file_get_contents()` 工作正常。
2) 传输 zip 文件时传输的字节数似乎总是少 8192 (8k)。这取决于文件类型,并且还会在文件大小低于 8k 时中断 STREAM_NOTIFY_PROGRESS。我修改了该函数,现在它在超过 8k 的 zip 文件上显示了正确的传输量和下载百分比。我不确定为什么回调如此不一致,但希望这对某些人有所帮助。这也经过修改,仅显示进度
<?php
$ctx = stream_context_create();
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
$fileData = @file_get_contents('http://example.com/test.zip',false,$ctx);
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
static $filesize = null;
switch($notification_code) {
case STREAM_NOTIFY_FILE_SIZE_IS:
$filesize = $bytes_max;
break;
case STREAM_NOTIFY_CONNECT:
echo "Connected...\n";
break;
case STREAM_NOTIFY_PROGRESS:
if ($bytes_transferred > 0 && $filesize >= 8192) {
$bytes_transferred += 8192;
if (!isset($filesize)) {
printf("\rUnknown filesize.. %2d kb done..", $bytes_transferred/1024);
} else {
$length = (int)(($bytes_transferred/$filesize)*100);
printf("\r[%-100s] %d%% (%2d/%2d kb)", str_repeat("=", $length). ">", $length, ($bytes_transferred/1024), $filesize/1024);
}
}
break;
}
}
?>
一些未报告的行为
- 情况 1) 如果此回调调用 die/exit($msg),它将打印 $msg,然后执行将继续直到请求/包装程序被消耗,在每次调用回调时发出“PHP Warning: Failed to call user notifier”。在最后一次回调调用之后,脚本立即终止。
- 情况 2) 如果此回调抛出异常,它将与 exit/die 的行为相同,除了在最后一次回调调用之后它不会终止脚本。异常改为在请求/包装程序的范围内引发,并且可以通过 try catch(在那里或在更高层级)捕获。
情况 1) 的示例代码。最终的“TEST ECHO”字符串不会被打印。
<?php
$context = stream_context_create(['http' => ['ignore_errors' => true,]]);
stream_context_set_params($context, ['notification' => function () {
die('error');
}]);
file_get_contents('https://www.google.com', false, $context);
echo "TEST ECHO";
?>
案例 2 的示例代码。异常在 file_get_contents 调用级别被抛出,捕获后,最终打印出“TEST ECHO”。
<?php
$context = stream_context_create(['http' => ['ignore_errors' => true,]]);
stream_context_set_params($context, ['notification' => function () {
throw new Exception('...');
}]);
try{
file_get_contents('https://www.google.com', false, $context);
}catch(exception $e) { }
echo "TEST ECHO";
?>