Facebook Like Chat Application in PHP

Facebook's Chat application is more social and it has a good user base. Making a simple chat application like Facebook is pretty much simple. In this post i will show you how make a simple chat application just like facebook.

Facebook Like Chat Application


Database Design

Database Schema

There are two tables in this design, users and chat. This schema design does not implement real authentication system. It just simulates a situation where both the users are logged in by passing the user id through the browser address bar i.e as a parameter.

The chat table has a foreign key reference to the users table id field.

Chat Class

The Chat Class (FbChatMock) contains the core methods for adding and retrieving the chat messages from the database. This class also connects to the database when the object is first created.

The methods in this class is easy to understand. The addMessage() method adds the user provided message by escaping it through real_escape_string and adds the message into the database. The user id is casted into an integer so that nothing gets inserted other than integer value.

The getMessages method makes a JOIN statement with both the users and chat table on the user_id field.

FbChatMock.php

/**
 * @author Tamil selvan k
 */
class FbChatMock {

  // Holds the database connection
  private $dbConnection;
  
  //----- Database connection details --/
  //-- Change these to your database values
  
  private $_dbHost = 'localhost';
  
  private $_dbUsername = 'root';
  
  private $_dbPassword = '';
  
  public $_databaseName = 'fbchat';
  
  //----- ----/

  /**
   * Create's the connection to the database and stores it in the dbConnection
   */
  public function __construct() {
    $this->dbConnection = new mysqli($this->_dbHost, $this->_dbUsername, 
        $this->_dbPassword, $this->_databaseName);

    if ($this->dbConnection->connect_error) {
      die('Connection error.');
    }
  }

  /**
   * Get the list of messages from the chat
   * 
   * @return array
   */
  public function getMessages() {
    $messages = array();
    $query = "
        SELECT 
          `chat`.`message`, 
          `chat`.`sent_on`,
          `users`.`id`, 
          `users`.`username`
        FROM `users`
        JOIN `chat`
          ON `chat`.`user_id` = `users`.`id`
        ORDER BY `sent_on`";

    // Execute the query
    $resultObj = $this->dbConnection->query($query);
    // Fetch all the rows at once.
    while ($row = $resultObj->fetch_assoc()) {
      $messages[] = $row;
    }
    
    return $messages;
  }

  /**
   * Add a new message to the chat table
   * 
   * @param Integer $userId The user who sent the message
   * @param String $message The Actual message
   * @return bool|Integer The last inserted id of success and false on faileur
   */
  public function addMessage($userId, $message) {
    $addResult = false;
    
    $cUserId = (int) $userId;
    // Escape the message with mysqli real escape
    $cMessage = $this->dbConnection->real_escape_string($message);
    
    $query = "
      INSERT INTO `chat`(`user_id`, `message`, `sent_on`)
      VALUES ({$cUserId}, '{$cMessage}', UNIX_TIMESTAMP())";
    $result = $this->dbConnection->query($query);
    
    if ($result !== false) {
      // Get the last inserted row id.
      $addResult = $this->dbConnection->insert_id;
    } else {
      echo $this->dbConnection->error;
    }
    
    return $addResult;
  }

}

Chat Page

The chat.php page initially fetches the previous chat messages if there is one and displays it. This page is the main chat page. Here is where the user is stored in the session which is got from the query parameter

chat.php

<?php
@session_start();

$_SESSION['user_id'] = isset($_GET['user_id']) ? (int) $_GET['user_id'] : 0;

if ($_SESSION['user_id'] === 0) {
  echo '<h1>
Provide your user id</h1>
';
  exit;
}

// Load the messages initially
require_once __DIR__ . '/../FbChatMock.php';
$chat = new FbChatMock();
$messages = $chat->getMessages();
?>
<div class="container" style="border: 1px solid lightgray;">
  <div class="msg-wgt-header">
    <a href="#">John</a>
  </div>
  <div class="msg-wgt-body">
    <table>
      <?php
      if (!empty($messages)) {
        foreach ($messages as $message) {
          $msg = htmlentities($message['message'], ENT_NOQUOTES);
          $user_name = ucfirst($message['username']);
          $sent = date('F j, Y, g:i a', $message['sent_on']);
          echo <<<MSG
          <tr class="msg-row-container">
            <td>
              <div class="msg-row">
                <div class="avatar"></div>
                <div class="message">
                  <span class="user-label"><a href="#" style="color: #6D84B4;">{$user_name}</a> <span class="msg-time">{$sent}</span></span><br/>{$msg}
                </div>
              </div>
            </td>
          </tr>
MSG;
        }
      } else {
        echo '<span style="margin-left: 25px;">No chat messages available!</span>';
      }
      ?>
    </table>
  </div>
  <div class="msg-wgt-footer">
    <textarea id="chatMsg" placeholder="Type your message. Press Shift + Enter for newline"></textarea>
  </div>
</div>

Ajax Components

There are two main ajax pages,

  1. add_msg.php - Adds the messages entered by the user to the database.
  2. get_messages.php - Retrieves the messages from the database.

add_msg.php

session_start();

if (isset($_POST['msg'])) {
  require_once __DIR__ . '/../../core/FbChatMock.php';
  
  $userId = (int) $_SESSION['user_id'];
  // Escape the message string
  $msg = htmlentities($_POST['msg'],  ENT_NOQUOTES);
  
  $chat = new FbChatMock();
  $result = $chat->addMessage($userId, $msg);
}

get_message.php

require_once __DIR__ . '/../../core/FbChatMock.php';

$chat = new FbChatMock();
$messages = $chat->getMessages();
$chat_converstaion = array();

if (!empty($messages)) {
  $chat_converstaion[] = '<table>';
  foreach ($messages as $message) {
    $msg = htmlentities($message['message'], ENT_NOQUOTES);
    $user_name = ucfirst($message['username']);
    $sent = date('F j, Y, g:i a', $message['sent_on']);
    $chat_converstaion[] = <<<MSG
      <tr class="msg-row-container">
        <td>
          <div class="msg-row">
            <div class="avatar"></div>
            <div class="message">
              <span class="user-label"><a href="#" style="color: #6D84B4;">{$user_name}</a> <span class="msg-time">{$sent}</span></span><br/>{$msg}
            </div>
          </div>
        </td>
      </tr>
MSG;
  }
  $chat_converstaion[] = '</table>';
} else {
  echo '<span style="margin-left: 25px;">No chat messages available!</span>';
}

echo implode('', $chat_converstaion);

Javascript

This scripts helps in getting the messages with an interval of 20 seconds for each messages fetching. The binding of users keyboard event is also done in this script. jQuery is needed for this scripts to work properly.

chat.js

/**
 * Add a new chat message
 * 
 * @param {string} message
 */
function send_message(message) {
  $.ajax({
    url: '/ajax/add_msg.php',
    method: 'post',
    data: {msg: message},
    success: function(data) {
      $('#chatMsg').val('');
      get_messages();
    }
  });
}

/**
 * Get's the chat messages.
 */
function get_messages() {
  $.ajax({
    url: '/ajax/get_messages.php',
    method: 'GET',
    success: function(data) {
      $('.msg-wgt-body').html(data);
    }
  });
}

/**
 * Initializes the chat application
 */
function boot_chat() {
  var chatArea = $('#chatMsg');

  // Load the messages every 5 seconds
  setInterval(get_messages, 20000);s  

  // Bind the keyboard event
  chatArea.bind('keydown', function(event) {
    // Check if enter is pressed without pressing the shiftKey
    if (event.keyCode === 13 && event.shiftKey === false) {
      var message = chatArea.val();
      // Check if the message is not empty
      if (message.length !== 0) {
        send_message(message);
        event.preventDefault();
      } else {
        alert('Provide a message to send!');
        chatArea.val('');
      }
    }
  });
}

// Initialize the chat
boot_chat();

CSS Style

core.css

/* message box header styles*/

.msg-wgt-header {
  background-color: #6D84B4;
  border: 1px solid rgba(0, 39, 121, 0.76);
  color: white;
  text-align: center;
  height: 28px;
}

.msg-wgt-header a {
  text-decoration: none;
  font-weight: bold;
  color: white;
  vertical-align: middle;
}

/* message box body styles*/

.msg-wgt-body {
  height: 300px;
  overflow-y: scroll;
}

.msg-wgt-body table {
  width: 100%;
}

.msg-row-container {
  border-bottom: 1px solid lightgray;
}

.msg-row-container td {
  border-bottom: 1px solid lightgray;
  padding: 3px 0 3px 2px;
}

.msg-row {
  width: 100%;
}

.message {
  margin-left: 40px;
}

/* Message box footer styles*/

.msg-wgt-footer {
  height: 50px;
}

.msg-wgt-footer textarea {
  width: 95%;
  font-family: 'tahoma';
  font-size: 13px;
  padding: 5px;
}

.user-label {
  font-size: 11px;
}

.msg-time {
  font-size: 10px;
  float: right;
  color: gray;
}

.avatar {
  width: 24px;
  height: 24px;
  float: left;
  background: url('/assets/chat_avatar.jpg');
  border: 1px solid lightgray;
}