2024 日本 PHP 研討會

stream_notification_callback

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

stream_notification_callback用於 notification 上下文參數的回呼函式

說明

stream_notification_callback(
    int $notification_code,
    int $severity,
    ?字串 $message,
    整數 $message_code,
    整數 $bytes_transferred,
    整數 $bytes_max
):

一個 可呼叫 的函式,由 通知上下文參數 使用,在事件期間呼叫。

注意事項:

不是一個真正的函式,只是一個函式應如何編寫的原型。

參數

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.dev.org.tw/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)

新增註記

使用者貢獻的註記 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。我修改了函式,現在它在超過 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;
}
}
?>
aetonsi
2 年前
一些未被回報的行為
- 案例 1:如果這個回呼函式呼叫 die/exit($msg),它會印出 $msg,然後執行會繼續,直到請求/包裝器被消耗完畢,每次呼叫回呼函式時都會發出「PHP 警告:呼叫使用者通知器失敗」。在最後一次呼叫回呼函式後,腳本會立即終止。
- 情況 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