How to display loading percentage and how to do it without javascript?
Asked Answered
P

21

7

I want to make something similar to loaders in PHP so I used this code:

<?php 
$x=1;
while($x<=100) {
   echo "Loading: $x %<br>";
   $x++;
}   
?>

So that it would display from "Loading 1%" to "Loading 100%". But this will result in all appearing one at a time without disappearing after the new line appears. So I want to know how to make the new line appear and the old disappear and this starts after the page loads, so the user will be able to watch a loader actually loading from 1% to 100%.

UPDATE: I know I should use JS and/or Ajax to achieve it, I just wanted to know if there's a way to also do it in PHP :)

Predestine answered 25/4, 2014 at 21:13 Comment(10)
Add some javascript to it to remove or replace it, or use ajax to poll the server for the line.Raylenerayless
Yes this would probably be easier done in JavaScript. And if you're just doing a random loop it won't actually have anything to do with how far along the page load is. Plus a loop of 100 that is just echoing will basically be instantaneous... computers are pretty darn fast.Safe
Yes, I don't understand why you think behavior would be different than what you get. There is no way to un-echo output once it is sent to the browser.Phemia
While unbuffered server-side scripts can behave like interactive console programs, you cannot assume no node between you and the browser isn't working as a buffer, and it's a bad idea to block the page-processing pipeline like this too.Righteous
I know I should use JS and/or Ajax to achieve it, I just wanted to know if there's a way to also do it in PHP :)Predestine
Downvoted question for rejecting Javascript based solutions yet clearly asking for something that updates content in the browserBille
#1364100Zoan
The result of your code will be your browser loading, showing nothing and, after a while, showing all the numbers from 1 to 100 in a unique div. I think it just cannot be done (without AJAX) since PHP echoes everything after the whole script has been executed.Miner
Perfectly doable in PHP, see my answer, and google "stream output from php"Casas
By the by, just stream the output from a script with the progress bar to a iframe on the page.Casas
S
30

To follow long-running tasks is common but not really easy to implement the first time. Here is a complete example.

a sample of long-running task (a sample of long-running task in a Sellermania's product)

Context

The task

Imagine you currently have the following task, and want to display a progress bar to your visitors.

PHP task.php

  <?php

  $total_stuffs = 200;
  $current_stuff = 0;
  while ($current_stuff < $total_stuffs) {
    $progress = round($current_stuff * 100 / $total_stuffs, 2);

    // ... some stuff
    sleep(1);

    $current_stuff++;
  }

The UI

Your beautiful UI looks like this:

a beautiful ui

HTML ui.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>My Task!</title>
    </head>
    <body>

        <a id="run_task" href="#">Run task</a>

        <div id="task_progress">Progression: <span id="task_progress_pct">XX</span>%

        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

        <script type="text/javascript">

            $('#run_task').click(function(e) {
                e.preventDefault();
                $.get('task-launch.php');
            });

        </script>

    </body>
</html>

The launcher

We need to launch the task in background, in order to make this demonstration relevant. So, this php script will be called asynchronously when clicking "run", and will execute the task above in background.

PHP task-launch.php

<?php

  // launches the task in background
  // see https://mcmap.net/q/358111/-multi-threading-in-php for details about the arguments
  exec("/usr/bin/php task.php > /dev/null 2>&1 &");

Problems

problems

There are 3 problems here:

  1. you can run the task more than once by clicking several times the run button, how to avoid several tasks in the background at the same time?

  2. the task is ran at server side, so we need to do something to access the server and ask for progression information.

  3. and when we will be connected to the server side, the $progress variable is unavailable for reading, because it is stored within the context of the task.php running instance.


Solutions

solutions

Store progression information into something readable from outside

Usually, progression information are stored into a database, or a file, or whatever that can be writtable by a program (your task actaully), and readable by another (your ui where progression should be displayed).

I developped a class for sharing data within several php applications (available on github here), it works about the same way as stdClass but always securely synchronize its contents into a file.

Just download src/Sync.php and change the task.php above by:

PHP task.php

<?php

require("Sync.php");

$total_stuffs = 200;
$current_stuff = 0;

$shared = new Sync("some_file.txt");
$shared->progress = 0;

while ($current_stuff < $total_stuffs) {
  $shared->progress = round($current_stuff * 100 / $total_stuffs, 2);

  // ... some stuff
  sleep(1);

  $current_stuff++;
}

// to tell the follower that the task ended
$shared->progress = null;

Important note: here, some_file.txt is where are stored your task's shared data, so don't hesitate to use "task_[user_id].txt" for example if each user has its own task. And look at the readme on github to optimize file access.

Use the synchronized variable to protect the task launcher

  • The progression is set to 0 at the beginning of the task, so the first thing to do is to check, before running the task, that this progression is not set to 0.

PHP task-launch.php

<?php

require("Sync.php");
$shared = new Sync("some_file.txt");

if (is_null($shared->progress)) {
  exec("/usr/bin/php task.php > /dev/null 2>&1 &");
}
  • If the run button is clicked twice very quickly, we can still have 2 instances of the task. To handle this case, we need to simulate a mutex, in a word, make the variable only available to the current application to do some stuff - other applications will stay sleeping until the shared variable is unlocked.

PHP task.php

<?php

require("Sync.php");

$total_stuffs = 200;
$current_stuff = 0;

$shared = new Sync("some_file.txt");

// here is the magic: impossible to set the progression to 0 if an instance is running
// ------------------
$shared->lock();
if (!is_null($shared->progress))
{
    $shared->unlock();
    exit ;  
}
$shared->progress = 0;
$shared->unlock();
// ------------------

while ($current_stuff < $total_stuffs) {
  $shared->progress = round($current_stuff * 100 / $total_stuffs, 2);

  // ... some stuff
  sleep(1);

  $current_stuff++;
}

// the task ended, no more progression
$shared->progress = null;

Warning: if your task crashes and never reach the end, you'll never be able to launch it anymore. To avoid such cases, you can also store the child's getmypid() and some time() stuffs inside your shared variable, and add a timeout logic in your task.

Use polling to ask the server progression information

Polling stands for asking for progression information to the server every lapse of time (such as, 1 sec, 5 secs or whatever). In a word, client asks progression information to the server every N secs.

  • at server-side, we need to code the handler to answer the progression information.

PHP task-follow.php

<?php

require("Sync.php");

$shared = new Sync("some_file.txt");

if ($shared->progress !== null) {
    echo $shared->progress;
} else {
    echo "--"; // invalid value that will break polling
}
  • at client-side, we need to code the "asking progression information to the server" business

HTML ui-polling.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>My Task!</title>
    </head>
    <body>

        <a id="run_task" href="#">Run task</a>

        <div id="task_progress">Progression: <span id="task_progress_pct">XX</span>%

        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

        <script type="text/javascript">

            $('#run_task').click(function(e) {
                e.preventDefault();

                <!-- not a good practice to run a long-running task this way but that's a sample -->
                $.get('task-launch.php');

                <!-- launches the polling business -->
                setTimeout(function() {
                    getProgressionInformation();
                }, 1000);

            });

            function getProgressionInformation() {
                $.get('task-follow.php', function(progress) {
                    $('#task_progress_pct').html(progress);
                    if (progress !== '--') {
                        <!-- if the task has not finished, we restart the request after a 1000ms delay -->
                        setTimeout(function() {
                            getProgressionInformation();
                        }, 1000);
                    }
                });
            }

            /* the task might be already running when the page loads */
            $(document).ready(function() {
                getProgressionInformation();
            });

        </script>

    </body>
</html>

it works!


With a minimum of JavaScript ?

I also developed a jquery plugin, domajax, intended to do "ajax without javascript" (in fact, the plugin itself is in jQuery, but using it do not require JavaScript code), and by combining options you can do polling.

In our demonstration:

  • the follower becomes:

PHP task-follow.php

<?php

require("Sync.php");

$shared = new Sync("some_file.txt");

if ($shared->progress !== null) {
    echo $shared->progress;
}
  • the UI source becomes:

HTML ui-domajax.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>My Task!</title>
    </head>
    <body>

        <a
            href="#"
            id="run-task"
            class="domajax click"
            data-endpoint="task-launch.php"
            data-domajax-complete="#polling"
        >Run task</a>

        <div
            id="polling"
            data-endpoint="task-follow.php"
            data-delay="1000"
            data-output-not-empty="#task-progress-pct"
            data-domajax-not-empty=""
        ></div>

        <div id="task-progress">Progression: <span id="task-progress-pct">--</span>%

        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
        <script src="//domajax.com/js/domajax/jquery.domajax.js"></script>

    </body>
</html>

As you can see, there is no visible javascript at all in this code. Clean isn't it?

Other examples are available on domajax's website, look at the "Manage progress bars easily" tab in the demo pane. All options are heavily documented for details.

Spencer answered 3/8, 2014 at 11:20 Comment(2)
All that Javascript is like 120kb of extra code for the client to download, load into memory, and JIT-compile. One reason for avoiding Javascript (which OPs question requested) is not simply for cleanliness, but also efficiency.Untaught
You're right, the question required to display progression without javascript; but it also contained confusions about output buffering and the global polling architecture. My answer is more general and covers that topic; it gives a (better) way to do polling. IMHO, in real life, javascript is rarely avoided when needing to poll.Spencer
H
38

PHP is a server side language and it give you response based on request .

you can not manipulate (like you want) on DOM .

for this you should use javascript and AJAX to change DOM elements based on PHP result

Holsinger answered 25/4, 2014 at 21:17 Comment(6)
I know I should use JS and/or Ajax to achieve it, I just wanted to know if there's a way to also do it in PHP :)Predestine
"PHP is a server side language ... you can not manipulate (like you want) on DOM."Vernacularize
@MarwanOssama No. Once PHP has sent something to the browser that's it; it is not possible for PHP to change, or otherwise modify, what was sent.Gowon
This is not always true, PHP's flush/ob_flush can essentially append to the DOM in some web servers. Check out the answer I just postedUntaught
(I am actually upvoting your answer, not only because it's correct, but also to let you have the rare Reversal badge, apparently your first gold one! Congratulations :-) )Gad
I'm looking at this answer now because I'm curious about who got Reversal badges! Half of them are deleted!Mello
S
30

To follow long-running tasks is common but not really easy to implement the first time. Here is a complete example.

a sample of long-running task (a sample of long-running task in a Sellermania's product)

Context

The task

Imagine you currently have the following task, and want to display a progress bar to your visitors.

PHP task.php

  <?php

  $total_stuffs = 200;
  $current_stuff = 0;
  while ($current_stuff < $total_stuffs) {
    $progress = round($current_stuff * 100 / $total_stuffs, 2);

    // ... some stuff
    sleep(1);

    $current_stuff++;
  }

The UI

Your beautiful UI looks like this:

a beautiful ui

HTML ui.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>My Task!</title>
    </head>
    <body>

        <a id="run_task" href="#">Run task</a>

        <div id="task_progress">Progression: <span id="task_progress_pct">XX</span>%

        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

        <script type="text/javascript">

            $('#run_task').click(function(e) {
                e.preventDefault();
                $.get('task-launch.php');
            });

        </script>

    </body>
</html>

The launcher

We need to launch the task in background, in order to make this demonstration relevant. So, this php script will be called asynchronously when clicking "run", and will execute the task above in background.

PHP task-launch.php

<?php

  // launches the task in background
  // see https://mcmap.net/q/358111/-multi-threading-in-php for details about the arguments
  exec("/usr/bin/php task.php > /dev/null 2>&1 &");

Problems

problems

There are 3 problems here:

  1. you can run the task more than once by clicking several times the run button, how to avoid several tasks in the background at the same time?

  2. the task is ran at server side, so we need to do something to access the server and ask for progression information.

  3. and when we will be connected to the server side, the $progress variable is unavailable for reading, because it is stored within the context of the task.php running instance.


Solutions

solutions

Store progression information into something readable from outside

Usually, progression information are stored into a database, or a file, or whatever that can be writtable by a program (your task actaully), and readable by another (your ui where progression should be displayed).

I developped a class for sharing data within several php applications (available on github here), it works about the same way as stdClass but always securely synchronize its contents into a file.

Just download src/Sync.php and change the task.php above by:

PHP task.php

<?php

require("Sync.php");

$total_stuffs = 200;
$current_stuff = 0;

$shared = new Sync("some_file.txt");
$shared->progress = 0;

while ($current_stuff < $total_stuffs) {
  $shared->progress = round($current_stuff * 100 / $total_stuffs, 2);

  // ... some stuff
  sleep(1);

  $current_stuff++;
}

// to tell the follower that the task ended
$shared->progress = null;

Important note: here, some_file.txt is where are stored your task's shared data, so don't hesitate to use "task_[user_id].txt" for example if each user has its own task. And look at the readme on github to optimize file access.

Use the synchronized variable to protect the task launcher

  • The progression is set to 0 at the beginning of the task, so the first thing to do is to check, before running the task, that this progression is not set to 0.

PHP task-launch.php

<?php

require("Sync.php");
$shared = new Sync("some_file.txt");

if (is_null($shared->progress)) {
  exec("/usr/bin/php task.php > /dev/null 2>&1 &");
}
  • If the run button is clicked twice very quickly, we can still have 2 instances of the task. To handle this case, we need to simulate a mutex, in a word, make the variable only available to the current application to do some stuff - other applications will stay sleeping until the shared variable is unlocked.

PHP task.php

<?php

require("Sync.php");

$total_stuffs = 200;
$current_stuff = 0;

$shared = new Sync("some_file.txt");

// here is the magic: impossible to set the progression to 0 if an instance is running
// ------------------
$shared->lock();
if (!is_null($shared->progress))
{
    $shared->unlock();
    exit ;  
}
$shared->progress = 0;
$shared->unlock();
// ------------------

while ($current_stuff < $total_stuffs) {
  $shared->progress = round($current_stuff * 100 / $total_stuffs, 2);

  // ... some stuff
  sleep(1);

  $current_stuff++;
}

// the task ended, no more progression
$shared->progress = null;

Warning: if your task crashes and never reach the end, you'll never be able to launch it anymore. To avoid such cases, you can also store the child's getmypid() and some time() stuffs inside your shared variable, and add a timeout logic in your task.

Use polling to ask the server progression information

Polling stands for asking for progression information to the server every lapse of time (such as, 1 sec, 5 secs or whatever). In a word, client asks progression information to the server every N secs.

  • at server-side, we need to code the handler to answer the progression information.

PHP task-follow.php

<?php

require("Sync.php");

$shared = new Sync("some_file.txt");

if ($shared->progress !== null) {
    echo $shared->progress;
} else {
    echo "--"; // invalid value that will break polling
}
  • at client-side, we need to code the "asking progression information to the server" business

HTML ui-polling.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>My Task!</title>
    </head>
    <body>

        <a id="run_task" href="#">Run task</a>

        <div id="task_progress">Progression: <span id="task_progress_pct">XX</span>%

        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

        <script type="text/javascript">

            $('#run_task').click(function(e) {
                e.preventDefault();

                <!-- not a good practice to run a long-running task this way but that's a sample -->
                $.get('task-launch.php');

                <!-- launches the polling business -->
                setTimeout(function() {
                    getProgressionInformation();
                }, 1000);

            });

            function getProgressionInformation() {
                $.get('task-follow.php', function(progress) {
                    $('#task_progress_pct').html(progress);
                    if (progress !== '--') {
                        <!-- if the task has not finished, we restart the request after a 1000ms delay -->
                        setTimeout(function() {
                            getProgressionInformation();
                        }, 1000);
                    }
                });
            }

            /* the task might be already running when the page loads */
            $(document).ready(function() {
                getProgressionInformation();
            });

        </script>

    </body>
</html>

it works!


With a minimum of JavaScript ?

I also developed a jquery plugin, domajax, intended to do "ajax without javascript" (in fact, the plugin itself is in jQuery, but using it do not require JavaScript code), and by combining options you can do polling.

In our demonstration:

  • the follower becomes:

PHP task-follow.php

<?php

require("Sync.php");

$shared = new Sync("some_file.txt");

if ($shared->progress !== null) {
    echo $shared->progress;
}
  • the UI source becomes:

HTML ui-domajax.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
        <title>My Task!</title>
    </head>
    <body>

        <a
            href="#"
            id="run-task"
            class="domajax click"
            data-endpoint="task-launch.php"
            data-domajax-complete="#polling"
        >Run task</a>

        <div
            id="polling"
            data-endpoint="task-follow.php"
            data-delay="1000"
            data-output-not-empty="#task-progress-pct"
            data-domajax-not-empty=""
        ></div>

        <div id="task-progress">Progression: <span id="task-progress-pct">--</span>%

        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
        <script src="//domajax.com/js/domajax/jquery.domajax.js"></script>

    </body>
</html>

As you can see, there is no visible javascript at all in this code. Clean isn't it?

Other examples are available on domajax's website, look at the "Manage progress bars easily" tab in the demo pane. All options are heavily documented for details.

Spencer answered 3/8, 2014 at 11:20 Comment(2)
All that Javascript is like 120kb of extra code for the client to download, load into memory, and JIT-compile. One reason for avoiding Javascript (which OPs question requested) is not simply for cleanliness, but also efficiency.Untaught
You're right, the question required to display progression without javascript; but it also contained confusions about output buffering and the global polling architecture. My answer is more general and covers that topic; it gives a (better) way to do polling. IMHO, in real life, javascript is rarely avoided when needing to poll.Spencer
U
27

You actually do not need Javascript to accomplish this. If the task you're waiting for is persistent, you can simply create a PHP script that:

  1. Finds the task (in a DB for example)
  2. Checks the percentage complete of the task
  3. Echo's the percentage to the user
  4. If percentage < 100%
    • Refresh page
    • Otherwise, redirect to complete page.

You can do a non-javascript refresh using an HTML meta refresh. It might look something like this (haven't done PHP in like 10 years, so treat this like pseudocode):

<?php
    $taskId = $_GET("taskId");
    $task = lookupTask($taskId);
    $refresh = $task->percentComplete < 100;
?>
<html>
    <head>
        <? if($refresh): ?>
        <meta http-equiv="refresh" content="5">
        <? else: ?>
        <meta http-equiv="refresh" content="0; url=taskComplete?taskId=<?=$taskId ?>" />
        <? endif; ?>
    </head>
    <body>
        Loading: <?=$task->percentComplete ?>
    </body>
</html>

Another approach, a really ugly hack, would be to use PHP's flush() method. Flush will force the current output buffer to whatever is running PHP (CGI for example). This output will sometimes be further buffered by the host application, but on some web servers it will send the output straight to the user. Read more here: http://php.net/manual/en/function.flush.php

You might need to combine it with ob_flush, but I'm not positive: http://php.net/manual/en/function.ob-flush.php

With CSS, you could hide all 'loading' text by default, but override that for the last-child loading text and display it instead. For example:

<style type="text/css">
.loadText {
    display: none;
}
.loadText:last-child {
    display: block;
}
</style>
<?php 
for ($x = 0; $x <= 100; $x++) {
   echo '<div class="loadText">Loading: ', $x, '%</div>';
   flush();
}
?>

Both of these solutions are terrible, I just wanted to share them for completeness :)

Untaught answered 1/8, 2014 at 17:52 Comment(0)
T
11

To elaborate a little on the answers here;

PHP is a server side language, but this is not why it won't normally work.

This has to do with the way we communicate over the HTTP protocol.
When a client sends an HTTP request to the server, the first thing that happens is they establish a connection. The server then processes the request and produces a response which is then sent back to the client.
Once the response is received, the connection dies.
So, generally, you get one response per request.

A common misconception is that using the now defaulted keep-alive header for persistent connections, you can keep pushing content as the socket remains open. HTTP doesn't actually work that way and would require a little more manipulation before you can stream your data.
But more on that here and here.

What can you do?

There are a few options I can think of.

  1. Using a client side language (typically, JavaScript), we can keep sending XMLHttpRequest objects to query the server based on each response we receive effectively creating a stream of media.
  2. Comet is a pretty broad term for allowing web servers to push data back to the client over HTTP requests. APE is one implementation of that.
  3. Have a look at Web Sockets using Ratchet.
  4. You could, in theory and if you knew the address of your client, write APIs on both sides which would accept cURLs and XHRs. In this case, both sides would act as clients and servers and could communicate on the same level. This is possible with PHP but not the best solution for browsers.

Finally, you might want to read this popular answer that goes through some of these technologies.

Tinder answered 29/7, 2014 at 10:57 Comment(4)
In HTTP 1.1 (also SPDY), all connections are considered persistent unless declared otherwise. You have multiple requests using a single connection. And SPDY behaves the same way. This is not a misconception! Simply read the SPEC: tools.ietf.org/html/rfc7230#section-6.3Blowfly
@Jens-AndréKoch What you say is true, but my answer is in regards to the question which you may not have read carefully. You have multiple requests using the same socket, yes. And this is defaulted too, which I have stated. But the persistent connection just means the client can use the same underlying socket to send multiple requests and receive the respective responses. The server can not simply push data back to the client as it is not in control, it is just responding multiple times to multiple requests. It is not initiating here.Tinder
Nope. The client makes a request. The server responds and keeps the connection open - to simply push data back to the client in chunks. Then the connection is closed. It is not responding multiple times to multiple requests, but responding multiple times to one request. For example: https://mcmap.net/q/356102/-how-to-display-loading-percentage-and-how-to-do-it-without-javascript - I read the question carefully: it doesn't contain a word about the requirement of multiple requests. Anyway, we both know that we would use JS or polling tech for this.Blowfly
@Jens-AndréKoch I read your answer, but it does not contradict anything in mine. The only thing I emphasized is the fact that HTTP is a request/response protocol. Even in your script, while the data is being sent back in chunks, it is still only one response. My answer was to elaborate on this issue, as I clearly stated in bold. Related answer hereTinder
V
7

More simply:

<style> div:not(:last-child) { display:none } </style>
<?php 
$x=1;
while($x<=100) {
   echo "<div>Loading: $x %</div>";
   $x++;
}   
?>

That way, the old <div>s will be hidden as the new ones appear.

Voodoo answered 3/8, 2014 at 5:39 Comment(5)
This won't work at all if you have response buffering on and don't flush after each iteration. But also; it measures nothing. Wherever you put this, it's going to basically instantly go through those lines. Even with response buffering.Wyon
Good point about the response buffering. Also I was assuming that the author would put the "echo" statements somewhere else, so that an actual value would be measured.Voodoo
I see what your second point is here; You might want to rearrange your code to simulate how that would work, instead of using a loop. Also, be sure to use an @-name to 'ping' me so I see the response; like this: @VoodooWyon
Sure it would work. But you would need to use either GOTO or a delay and wait until something finishes - or delay another round. Man, that would be a nasty hack :)Kuibyshev
@Kuibyshev not if he's using something like curlopt_progressfunctionVoodoo
B
4

This script works with PHP 5.4 and Nginx on server-side and it's tested with Firefox on client-side. It doesn't work with Chrome.

Demo over at YouTube

Hints

  • only one request
  • the connection must be kept open
  • in order to send content in parts, the response must be chunked
  • you need set a no-buffering header for your webserver
  • php must be configured to allow immediate flushes
  • you need a trick or clever solution to write to the same line in the browser
    • here a css trick is used

<?php
header('X-Accel-Buffering: no'); // nginx related - turn the output buffer off
header('Content-Encoding: chunked;');

header('Transfer-Encoding', 'chunked');
header('Content-Type', 'text/plain');
header('Connection', 'keep-alive');

function dump_chunk($chunk)
{
  printf("%s\r\n", $chunk);
  flush();
  ob_flush(); // if you comment this out, the chunk updates are slower
}

ob_start();
?>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>A Chunked Response</title>
        <style> div:not(:last-child) { display:none; } </style>
    </head>
    <body>
        <?php
        // inital clear all
        ob_end_flush(); flush(); ob_flush();

        // browser padding
        echo str_pad('', 4096) . "\n"; flush(); ob_flush();

        for ($i = 0; $i < 1000; $i++) {
            usleep(15000);
            dump_chunk('<div>Sending data chunk ' . ($i + 1) . ' of 1000 <br /></div>');
        }

        usleep(10000); // this is needed for the last update
        ?>
    </body>
</html>
Blowfly answered 8/8, 2014 at 11:58 Comment(0)
G
3

I don't know if I really understand your question, but I did something usually people don't know about it. In order to give the impression that the line is being overwritten, you can insert a CR (Carriage Return, "\r") in front of your output. I wrote it down on an example:

<?php
$x=1;
while($x<=100) {
   echo "\rLoading: $x %";
   usleep(10000);
   $x++;
}
?>

Running this on a terminal I get a cool loading status: $ php code.php

Hope that's what you've been looking for.

Gunpoint answered 12/8, 2014 at 11:57 Comment(0)
M
2

No, You cannot do this without the help of javascript or jquery.

You can have a php page, if you need and that will return the progress like below,

//yourprogresspage.php

    <?php 
    $x="your progress";
    echo "Loading: $x %<br>";

    ?>

then you need to call it from your page using jquery

<script>
   var repeater;
   function updateprogress() {
      $.post( "yourprogresspage.php", function( data ) {
         $( ".progressdiv" ).html( data );
      });
      repeater = setTimeout(updateprogress, 1000);
   }   
  updateprogress();
</script>
Moise answered 31/7, 2014 at 6:26 Comment(2)
It can be done using only HTML and CSS. You can use fixed positioning or CSS selectors targeting the last line echoed.Dupion
In the question he clearly mentioned that they need to do this with the help of php, if the progress is getting from a database value than surely you need to use php or any other server side scripting. In that case, how can you display the progress using only HTML and CSS?Moise
O
2

I used a bit of CSS and HTML, This is what I came up with:

<html>
    <?php 
        $x=1;
        while($x<=100)
        {
    ?>
    <div id="count"style="position:absolute;top:10px;left:10px;">
        <style>
        #count:after 
        {
          content:'<?php echo "Loading: $x % "; ?>';      
        }
        </style>
    </div>
    <?php 
        $x++;
        sleep(1); 
        } 
    ?>
</html>
Ornamentation answered 3/8, 2014 at 10:23 Comment(0)
T
1

You need to update the page using javascript.

<?php

echo '<div id="percent"></div>';

$x = 0;
while($x<=100) {
   echo '<script type="text/javascript">document.getElementById("percent").innerHTML = '.$x.';'.'</script>';
   $x++;
   sleep(1);
}   

You can remove the sleep function and do your work there.

Tver answered 25/4, 2014 at 21:19 Comment(1)
I already know how to do it in Javascript, but thanks anyway :)Predestine
O
1

Actually, PHP doesn't provide such a feature.

To show a progress-bar, you might try this:

While executing the long-running file generation process, save a percentage-done value in the current user session

On the page, which shows the download form, add an iFrame which calls a progress.php script

The progress.php script needs to generate webpage, which needs to reload auto-magically

The progress.php script needs to read the current progress-value from the session and draw a progress bar graphics, which visually represents the progress.

While this is a PHP-only solution, you might well prepare another one using JS / AJAX.

The HTML auto-reload should like like this:

http://linuxfreak.com/progress.php">

Omega answered 5/8, 2014 at 13:58 Comment(0)
D
1

After reading everyone's comments and answers saying that this isn't possible using PHP, I thought I'd take a minute to prove them wrong :P

I don't recommend actually using this (because there's certainly better ways to achieve a loading progress indicator) but this will "un-echo" the previous line as long as you don't have zlib compression enabled in PHP:

<?php 
$x = 0;
echo "<html><body>";
echo "<div style=\"position: fixed; color: white; background-color: rgba(0,0,0,0.5); top: 0; left: 0;\" id=\"loader0\">Loading: 0%</div>";
ob_implicit_flush(true); //Forces all output to immediately send
while($x < 100) {
    $x++;
    //Padding the string overcomes browser caching and TCP packet delays due to insufficiently sized packets
    echo str_pad("<div style=\"position: fixed; color: white; background-color: rgba(0,0,0,0.5); top: 0; left: 0;\" id=\"loader{$x}\">Loading: {$x}%<script>var prev = document.getElementById('loader".($x-1)."'); prev.parentNode.removeChild(prev)</script></div>", 4096, " ");
    sleep(1); //Just for demo
}
?>
Dupion answered 6/8, 2014 at 18:33 Comment(8)
ok, the loop runs 100 times and each iteration sleeps for 1 sec, thats 100secs. php's default max_execution_time is 30 seconds, so this will halt at 30% - if the value of max_execution_time is not raised.Blowfly
You're right about that. Another of the endless reasons to not actually do this.Dupion
@Jens-André Koch: does sleep() count into php's execution time? I don't think so. - nope, it's safe: php.net/manual/en/function.set-time-limit.phpApiarian
Hmm. Interesting. I'm on Windows and the sleep() time is counted as execution time. I guess, you are on Linux, where the sleeping time is ignored and not counted into the execution time. But can you explain why sleep() doesn't count into the execution time on Linux?Blowfly
"The set_time_limit() function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), stream operations, database queries, etc. is not included when determining the maximum time that the script has been running. This is not true on Windows where the measured time is real." -http://php.net/manual/en/function.set-time-limit.phpDupion
@hakre, you're both right. It depends on the platform though. Apparently in Linux/Unix, sleeping threads don't count against exec time, but in Windows they do.Dupion
@Jens-AndréKoch: it is a system call so the process doesn't use any real time while system does make it wait (it sleeps). it does not run until woken up again. so sleeping doesn't cost any execution time (real time). en.wikipedia.org/wiki/Sleep_(system_call) - and yes with PHP on windows it does not work. php -dmax_execution_time=1 -r "echo 'test'; sleep(2); echo 'now.'";Apiarian
Thank you both for explaining the underlying details of sleep() and the syscall. So, sleep() pauses the script execution by giving control to the underlying system, which returns after the given number of seconds. What are my alternatives, if the usage of sleep() is OS bound and i want my script to "do nothing for an amount of time, which increases the total script exec time" in a cross-platform way? How can i reproduce the windows behaviour of sleep() on Linux/Unix?Blowfly
L
1

You could use ob_flush(); and flush();

This will work but be aware that you should specify document Charset

<meta charset="utf-8" />

and then in the beginning of php add ob_start(); in the loop copy both ob_flush(); flush();

but this will print all 1 to 100 you should set php to print

<script>
   document.getElementById('that div').innerHTML = "Loading [THE PERCENT]%";
</script>

This is my own way.

Lack answered 7/8, 2014 at 19:2 Comment(0)
V
1

I can't see other way then sending multiple requests to the same script and saving the progress percentage in session. This could be a pure PHP and HTML solution.

<?php
session_start();
if(isset($_SESSION['progress']))
    $progress = $_SESSION['progress'] = $_SESSION['progress'] + 10;
else
    $progress = $_SESSION['progress'] = 10;
if($progress < 100){
    echo '<head><meta http-equiv="refresh" content="1; 
            url=http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'].'" /></head>
        <body>'.$progress.'%</body>';
} else {
    echo 'Done';
}

What do you think ?

Vanadinite answered 12/8, 2014 at 9:17 Comment(0)
C
1

So much for such a simple question. Really what you are asking is how to stream output from the server, If I got the question correct, call this function.

function streamOutput(){
    // Turn off output buffering
    ini_set('output_buffering', 'off');
    // Turn off PHP output compression
    ini_set('zlib.output_compression', false);

    //Flush (send) the output buffer and turn off output buffering
    //ob_end_flush();
     while (@ob_end_flush());

    // Implicitly flush the buffer(s)
    ini_set('implicit_flush', true);
    ob_implicit_flush(true);

    //prevent apache from buffering it for deflate/gzip.
    for($i = 0; $i < 1000; $i++){
        echo ' ';
    }

    flush();
}

Then in your loop do something like this.

$sleep = 100;
for( $i = 0; $i < $sleep; $i++){
    echo str_repeat('.', $i+1)." ".$i."%\n";
    flush();
    sleep(1);
}

The important part here is to call flush whenever you want to generate output. As for changing the DOM, ever hear of an IFRAME, just run it in an iframe and its all good. Iframes are the wormholes of the interwebs.

Casas answered 7/10, 2014 at 19:43 Comment(0)
J
1

this works fine for me

if(!session_id())
    session_start();        

if($_SESSION['loding']<=100){
   echo "Loading:".$_SESSION['loding']++ ."%<br>";
    ?>
     <div style="width:<?echo $_SESSION['loding'];?>px; height:10px; background-color:green;"></div>
   <? //the above div content displays a green loader type effect.
    header( "refresh:.5; url=loaderinphp.php" );
   }
else{
     //redirect to another page or code here
}//unset the session $_SESSION['loding'] while calling again for another page load

Screenshot:

enter image description here

the thing about php is that it gets fully loaded and displayed as html content.

(i.e)

echo "1";
usleep(5000000);
echo "2";

here both the echo statements are displayed at the same time but the loading time of the page gets extended as you increase the time in usleep function.

so we have to make a server request every time we increment the value in the loading content.

as u already know that this could be done with javascript,jquery,ajax i have tried this using session.

hope this comes handy...

Jehovist answered 10/10, 2014 at 6:54 Comment(0)
U
0

Not possible only with PHP, but it can be achieved using PHP + JS, try following example,

<?php
ob_start();
ob_implicit_flush();
ob_end_flush();
?>
<html>
    <head>
        <title>Loader Test</title>
        <script type="text/javascript">
            function updateLoader(val) {
                document.getElementById('loader').innerHTML = "Loading:" + val + "%";
            }
        </script>
    </head>
    <body>
        <div id="loader"></div>
        <?php flush();ob_flush(); ?>
        <?php
            for( $i = 1 ; $i <= 100 ; $i++ )
            {
                echo '<script type="text/javascript">updateLoader('.($i).')</script>';
                flush();
                ob_flush();
                usleep(50000);
            }
        ?>
        <?php ob_end_flush(); ?>
        <p>Completed</p>
    </body>
</html>

Note: this will not work if zlib.output_compression is enabled or some antivirus software holds the buffer until the page has finished loaded before sending it to the browser.

Uri answered 12/8, 2014 at 12:15 Comment(0)
M
0

Lets say the page has some big data to load so you want a loader.

You can make the user think there is a loader.

<?php
    // Loader last id
    $loader_id = 0;

    // Initiate loader
    function initLoader(){
        echo '<style>.php_loader{width:100px;height:50px;line-height:50px;margin-top:-25px;margin-left:-50px;position:fixed;top:50%;left:50%;}</style>';
        // Load first loader
        echo '<div id="php_loader_'.$GLOBALS['loader_id'].'" class="php_loader">0%</div>';
    }

    // Upadate loader
    function updateLoader($percent){
        // Remove last loader
        clearLoader();

        // Insert new loader
        $GLOBALS['loader_id']++;
        echo '<div id="php_loader_'.$GLOBALS['loader_id'].'" class="php_loader">'.$percent.'%</div>';

        ob_flush();
        flush();
    }

    function clearLoader(){
        // Remove last loader
        echo '<style>#php_loader_'.$GLOBALS['loader_id'].'{display:none;}</style>';
    }

?>
<html>
    <head>
        <title>Server Side Loader</title>
    </head>
    <body style="padding:30px; font-size:30px;font-family: Courier;">
        <?php
            // On body start we init loader
            initLoader();
            sleep(1);
        ?>

        25% off the page just loaded</br>

        <?php
            updateLoader(25);
            sleep(1);
        ?>

        30% off the page just loaded</br>

        <?php
            updateLoader(30);
            sleep(1);
        ?>

        50% off the page just loaded</br>

        <?php
            updateLoader(50);
            sleep(1);
        ?>

        80% off the page just loaded</br>

        <?php
            updateLoader(80);
            sleep(1);
        ?>

        100% off the page just loaded</br>

        <?php
            updateLoader(100);
            sleep(1);

            // Clear loader before body
            clearLoader();
        ?>
    </body>
</html>

Nice and smooth.

Marlite answered 10/10, 2014 at 0:33 Comment(0)
A
-1

It really depends on how you do the loading but what you could do if you really want to use AJAX js to keep an opened connection and on each update from the processing on the back-end to update the front end using JS that updated the correct display widget/field in the AJAX callback.

If you just want to do it with PHP you could easily do the following:

<?php 
$x=1;
while($x<=100) {
   echo "Loading: $x %<br>";
   $x++;
   <CALL THE FUNCTION THAT DOES THE ACTUAL PROCESSING HERE>
   ob_end_clean();
}  
Ankara answered 31/7, 2014 at 19:51 Comment(0)
C
-1

Just to add to this question, you could use output buffering if you really wanted to achieve this (this is not advised though). The only limitation is that you cannot overwrite previous output. This is more like a progress bar:

<?php

while(@ob_get_clean());
echo "Loading: ";
for($i=0; $i<10; $i++) {
    echo str_pad("+", 4096);          
    usleep(200000); 
}
echo "Done.";

?>

See Display output in parts in PHP.

Classy answered 1/8, 2014 at 14:20 Comment(2)
Essentially, what your code does is not output anything until 100% has been reached or be still subjectible to the problems described by OP.Hoy
The code above does the exact opposite, it does not wait until 100% is reached, it outputs the progress in chunks. That's the whole point ...Classy
M
-2

Solution, not the best for developer, but it'll work for what you want. You're script is ok. But if you see all the result at all, and you don't see progression, it's because there is a buffer mechanism. Maybe php, maybe web-server (apache...) or even maybe browser buffer.

Take a look to the flush() php function. In order to test, you need to user a "command line browser" (maybe telnet / GET manual request). If command line show you the content's coming line after line, then the problem is with your web browser.

But first, you need to identify WHERE is the current buffer mechanism: php output? (see flush php function), web server compression ? (see gzip compression) maybe browser buffering ? (chrome's waiting the end of file before showing the results)

Mallorymallow answered 7/8, 2014 at 12:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.