Topic markers have evolved quite a bit over the past few years. What started out as being an almost secondary concern has become quite an important part of the user experience.
A Very Brief History
Early versions of IP.Board relied on cookies to track read topics. This worked fairly well but it wasn't without problems. Anything to do with cookies is always a little flaky. There is a very finite amount of information you can store and browsers have a habit of eating them or not setting them correctly. The biggest complaint, however, is that topics that you have read on one computer do not stay read on another computer.
IP.Board 2 introduced topic markers that are stored in a database table. This greatly increased stability and allowed the read topics to remain read regardless of computer used.
However, this did lead to some performance issues on busier boards as the topic marker table is often in demand whcih can result in locking issues and processes queueing. Also, the code wasn't centralized and sprawled through different files making maintenance very difficult.
Learning From Experience
It was obvious we needed to centralize all the code and create a common, simple public interface as a matter of course. We also wanted to allow our other applications (such as blog and gallery) to use this system without having to copy code. Making it truly extensible also allows modification authors to use this centralized system without having to maintain their own code.
Next up was the performance issue. How could we increase performance without losing functionality? The most obvious answer was to store the read topics in the user's session. This was fine in principle but there were obstacles to overcome. First of all, session handling in IP.Board 2 is not entirely centralized. There is a class but it is not used exclusively. Other files such as register.php and login.php are allowed to modify the session table without telling other classes what it did. When you're trying to preserve data, this cannot be allowed. Also, session handling in general isn't an exact science. IP addresses change meaning new sessions can be created several times during a single visit.
The solution to these problems must be robust, centralized and extensible.
So, what's new?
First up was tightening up session handling. All session management is now performed through a single class (publicSessions). Other files that need to manage sessions are done so via publicSessions. This allows this one class to keep tight control over the session data.
The biggest challenge was to allow sessions to handle the marker data and only allowing the markers to be written back to the database table when the session is deleted. The theory is sound and simple but the execution a little trickier. The gory details of which are explained a littler further down for the technically minded.
In brief, the system loads the markers from the database table when a new member session is created. The markers are saved in with the rest of the session data when the session is updated at the end of a page view (to update the running time and location, etc). This continues until the session is due to be removed, for example, when the member is no longer active and the session is older than the allowed time. The sessions to be removed are captured and allowed to be examined by other classes. The item marking class examines these sessions and writes back the marker information to the marker database. The system also makes use of cookies to give some permanence for guests and those that are not logged in. The cookies are limited to the last 100 items read to prevent cookies being sent that are too large.
The end result is a much leaner system which should be much more efficient and presents a very simple public interface. Here's some sample code to give you a taste:
$itemMarking->markRead( array( 'forumID' => 2, 'topicID' => 10 ) ); # To fetch the read status of an item if ( $itemMarking->isRead( array( 'forumID' => 2, 'itemID' => 99, 'itemLastUpdate' => 1200098989 ) ) === TRUE ) { .... } # To fetch the last time an item was marked $lastMarked = $itemmarking->fetchTimeLastMarked( array( 'forumID' => 2, 'itemID' => 99 ) );
# To mark an item read (an item is a topic for the forums 'application')
Modification authors can write their own plug-ins and make use of the system with minimal coding effort.
Listen Up, Here Comes The Science Bit
For the more technically minded, this section explains how the system works within the new IP.Board 3.0.0 framework.
The first challenge was to make proper use of __construct and __destruct within PHP 5. This is very straightforward in the case of __construct; this is run when the class is initialized. There are no problems with that. The item marking class grabs and filters the cookie data and also grabs and filters the session item marking data in __construct.
The real issue is with __destruct. Specifically the order in which they are called. In PHP 5.0.0 to PHP 5.2.4 they are called in the order they were created. This means that by the time __destruct() is called in item Marking the DB connection has been closed which isn't at all helpful. This is because the DB is set up before classItemMarking. Now, as of PHP 5.2.5 the __destruct order has been reversed. This means that the DB connection will still be available.
However, that's not very convenient for code that has to be robust on different platforms and with unpredictable version of PHP. Another solution was needed.
Thankfully register_shutdown_function() executes before any __destruct() calls, so we can reliably use this. There is a __myDestruct function in the ipsRegistry class which calls __myDestruct() in all the registry child classes (DB, Member, Request, Cache, Settings, etc). The member registry class makes use of this to fire a manual destruct function within itemMarking to allow it to save back any data for deleted sessions and to provide information for the session update. A __myDestruct fires within the public session class to actually update the sessions and delete the expired ones.
Phew! As you can see, there's a lot going on behind the scenes in IP.Board 3.0.0 which makes full use of all PHP 5 has to offer.