Ajax Long Polling with PHP

EDIT: At the time I was writing this article, I hadn't created a hosted application using Long Polling. But, in December 2018, I published Hyvor Talk and at first, its real-time system was built using Long Polling.

But, everything screwed up. My VPS server could not manage to handle the traffic with long polling. So, I changed the system to websockets with the help of Node.JS.

AJAX Long Polling is an interesting topic to learn, but do not use it in productional websites unless you have unlimited hosting resources.

I found a smarter solution for connecting PHP and Node.JS for real-time communication. You can see it here.

Need to create a real-time application with Javascript and PHP? Actually, there are some different ways to client-side to interact with the server in real time.

Short Polling and SSE are not good to use for a website. You may use long polling or Web Sockets. Long polling is supported by the most of web browsers while web sockets do not support old browsers. However, long polling needs more CPU capacity than web sockets. Many websites such as Facebook, etc. use long polling. I will be showing you how to create a long polling script with PHP. Examples at the end will describe you advanced long-polling.

This article is divided into following sub-topics.

Introduction

The following image describes the concept of Long-Polling.

Long Polling

Explained...

Writing the JS code

First, we have to send an ajax request from the browser to the server to accomplish our target. I assume that you have basic knowledge of using Ajax with Javascript. If you like, you can follow my tutorial on that topic to learn more.


var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
	if (this.readyState == 4 && this.status == 200) {
	 		// when everything goes fine...
	}
};
xhttp.open("GET", "long-polling.php", true);
xhttp.send();


What the above code does...

The back-end

First of all, you should have a good knowledge of using following statements, functions, and tricks. Let's go through those kinds of stuff.

The Infinite Loop


<?php
while(true) {
	echo 'yes'; // this will be executed unlimited times
}

Ending the execution

There should be a way to go out from the infinite loop and send a response to the client. In the script below, I have shown 3 different ways to break loops.


<?php
while(true) {
	// escape from the infinite loop
	break; 
	// escape from multiple loops
	for ($i = 0; $i < 10000; $i  ) {
		if ($foundAnUpdate) {
			break 2; // escapes from the for loop & the infinite loop
		}
	}
	// output a message and terminate the script 
	if ($foundAnUpdate) {
		exit(json_encode[
			'status' => 'success'
		]);
	}
}

Other useful functions



<?php
session_write_close();
ignore_user_abort(true);
set_time_limit(0);
while(true) {
	// here goes the loop

	sleep(1);
}

Code Explanation

Examples

I have created two examples for you. You can download a .zip file of both examples at the end of this sub-topic.

1. Listening for a file update - The Cookie Method

First, I will create index.html which is the client-side file. It sends an ajax request to long-polling.php which is an Ajax Request Handler.



<html>
<head>
	<title>Listen to File Update</title>
</head>
<body>
<script type="text/javascript">
	
	function poll() {
		var ajax = new XMLHttpRequest();
		ajax.onreadystatechange = function() {
			if (this.readyState === 4 && this.status === 200) {
				if (this.status === 200) {
					try {
						var json = JSON.parse(this.responseText);
					} catch {
						poll();return;
					}
					

					if (json.status !== true) {
						alert(json.error);return;
					}

					var div = document.createElement("DIV");
					document.body.appendChild(div);
					div.innerHTML = 'time: '   json.time   ', content: '   json.content;
					poll();
				} else {
					poll();
				}
			}
		}
		ajax.open('GET', 'long-polling.php', true);
		ajax.send();
	}
	poll();
</script>
</body>
</html>



<?php
session_write_close();
ignore_user_abort(false);
set_time_limit(40);

try {
	// lastUpdate cookie saves the file update time which was sent to the browser
	if (!isset($_COOKIE['lastUpdate'])) {
		setcookie('lastUpdate', 0);
		$_COOKIE['lastUpdate'] = 0;
	}
	$lastUpdate = $_COOKIE['lastUpdate'];
	$file = 'file.txt';
	if (!file_exists($file)) {
		throw new Exception('file.txt Does not exist');
	}
	while (true) {
		$fileModifyTime = filemtime($file);
		if ($fileModifyTime === false) {
			throw new Exception('Could not read last modification time');
		}
		// if the last modification time of the file is greater than the last update sent to the browser... 
		if ($fileModifyTime > $lastUpdate) {
			setcookie('lastUpdate', $fileModifyTime);
			// get file contents
			$fileRead = file_get_contents($file);
			exit(json_encode([
				'status' => true,
				'time' => $fileModifyTime, 
				'content' => $fileRead
			]));
		}
		// to clear cache
		clearstatcache();
		// to sleep
		sleep(1);
	}
} catch (Exception $e) {
	exit(
		json_encode(
			array (
				'status' => false,
				'error' => $e -> getMessage()
			)
		)
	);
}

This example is based on a cookie. In the first request, it creates a cookie named lastUpdate and sends it to the client's browser. This cookie saves the last file modification time of file.txt that was sent to the browser.

In the next request, it loops until the actual modification time of file.txt is greater than the lastUpdate cookie. If yes, it sends a JSON response to the browser. Javascript code in ajax request creates new <div> and adds some content to it.

2. Listening for a database update

Here we only create a cookie to save the user id. Timestamps and other data are saved in the database.

  • There are two tables, db_user_data (to save user data) and db_updating_table (to save new messages)
  • In the first request, the user cookie is created.
  • In the loop, it checks whether there are new rows in db_updating_table. If yes, sends column values of those rows to the browser.
  • There is update-other-table.php to update db_updating_table.
  • When a user is in index.html, and anyone requests update-other-table.php to create a new message (or a row), the first user sees the message in real-time.
  • I recommend you to download the .zip file and try it yourself. I have explained the code line by line with comments.

    In index.html only the onreadystatechange function is changed.

    
    function poll() {
    	var ajax = new XMLHttpRequest();
    	ajax.onreadystatechange = function() {
    		if (this.readyState === 4 && this.status === 200) {
    			if (this.status === 200) {
    				try {
    					var json = JSON.parse(this.responseText);
    				} catch {
    					poll();return;
    				}
    				if (json.status !== true) {
    					alert(json.error);return;
    				}
    				var data = json.data;
    				for (var i = 0, len = data.length; i < len; i  ) {
    					var x = data[i];
    					var div = document.createElement("DIV");
    					document.body.appendChild(div);
    					div.innerHTML = 'time: '   x.time   ', content: '   x.content;
    				}
    				poll();
    			} else {
    				poll();
    			}
    		}
    	}
    	ajax.open('GET', 'long-polling.php', true);
    	ajax.send();
    }
    poll();
    
    
    
    
    <?php
    
    session_write_close();
    ignore_user_abort(false);
    set_time_limit(40);
    
    try {
    	// connect to the db ($mysqli)
    	include_once '../config.php';
    
    	if (empty($_COOKIE['user'])) {
    		$user = rand(0,10000000);
    		// send to the browser
    		setcookie('user', $user);
    
    		// save in global variable
    		$_COOKIE['user'] = $user;
    
    		// add user to the database
    		$mysqli -> query("INSERT INTO db_user_data VALUES ($user, 0)");
    
    		// first request does not do anything than creating the cookie
    		exit();
    	}
    
    	// get the user value 
    	$user = $_COOKIE['user'];
    
    	while (true) {
    
    		// select new rows
    		$result = $mysqli -> query("SELECT t.id, t.content, t.time FROM db_updating_table t INNER JOIN db_user_data ud ON ud.last_sent_id < t.id WHERE ud.user = $user ORDER BY t.id");
    
    		if ($result && $result -> num_rows) {
    			$output = [];
    			$lastId = 0;
    			foreach ($result as $row) {
    				$output[] = [
    					'content' => $row['content'], 
    					'time' => $row['time']];
    
    				$lastId = $row['id'];
    			}
    			$mysqli -> query("UPDATE db_user_data SET last_sent_id = $lastId WHERE user = $user");
    
    
    			echo json_encode([
    				'status' => true,
    				'data' => $output
    			]);
    			exit;
    		}
    
    
    
    		// db queries are heavy. So 2 seconds
    		sleep(2);
    	}
    
    } catch (Exception $e) {
    
    	exit(
    		json_encode(
    			array (
    				'status' => false,
    				'error' => $e -> getMessage()
    			)
    		)
    	);
    
    }
    
    
    
    
    <?php
    include_once '../config.php';
    
    $content = !empty($_GET['content']) ? $_GET['content'] : 'No Message Defined';
    $time = time();
    
    $mysqli -> query("INSERT INTO db_updating_table (content,time) VALUES ('$content', $time)");
    
    
    

    Querying the database continuously needs a powerful server. If you have one, there's nothing wrong with doing this.

    Downloading the examples

    This zip has both above examples. To use the second example, first, import the .sql file to your MYSQL database and then change config.php with your hostname, username, password and the database.

    Download Zip

    Conclusion

    I have shown you how to use long polling different ways. I hope you enjoyed this tutorial. If you have any question, suggestion or feedback, comment below. Thanks for reading.

    Tagged: PHP
    Written By
    Supun Kavinda
    Written On
    Feb 6, 2018
    Last Updated On
    Apr 18, 2019
    Latest on Hyvor Developer
    Creating a Real-Time Chat App with PHP and Node.js
    All About MYSQLI Prepared Statements in PHP
    Image Upload with AJAX, PHP, and MYSQL - The Beginner's Guide
    PHP Contact Form - The Email Method
    Related Articles
    100