Please note that the callback function never gets called when the stream is (at least) of type socket. It is probably the same for the file:// proto although I've not done further tests.
I'm using php 5.3.3.
(PHP 5 >= 5.2.0, PHP 7, PHP 8)
stream_notification_callback — Callback-функция для параметра контекста notification
$notification_code
,$severity
,$message
,$message_code
,$bytes_transferred
,$bytes_max
Callback-функция типа 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("http://php.net/contact", false, $ctx);
?>
Вывод приведённого примера будет похож на:
Подсоединились... Получили mime-тип файла: text/html; charset=utf-8 Перенаправлены на: http://no.php.net/contact Подсоединились... Получили размер файла: 0 Получили mime-тип файла: text/html; charset=utf-8 Перенаправлены на: http://no.php.net/contact.php Подсоединились... Получили размер файла: 4589 Получили mime-тип файла: text/html;charset=utf-8 Пошёл прогресс, пока загружено 0 байт Пошёл прогресс, пока загружено 0 байт Пошёл прогресс, пока загружено 0 байт Пошёл прогресс, пока загружено 1440 байт Пошёл прогресс, пока загружено 2880 байт Пошёл прогресс, пока загружено 4320 байт Пошёл прогресс, пока загружено 5760 байт Пошёл прогресс, пока загружено 6381 байт Пошёл прогресс, пока загружено 7002 байт
Пример #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 Кб..", $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
выведет что-то похожее на это:
Подсоединились... Mime-тип файла: text/html; charset=utf-8 Перенаправлены на: http://no2.php.net/distributions/php-5.2.5.tar.bz2 Подсоединились... Размер файла: 7773024 Mime-тип файла: application/octet-stream [========================================> ] 40% (3076/7590 kb)
Please note that the callback function never gets called when the stream is (at least) of type socket. It is probably the same for the file:// proto although I've not done further tests.
I'm using php 5.3.3.
A couple of notes regarding the examples here (I'm using PHP 7.0.18):
1) fopen() did not provide STREAM_NOTIFY_PROGRESS for me but `file_get_contents()` worked fine.
2) The bytes transferred when transferring a zip file seems to always be shy by 8192 (8k). This varies depending on filetype and also breaks STREAM_NOTIFY_PROGRESS when the filesize is below 8k. I modified the function and it's now displaying the correct amount transferred and percentage downloaded on zip files above 8k. I'm not sure why the callback is so inconsistent but hopefully this is helpful to someone. This was also modified to only display progress:
<?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;
}
}
?>
A couple of unreported behaviors:
- case 1) if this callback calls die/exit($msg), it will print $msg, then the execution will go on until the request/wrapper is consumed, emitting a "PHP Warning: Failed to call user notifier" on each invocation of the callback. After the last callback invocation, the script is immediately terminated.
- case 2) if this callback throws an exception, it will behave the same way as exit/die, except for the fact that after the last callback invocation it does not terminate the script. The exception is instead raised in the scope of the request/wrapper, and can be caught with a try catch (right there or at a higher level).
Example code for case 1). The final "TEST ECHO" string will NOT be printed.
<?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";
?>
Example code for case 2). The exception is raised at the level of the file_get_contents call, it's catched, and the final "TEST ECHO" gets printed.
<?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";
?>