Implementing an online chat
Bogdan Roman on 2002 July 23
Bogdan Roman on 2002 July 23
How many of you wondered if there can be a simpler/faster way to chat with no downloads or installs of chat software, without the constraints of proxies or firewalls. I don't think I am the only one. So how would that be possible? Well, how about having a chat directly on a website, with no JavaScript, Java Applets, or IRC Clients? How about implementing one with our good ol' pals: PHP and MySQL?
Your questions are answered and recently more and more websites have chosen to have a little, if not a sophisticated, online chat implemented in PHP. This tutorial will show you the basics of building and implementing an online chat in PHP.
The Problems
As in any chat, the user should be able to write and receive messages. This tutorial will cover the basic aspects of a chat. We will discuss a public chat where the users can all chat together. More sophisticated features like private chat/messages, message/sender search, and so on is left to the reader as an exercise.
The problem with an online chat implemented in PHP is that there cannot be real-time messages received. That means that the users won't see the message as the sender presses [Enter], but will have to refresh their chat window to check for new messages. I know, that is rather lame and doesn't seem like a real chat. You're absolutely right, but what you are trying to achieve here can't be done any more professionally using a scripting language like PHP. So lets stop whining and find some solutions to that.
The above are the major problems we will face. Those and others that will occur will be discussed as we go on with the tutorial.
The Solutions
For our chat we need a database to hold the chat information (sender, messages ...) and an interface between the user and that database allowing the user to read and write messages. So, in summary, we would have to build ourselves the following:
- An html table to display the messages with their date and senders (if the case)
- An html form to allow the user to write and send messages
- A PHP script that is practically our interface, handling the records and displaying the messages (you can split it into two scripts if you like)
- A database to hold the chat information
See? nothing complicated. Let's go on.
You may wonder, "ok, but what do we do when the database gets larger?". Good question; simple answer: It is up to you. You may choose to "window" the messages, that is to keep only the most X recent messages (X being an arbitrary number), or to keep them all and provide the user with some controls so he can be able to browse through the past messages. We will cover this aspect also.
In case you choose to keep all messages and display a browsing controls bar, to browse back and forth through the messages we will use a simple SELECT query with the LIMIT n,m option so we can display only m messages at a time, the n standing for the offset of the starting message number. This is a little bit more complicated and will be explained at the end of the tutorial. For now we will choose to have a chat limited to the last X messages written.
Code Sample
We want to chat--ok, that's clear. What we need first of all is the help of the other good ol' pal, MySQL. So here comes our buddy with a database and a table called olchat containing as fields the date/time the message was sent, the user name (optional), the message, and some unique identifier to make all the things go smooth. Great, let's create that table:
CREATE TABLE olchat (
id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
date DATETIME,
sender VARCHAR(32),
message TEXT,
);
We will use that AUTO_INCREMENT field to delete old messages. Suppose you would like only the last 10 messages displayed, then the PHP code displaying the messages would look like:
<?php
// Delete old messages that have the ID
// smaller than (last_id - 10).
$query = "SELECT MAX(id) as last_id FROM olchat";
$res = mysql_query ($query);
$row = mysql_fetch_array ($res);
$query = "DELETE FROM olchat WHERE id < ".($row['last_id']-10);
mysql_query ($query);
// Display the messages.
$query = "SELECT * FROM olchat ORDER BY date ASC";
$res = mysql_query ($query);
while ($row = mysql_fetch_array($res)) {
echo date("m/d g:I A", strtotime ($row['date']))." ";
echo $row['sender'].": ";
echo $row['message']."<br>";
}
?>
Of course, the above output can be "styled" according to your tastes (date colored and bolded, sender name in different color, and so on). Now here's what would the html form looks like:
<form name=oChatForm method=post action="olchat.php">
Your name: <input type=text name=sender maxlength=32><br>
Your message: <input type=text name=message><br>
<input type=submit>
</form>
And now the PHP code that handles recording the message into the olchat MySQL table:
<?php
// Check to see if the sender name or message are empty.
if (@$_POST['sender']) == "" || @$_POST['message'] == "")
die ("Sender name or message field cannot be emtpy");
// Escape all the input for increased security.
$sender = mysql_escape_string ($_POST['sender']);
$message = mysql_escape_string ($_POST['message']);
// Insert the new message into the table.
$query = "INSERT INTO olchat (date, sender, message)
VALUES (NOW(), '".$sender."', '".$message."')";
mysql_query ($query);
?>
Frankly, I recommend implementing the above code snippets into the same olchat.php file. This will save you managing multiple files for one job. Here is an example of the whole PHP file for the online chat:
<?php
mysql_pconnect ('localhost', 'root', '');
mysql_select_db ('website');
// Check if the sender and message exist and
// record the message in that case.
if (@$_POST['sender'] != '' || @$_POST['message'] != '') {
// Delete old messages.
$query = "SELECT MAX(id) as last_id FROM olchat;";
$res = mysql_query ($query);
$row = mysql_fetch_array ($res);
$query = "DELETE FROM olchat WHERE id < ".($row['last_id'] - 10);
mysql_query ($query);
// Escape all input and record the new message.
$sender = mysql_escape_string ($_POST['sender']);
$message = mysql_escape_string ($_POST['message']);
$query = "INSERT INTO olchat (date, sender, message)
VALUES (NOW(), '".$sender."', '".$message."')";
mysql_query ($query);
}
// Display the messages, along with the new recorded one.
$query = "SELECT * FROM olchat ORDER BY date ASC;";
$res = mysql_query ($query);
while ($row = mysql_fetch_array($res)) {
echo date("m/d g:I A", strtotime ($row['date']))." ";
echo $row['sender'].": ";
echo $row['message']."<br>";
}
?>
<br>
<form name=oChatForm method=post>
Your name: <input type=text name=sender maxlength=32><br>
Your message: <input type=text name=message><br>
<input type=submit>
</form>
You can simply copy-paste the above code into a PHP file and test it with your own web server, after, of course, you have created the olchat table in your MySQL database.
Now, for the real-time issue, we would use a simple JavaScript snippet placed in <head>..</head> to refresh the page each minute (or a chosen time interval):
<script language="javascript">
var iTmr = 0;
function Load {
iTmr = setInterval ('history.go(0)', 60000);
//iTmr = setInterval ('document.forms("oChatForm").submit()', 60000);
}
window.onload = Load;
</script>
As you can see you can use two methods (actually there are plenty) to refresh your page. One is by invoking the history object's method of reloading the same page. Another method involves forcing and submitting your form for the message, triggering the PHP script to load and process the inputs. I don't recommend using the history method, because I had trouble displaying new messaging with some browsers.
As expected, any additions to improve the functionality can cause new problems. What if the user is typing a message at the auto-refresh time? Then you would either erase his message if you use the history method or force the recording of an unfinished message if you use the form.submit() method. Kind of frustrating, isn't it? Well, you guessed it ... the auto-refresh procedure has to cease when the user starts typing something. You can do that by adding an onkeypress handle to the form's inputs:
<form name=oChatForm method=post>
Your name: <input type=text name=sender maxlength=32
onkeypress="clearInterval(iTmr)"><br>
Your message: <input type=text name=message
onkeypress="clearInterval(iTmr)"><br>
<input type="submit">
</form>
As you can see, problems just keep arising whenever you add something to your code. Further issues will be left to the reader as an exercise--otherwise this tutorial will never end.
Let us see now how can we implement that messages browsing control bar so the user can see the past messages. I will briefly explain this part and show you the PHP code for the function that generates that control bar. You will see a working example also.
The Controls Bar
I'm sure everyone of you has seen the results of a Web search. Almost all of them contain a set of links so you can browse the results in case there are too many to display in one page. I call that set of links a Browsing Controls Bar. In our case, the user will use it to browse the past messages. Below you have the PHP function that is used to generate such a Browsing Controls Bar.
<?php
function ControlsBar ($pos, $rows, $total_found_rows, $max_page_rows, $ctl_bar_class = 'ctlbar', $href = '?pos=') {
// Check for invalid starting offset:
// If negative force to zero,
// if bigger than the total number of results,
// force to total number of results - 1
// (in order to see at least one result).
$pos = ($pos < 0) ? 0 : (($pos >= $total_found_rows) ? $total_found_rows-1 : $pos);
// Build the links that allow to browse through the results.
$pages = '<b>Page</b>: ';
for ($i=0; $i<($total_found_rows / $max_page_rows); $i++)
$pages .= ($pos == ($i * $max_page_rows)) ? '<b> '.($i + 1).' </b>' : '<a href="'.$href.($i * $max_page_rows).'"> '.($i + 1).' </a>';
// Now the link to the previous page of results.
$prevp = ($pos == 0) ? " " : '<a href="'.$href.($pos - $max_page_rows).'"><< Previous</a>';
// And of course to the next page of results.
$nextp = ($rows < $max_page_rows) ? " " : '<a href="'.$href.($pos + $max_page_rows).'">Next >></a>';
// And now the control bar.
$ctl_bar = '<table class='.$ctl_bar_class.' width="100%" cellspacing=0 cellpadding=0 border=0>';
$ctl_bar .= '<tr>';
$ctl_bar .= '<td align=left style="width: 100px">'.$prevp.'</td>';
$ctl_bar .= '<td align=center>'.$pages.'</td>';
$ctl_bar .= '<td align=right style="width: 100px">'.$nextp.'</td>';
$ctl_bar .= '</tr>';
$ctl_bar .= '</table>';
Return $ctl_bar;
}
?>
Now, a little explaining:
- $max_page_rows is a the maximum number of results to display in one page
- $total_found_rows is the total number of results, which normally you would determine by doing a SELECT COUNT(*) FROM olchat query, and saving the fetched result in a variable, passing it as argument to this function
- $rows is the number of results that the query using LIMIT n,m returned. It can be either bigger or smaller than $max_page_rows. We will use this to detect when we are in the first, middle or last set of results
- $pos is the variable that stands for the starting offset at which to begin displaying $max_page_rows results. This variable will be actually provided to the script
- $ctl_class stands for the CSS class name that you would use to format the table of the controls bar.
- $href is the href for the controls bar links. It is usefull to have it as argument because you can add more variables that are to be passed to the script when browsing the results i.e. $href = "search.php?foo=blue&ben=yellow&pos=", or in case of a HTML form you can use JavaScript inside the href to assign values to the forms elements i.e. $href = "javascript:document.forms['oForm'].pos="
Of course, this is only an example and improvements/customisations can always be made.
Now we won't have to delete the last messages every time a user sends a new message. Instead we will provide him with a Browsing Controls Bar so he can browse the past messages. This is also helpful when you have a big number of online users chatting and the table gets filled with messages quickly. It avoids the loss of messages in case of a high message rate.
Next you will see sample of the PHP file that handles both message reading and writing and the Browsing Controls Bar.
Full Code Sample
<?php
define ('MAX_RESULTS_PER_PAGE', 10);
mysql_pconnect ('localhost', 'root', '');
mysql_select_db ('website');
function ControlsBar ($pos, $rows, $total_found_rows, $max_page_rows, $ctl_bar_class = 'ctlbar', $href = '?pos=') {
$pos = ($pos < 0) ? 0 : (($pos >= $total_found_rows) ? $total_found_rows - 1 : $pos);
$pages = '<b>Page</b>: ';
for ($i=0; $i<($total_found_rows / $max_page_rows); $i++)
$pages .= ($pos == ($i * $max_page_rows)) ? '<b> '.($i + 1).' </b>' : '<a href="'.$href.($i * $max_page_rows).'"> '.($i + 1).' </a>';
$prevp = ($pos == 0) ? " " : '<a href="'.$href.($pos - $max_page_rows).'"><< Previous</a>';
$nextp = ($rows < $max_page_rows) ? " " : '<a href="'.$href.($pos + $max_page_rows).'">Next >></a>';
$ctl_bar = '<table class='.$ctl_bar_class.' width="100%" cellspacing=0 cellpadding=0 border=0>';
$ctl_bar .= '<tr>';
$ctl_bar .= '<td align=left style="width: 100px">'.$prevp.'</td>';
$ctl_bar .= '<td align=center>'.$pages.'</td>';
$ctl_bar .= '<td align=right style="width: 100px">'.$nextp.'</td>';
$ctl_bar .= '</tr>';
$ctl_bar .= '</table>';
return $ctl_bar;
}
// Check the inputs and record new messages.
if (@$_POST['sender'] != '' || @$_POST['message'] != '') {
$sender = mysql_escape_string ($_POST['sender']);
$message = mysql_escape_string ($_POST['message']);
$query = "INSERT INTO olchat (date, sender, message) VALUES (NOW(), '".$sender."', '".$message."');";
mysql_query ($query);
}
// Get the total number of messages.
$query = "SELECT COUNT(*) as total_msgs FROM olchat;";
$res = mysql_query ($query);
$row = mysql_fetch_array ($row);
$total_found_rows = $row['total_mysgs'];
// Check the seeking offset.
$pos = (@is_numeric (@$_GET['pos'])) ? $_GET['pos'] : 0;
// Get the messages from the table.
$query = "SELECT * FROM olchat ORDER BY date DESC LIMIT ".$pos.", ".MAX_RESULTS_PER_PAGE.";";
$res = mysql_query ($query);
$rows = mysql_num_rows ($res);
// Display the messages and the Controls Bar.
echo '<hr>';
echo ControlsBar ($pos, $rows, $total_found_rows, MAX_RESULTS_PER_PAGE);
echo '<hr>';
while ($row = mysql_fetch_array($res)) {
echo date("m/d g:i A", strtotime ($row['date']))." ";
echo $row['sender'].": ";
echo $row['message']."<br>";
}
echo '<hr>';
echo ControlsBar ($pos, $rows, $total_found_rows, MAX_RESULTS_PER_PAGE);
echo '<hr>';
?>
<br>
<form name=oChatForm method=post>
Your name: <input type=text name=sender maxlength=32><br>
Your message: <input type=text name=message><br>
<input type=submit>
</form>
NOTE: $_POST/$_GET are specific to PHP 4.1+. If you use PHP < 4.1 then you should replace $_POST by $HTTP_POST_VARS, and $_GET by $HTTP_GET_VARS. The rest should work just fine.
Epilogue
Well, now you know how to implement an online chat. Of course you can customize it, add features like user login, private messages, rooms, user info tracking, and so on. A working example of an online chat with auto-refresh and a browsing controls bar can be found at http://bog.ath.cx.
I hope this was instructive enough to get you started and maybe get new ideas. Happy coding.
-Bogdan ROMAN
