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 :)
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 in a Sellermania's product)
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++;
}
Your beautiful UI looks like this:
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>
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://stackoverflow.com/a/12341511/731138 for details about the arguments
exec("/usr/bin/php task.php > /dev/null 2>&1 &");
There are 3 problems here:
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?
the task is ran at server side, so we need to do something to access the server and ask for progression information.
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.
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.
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 &");
}
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.
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.
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
}
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>
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:
PHP task-follow.php
<?php
require("Sync.php");
$shared = new Sync("some_file.txt");
if ($shared->progress !== null) {
echo $shared->progress;
}
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.