Chat Application

Following along the line of the HelloWorld example, here is an example of a chat application for Feng Office. It is not a very useful application, but it will help us get to know more about hacking Feng Office (we call it “hacking Feng Office” and not “developing for Feng Office” because of the lack of a formal API). Let's call our little application ChatUp.

ChatUp will consist of a new tab in Feng Office's main interface, were a user will be able to see and join ongoing chats or create a new chat and wait for users to join. Once he joins a new chat he will see which users have joined the chat and what anyone has said since the chat begun, and he can now send messages to the chat and will also keep receiving other users chat messages as they are being sent. We'll keep the app simple for educational purposes, but we'll later see that we could do lots of things to make the app more useful and usable.

Our model will consist of:

  • a Chat object, with an id, a name, a start date and an end date.
  • a ChatLine object with an id, a chat id, a sender id, a text and a date.

We will have two screens:

  • a list of chats, were you can see ongoing chats.
  • a chat's view, were you can see a specific chat. You'll see who's in that chat and all messages that have been sent to the chat.

There are several things we need to do to create our two objects (Chat and ChatLine) in Feng Office. We will start by defining the PHP classes that will represent these two objects but before we do that lets see a brief introduction of models (the M in MVC) in Feng Office. If you take a look at the 'application/models' folder you will see many folders, each representing one object in Feng Office. For example, the 'contacts' folder defines the classes for Feng Office contacts. If you take a look inside that folder you will see this file structure:

  • base/
    • BaseContact.class.php
    • BaseContacts.class.php
  • Contact.class.php
  • Contacts.class.php

The code is separated in 'base' classes and non-base classes because this is how the original framework, that was inherited from activeCollab, defined objects. We now continue defining objects like this to be coherent with the rest of the code, and because it makes the code more readable as you'll see.

The BaseContact class basically defines all getters and setters. Take a look at that the file to see for yourself. Most of the functions in that class are called like getSomething and setSomething. There are however two elements that are not getters and setters. At the beginning there's a property called $objectTypeIdentifier. This property defines a two character code to identify the type of this object. For contacts this is 'ct', for companies 'co', etc. There's also another interesting function called 'manager' at the end of the file. All objects in Feng Office have this function, and it returns the objects manager. An object manager is a class in charge of performing queries against the database. In the case of contacts, the manager is the Contacts class.

Now let's take a look at the BaseContacts class. This class is in charge of mapping Feng Office Contact objects to the MySQL database. It does that by implementing a few functions called 'getColumns', 'getColumnType', 'getPkColumns' and 'getAutoIncrementColumn', as well as other functions ('finders') that are usually the same among all objects, but changing the name of the object. In the constructor we call the parent constructor, passing it two strings: the first one is the name of the object's class ('Contact') and the second is the name of the table that is mapped to this object ('contacts'). The 'getColumns' function returns the columns in that table that will be used by our object. The function 'getColumnType' returns the type of the column passed as an argument. To simplify these two functions, we define the columns as an array whose keys are the column names and the values are the column values. By doing this the 'geColumns' and 'getColumnType' functions are the same across all objects and just return the array and the value for a key in the array respectively. The 'getPkColumn' usually just returns the column 'id', but could return an array of column names that conform the objects Primary Key. Finally, the 'getAutoIncrementColumn' function returns which column increments automatically (if there is one).

A further note on columns: All Content Objects in Feng Office have some common columns, which are 'id', 'created_on', 'created_by_id', 'updated_on', 'updated_by_id', and may contain 'trashed_on' and 'trashed_by_id'. These columns are required for some functionality to work correctly, and so our Chat object will contain them. ChatLine is not really a Content Object and so will not contain these columns.

So following with the concepts defined in the previous section, these are the files that we should create in 'application/models':

  • chats/
    • base/
      • BaseChat.class.php
      • BaseChats.class.php
    • Chat.class.php
    • Chats.class.php
  • chat_lines/
    • base/
      • BaseChatLine.class.php
      • BaseChatLines.class.php
    • ChatLine.class.php
    • ChatLines.class.php

You can find the full source code of these files at Chat Application Source.

Both 'base' managers (BaseChats and BaseChatLines) will be the same as BaseContacts, except for the object names and the columns defined at the top. Here's the column definition for both:

	static private $columns = array(
		'id' => DATA_TYPE_INTEGER,
		'name' => DATA_TYPE_STRING,
		'start_date' => DATA_TYPE_DATETIME,
		'end_date' => DATA_TYPE_DATETIME,
 
		'created_on' => DATA_TYPE_DATETIME,
		'created_by_id' => DATA_TYPE_INTEGER,
		'updated_on' => DATA_TYPE_DATETIME,
		'updated_by_id' => DATA_TYPE_INTEGER,
		'trashed_on' => DATA_TYPE_DATETIME,
		'trashed_by_id' => DATA_TYPE_INTEGER,
	);
	static private $columns = array(
		'id' => DATA_TYPE_INTEGER,
		'chat_id' => DATA_TYPE_INTEGER,
		'sender_id' => DATA_TYPE_INTEGER,
		'text' => DATA_TYPE_STRING,
		'date' => DATA_TYPE_DATETIME,
 
		'created_on' => DATA_TYPE_DATETIME,
		'created_by_id' => DATA_TYPE_INTEGER,
		'updated_on' => DATA_TYPE_DATETIME,
		'updated_by_id' => DATA_TYPE_INTEGER,
		'trashed_on' => DATA_TYPE_DATETIME,
		'trashed_by_id' => DATA_TYPE_INTEGER,
	);

BaseChat, since it is a Content Object, it will define the object type identifier as 'ch', and then it will include getters and setters for each of the columns, as well as the 'manager' function that returns an instance of 'Chats', like we saw in the Contacts class. Here's an excerpt from the code:

<?php
abstract class BaseChat extends ProjectDataObject {
	protected $objectTypeIdentifier = 'ch';
 
	function getId() {
		return $this->getColumnValue('id');
	} // getId()
 
	function setId($value) {
		return $this->setColumnValue('id', $value);
	} // setId()
 
	// (...) 
 
	function manager() {
		if(!($this->manager instanceof Chats)) $this->manager = Chats::instance();
		return $this->manager;
	} // manager
 
} // BaseChat
?>

BaseChatLine looks the same but has no object type identifier and different getters and setters.

Now onto Chats. This class will extend BaseChats. Now, just for extending this class we end with a useful class to perform queries on Chats, by using the find* functions. But we can add some extra functions that can save us some code later and will make our code easier to understand. One function that would be useful is one that returns all ongoing chats (chats that don't have an 'end_date'). We will use this function in one of the screens we designed in a previous section. This function would look like this:

WORK IN PROGRESS…