Search code examples
phpsessiontimercountdowntimer

PHP Session Timer Works Initially but Gone Wrong


So I have a php session timer that works but somehow gets bugged out after awhile... this is the code and the console log I got. I'm looking for a fix to this problem, or possibly a different set of code to achieve the same timer effect (as I'm not sure if using session is the best method for a timer)

session_start();
function timer($time) {
        //Set the countdown to 120 seconds.
    $_SESSION['countdown'] = $time*60;
        //Store the timestamp of when the countdown began.
    $_SESSION['time_started'] = time();

    $now = time();
    $timeSince = $now - $_SESSION['time_started'];
    $remainingSeconds = abs($_SESSION['countdown'] - $timeSince);
    $counter = 0; 

    $minutes = $remainingSeconds/60;
    echo "$minutes minutes countdown starts.".PHP_EOL;
    while($remainingSeconds >= 1) {
        $now = time();
        $timeSince = $now - $_SESSION['time_started'];

        if (($timeSince-$counter) >= 60) {
            $remainingSeconds = abs($_SESSION['countdown'] - $timeSince);
            $counter = $timeSince;
            $minutes = $remainingSeconds/60;
            echo "$minutes minutes has passed.".PHP_EOL;
        }
    }

    if($remainingSeconds < 1){
        session_abort(); 
        return true;
    }
}

if($this->timer(30)) {
        // do whatever
        echo "$time has passed";
    }

Here's what happens in the console:

30 minutes countdown starts.
29 minutes has passed.
.... (continue as per pattern)
16 minutes has passed.
15 minutes has passed. (problem occurs here)
8.7166666666667 minutes has passed.
7.7166666666667 minutes has passed.
6.7166666666667 minutes has passed.
.... (continue as per pattern)
0.71666666666667 minutes has passed.
0.28333333333333 minutes has passed.
1.2833333333333 minutes has passed.
2.2833333333333 minutes has passed.
.... (continue as per pattern all the way)

Extra notes: The session timer doesn't always recur this same pattern, there have been times when it ran through the entire 30minutes and managed to echo "$time has passed"; while the bug only occured later on


Solution

  • I haven't run your, but just from reading it I think there are a few things very wrong with it.

    1. Sessions. You're not using them right.

    Session values should only be set once, meaning before you do $_SESSION['countdown'] = $time*60; and $_SESSION['time_started'] = time();, you should check if they already exist or not, and only assign if nonexistent. Your current code resets the clock every time the page is refreshed, which defeats the purpose of sessions.

    1. abs. I think you're not using them right either.

    You shouldn't abs the remaining seconds all the time. $remainingSeconds = abs($_SESSION['countdown'] - $timeSince); should be allowed to go into negative. Negative remaining seconds mean your timeout has expired / you've missed it! Calling abs means you're effectively letting it go forever if you by any chance miss the exact time of your event. This is the answer to your main problem. Fix this and your counter will stop going to zero and back up again.

    1. You're relying on your code correctly checking every single second. But it doesn't.

    The nasty decimals you're getting happen when for some reason your code gets delayed and doesn't correctly check the 60th second, which means your division by 60 is not perfectly round and you get 8.7166666 minutes.

    If you start by removing the abs calls and generally try to simplify your code a bit, I believe you'll quickly get it to work as intended.

    // Edit 1

    This is a very naive, but simplified approach to your problem. I left two different outputs in there for you to pick one.

    function timer($time) {
        echo "$time minutes countdown starts." . PHP_EOL;
    
        // Save the date in future when the timer should stop
        $endTime = time() + $time * 60;
    
        // Keeps track of last full minute to simplify logs
        $lastFullMinute = $time;
    
        while(true) {
            $timeRemaining = $endTime - time();
    
            if ($timeRemaining <= 0) {
                // Time remaining is less than zero, which means we've gone beyond the end date.
                // End the loop
                return;
            }
    
            // Round up!
            $minutesRemaining = ceil($timeRemaining / 60);
            if ($minutesRemaining != $lastFullMinute) {
                // Current "minute" is different than the previous one, so display a nice message
    
                // If you want to show how many minutes are remainig, use this:
                echo "$minutesRemaining minutes remaining." . PHP_EOL;
    
                // If you want to show how many minutes have passed, you have to take mintutesRemaining away from the original time
                $minutesPassed = $time - $minutesRemaining;
                echo "$minutesPassed minutes passed." . PHP_EOL;
    
                $lastFullMinute = $minutesRemaining;
            }
        }
    }
    

    The main way for you to improve it further would be to use the sleep function http://php.net/manual/en/function.sleep.php. Currently the while loop will hog all the CPU by constantly checking if the timer happened, so you should sleep for a few seconds inside.