PHP Conference Japan 2024

stream_notification_callback

(PHP 5 >= 5.2.0, PHP 7, PHP 8)

stream_notification_callback用于notification上下文参数的回调函数

描述

stream_notification_callback(
    int $notification_code,
    int $severity,
    ?string $message,
    int $message_code,
    int $bytes_transferred,
    int $bytes_max
): void

一个callable 函数,由notification 上下文参数使用,在事件期间调用。

注意:

这不是一个真正的函数,只是一个函数应该如何实现的原型。

参数

notification_code

STREAM_NOTIFY_* 通知常量之一。

severity

STREAM_NOTIFY_SEVERITY_* 通知常量之一。

message

如果事件有描述性消息可用,则传递。

message_code

如果事件有描述性消息代码可用,则传递。

此值的含义取决于使用的特定封装器。

bytes_transferred

如果适用,将填充bytes_transferred

bytes_max

如果适用,将填充bytes_max

返回值

不返回任何值。

变更日志

版本 描述
8.3.0 实现了对STREAM_NOTIFY_COMPLETED 的支持,早期的PHP版本永远不会触发此通知。

示例

示例 #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
"用法:\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:
/*忽略*/
break;

case
STREAM_NOTIFY_REDIRECTED:
echo
"正在重定向到:", $message, "\n";
break;

case
STREAM_NOTIFY_CONNECT:
echo
"已连接...\n";
break;

case
STREAM_NOTIFY_FILE_SIZE_IS:
$filesize = $bytes_max;
echo
"文件大小:", $filesize, "\n";
break;

case
STREAM_NOTIFY_MIME_TYPE_IS:
echo
"MIME类型:", $message, "\n";
break;

case
STREAM_NOTIFY_PROGRESS:
if (
$bytes_transferred > 0) {
if (!isset(
$filesize)) {
printf("\r未知文件大小.. 已完成 %2d kb..\n", $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
"\n完成!\n";
exit(
0);
}

$err = error_get_last();
echo
"\n错误..\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)

添加备注

用户贡献的备注 2 条备注

billynoah at gmail dot com
7 年前
关于此处示例的一些说明(我使用的是 PHP 7.0.18)

1) `fopen()` 没有为我提供 `STREAM_NOTIFY_PROGRESS`,但 `file_get_contents()` 工作正常。

2) 传输 zip 文件时传输的字节数似乎总是少 8192(8k)。这取决于文件类型而有所不同,并且当文件大小小于 8k 时也会破坏 `STREAM_NOTIFY_PROGRESS`。我修改了该函数,它现在显示了 zip 文件(大于 8k)传输的正确数量和下载的百分比。我不确定为什么回调如此不一致,但希望这对某些人有所帮助。此修改也只显示进度

<?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
"已连接...\n";
break;
case
STREAM_NOTIFY_PROGRESS:
if (
$bytes_transferred > 0 && $filesize >= 8192) {
$bytes_transferred += 8192;
if (!isset(
$filesize)) {
printf("\r未知文件大小.. 已完成 %2d kb..\n", $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;
}
}
?>
aetonsi
2 年前
一些未报告的行为
-情况 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";
?>
To Top