Christoffer Kjølbæk I have a blog, therefore I am…

18Jul/100

PHP function to get current SVN ID

Make a function like the below in you PHP file

function get_rev() {
	return (int)substr(substr('$Rev: 168 $', 6), 0, -2);
}

And add the Revision keyword to the properties of the file

svn propset svn:keywords Revision your_file.php

"$Rev: 168 $" will then automatically be updated on every commit.

26Apr/100

What is an optimal object-oriented program with an underlying database?

Consider a very simple message board system consisting of a user and amessages table in the database. The user table would look like the following:

+----+-------------+
| id | username    |
+----+-------------+
|  1 | John Doe    | 
|  2 | Jane Doe    | 
|  3 | Richard Roe | 
+----+-------------+

and the messages would look like the following:

+----+-------------+---------+--------------------------------------------------------------------------+
| id | in_reply_to | user_id | message                                                                  |
+----+-------------+---------+--------------------------------------------------------------------------+
|  1 |           0 |       2 | What is an optimal object-oriented program with an underlaying database? | 
|  2 |           0 |       1 | What does cohesion mean?                                                 | 
|  3 |           1 |       3 | Donno, check ostehamster.dk                                              | 
|  4 |           2 |       1 | Try http://en.wikipedia.org/wiki/Cohesion_%28computer_science%29         | 
+----+-------------+---------+--------------------------------------------------------------------------+

Now, if we want to show the message with id 1 and all answers, together with the name of the user posting it, it is very simple

SELECT u.username, m.message
FROM messages m
LEFT JOIN user u ON u.id = m.user_id
WHERE m.id = 1 OR m.in_reply_to = 1 
ORDER BY m.id ASC;

This results in :

+-------------+--------------------------------------------------------------------------+
| username    | message                                                                  |
+-------------+--------------------------------------------------------------------------+
| Jane Doe    | What is an optimal object-oriented program with an underlaying database? | 
| Richard Roe | Donno, check ostehamster.dk                                              | 
+-------------+--------------------------------------------------------------------------+

Very simple!

Object-oriented
Now, if we want to make this object-oriented with high cohesion, a possibility for inheritance and all the other buzzwords, we need a User and a MessageBoard class.

class User {
   int user_id;
   std::string username;
 
   public:
      User(int user_id);
      std::string get_username(void);
      int get_user_id(void);
 
   private:
      void load_user(int user_id);
}
class MessageBoard {
   int msg_id;
   std::string[] messages;
 
   public:
      MessageBoard(int msg_id);
      std_string print(void);
 
   private:
      void load_messageboard(int msg_id);
}

Actually, it might be more correct to have a Message class too. The Message class would handle each unique message, and the MessageBoard would then link a number of Message objects together. However, to make it more simple, we only use the MessageBoard class.

If the User and MessageBoard should have high cohesion, the classes should be wrappers for the underlying tables, and the above SQL query with the LEFT JOIN cannot be used. It should be possible for the User developers to change the database design and the internal functionality of the User class, without the MessageBoard developers needing to make any changes, as long as the User API does not change.

The User class could have a function returning the "LEFT JOIN" statement, which could then be put into the query made by the MessageBoard class. But in my opinion this will result in lower cohesion, because users and messages could be stored in different databases, hosts and even different formats.

If the tables can not be joined in any way, the print function could be implemented like:

void print() {
   conn = mysql_init(NULL);
   if (!mysql_real_connect(conn, server, user, password, database, 0, NULL, 0)) {
      mysql_query(conn, "SELECT user_id, message FROM messages m WHERE m.id = 1 OR m.in_reply_to = 1  ORDER BY m.id ASC;")
 
      res = mysql_use_result(conn);
      while ((row = mysql_fetch_row(res)) != NULL) {
         User user = new User(atoi(row[1]));
         printf("User: %s, Message: %s\n", user.get_username(), row[2]);
      }
 
      mysql_free_result(res);
   }
 
   mysql_close(conn);
}

However, for each line in the message board this will make a new instance of the User class, which again will make a connection and a query to the database. For a message board it will not be a lot of calls, but in bigger and more complex systems it can be a serious bottleneck.

Caching objects
This could be solved by having a User cache, like this static function:

static User* get_cached_user(int user_id) {
   static std::vector< User* > cache;
 
   vector< User* > ::iterator it;
   /* Is the user chached? */
   for ( it=cache.begin() ; it < cache.end(); it++ ) {
      if( (*it).get_user_id() == user_id) {
         return (*it);
      }
   }
 
   /* Generate and cache user */
   cache.push_back(new User(user_id));
   return cache.back();
}

This will decrease the number of queries to the database. However it might still result in a lot of calls to the database compared to the "LEFT JOIN" query.

The last option I can think of is to make a static function in the User class called

User*[] get_multiple_user(int[] ids)

The function takes an array of user ids, fetches all user data from the database in one query, and then generates an array of User objects pointers. This caching method will work like the one above, but all users are found using one database call, and thereby it decreases the total number of database queries to two.

But what is the right solution?