[How-To] Extend and use the Session Table Effectively
by
15 Jul 2007
[How-To] Extend and use the Session Table Effectively Have you ever wondered what would be the most efficient way to pass information between pages as a user moves through the forum? What about if you wanted to check for a certain condition in a few different places, but the check itself involved a few different operations and you were worried that this might add too much overhead on a busy forum? You can make use of vBulletin's session table to store this information. This will also have the added benefit of other people being easily able to make use of your session variables in their own modifications. The basics Let's make up a fictitious example. Imagine we want to create a session variable called "mozillian". This variable will be set according to whether the user has the text string "Mozilla" in their user agent. Sure, it would be simple to test the user agent variable ourselves, but we are trying out a simple example. Now because we don't want to have to do our test every time, we'll test it just once when the session is first created and then store the result in a variable. Let's go ahead and create an extra column in our session table. We'd use a query (or installation code) something like this: Code:
$vbulletin->db->query_write("ALTER TABLE " . TABLE_PREFIX . "session ADD mozillian TINYINT(4) DEFAULT 0 NOT NULL"); Now that our column is ready we'll be able to access the result in our plugins as $vbulletin->session->vars['mozillian'], or $session[mozillian] in templates. First we'll take a look at how to get it there and some other issues. Using vBulletin's built-in session class and session functions At this point you may also want to check out the vBulletin 3.6 http://members.vbulletin.com/api/, specifically in the "Class(es)" section under "vB_Session". Under "Method Summary" you'll see methods that concern us - build_query_array(), save() and set(string $key, string $value). Of these three methods, we'll only be using one of them directly. Before we go on, let's take a quick look at how vBulletin handles sessions from the beginning of a page load to the end. Near the beginning of each page load vBulletin checks to see if a session exists for the current user, and if not it creates one. If an existing session is found, all of the columns in the session table are loaded automatically into the $vbulletin->session-vars array. This makes life very easy for us, since we don't have to do anything to read these values once they are written to the session table. As the page loads other details may need to be changed in the session table, for example the user's location within the forum or the number of unread messages. When these changes occur the set(string $key, string $value) method is used to change the variables, but they are not saved immediately to the database. As each key is updated with a new value vBulletin tracks which variables need to be changed. Just before the page finishes loading (and output is flushed to the browser) the save() method is called and any changes (and only those changes) are written to the session table, ready to be read again when the next page loads. How should we apply these functions? Our logic is very simple - first we need to check to see if our variable has been set - if not, this is the beginning of a session and we need to execute whatever code we need to determine how our session variable should be set. For our above example it means we would be starting a plugin at the global_start hook which would begin something like this: Code:
if ($vbulletin->session->vars['mozillian'] == 0) { // Set to 1 if user agent contains "Mozilla", or -1 if it doesn't $mozilla = strpos(USER_AGENT, 'Mozilla') ? 1 : -1; So our code is only ever going to execute once, and that's when the session is first created. Once our session variable "mozillan" is set, we don't need to run our code again (important point to save some resources on a busy system). Now we need to write our variable to the session table. However, because our column is "non-standard" the methods within the session class will not accept our changes until we push the column name into a special array like this: Code:
$vbulletin->session->db_fields = array_merge($vbulletin->session->db_fields, array('mozillian' => TYPE_INT)); Now that we've made sure our new variable will be included in the update, it's time to set it: Code:
$vbulletin->session->set('mozillian', $mozilla); Code:
if ($vbulletin->session->vars['mozillian'] == 0) { // Set to 1 if user agent contains "Mozilla", or -1 if it doesn't $mozilla = strpos(USER_AGENT, 'Mozilla') ? 1 : -1; $vbulletin->session->db_fields = array_merge($vbulletin->session->db_fields, array('mozillian' => TYPE_INT)); $vbulletin->session->set('mozillian', $mozilla); } Caveat one - not using save() Note that we didn't use the $vbulletin->session->save() method. Why not? My own tests have shown that if you do, you sometimes get the results overwritten with the other save() operation that vBulletin performs. There's nothing in the documentation to warn about this, I discovered it only after a lot of digging around and head-scratching. See this thread for an example of a problem I had. Therefore we simply use the set() method to put our variables into the list of changes to be written later to the session table. When the page ends, all the changed variables will be written to the session table automatically and we didn't need to use any additional queries! Remember - if you do use save() manually, you risk not having your data written correctly to the session table - just don't do it. Caveat two - writing zero or a null value with set() You'll see from our example that we basically use three states - the user is a mozilian, they are not a mozillian, or we don't yet know. Now you've probably been wondering why we haven't simply used a boolean true/false here, and rely on the value being NULL or simply not set (testing with the isset() function for example) when we don't know. This seems like a reasonable idea, except for one small problem - during the set() method we encounter this code: Code:
if ($this->vars["$key"] != $value) Code:
if (NULL != 0) For this reason I've chosen to make the default value 0, and the "true / false" states 1 and -1 respectively. You may want to verify your results by looking into your session table and/or writing out log entries to make sure that everything is working the way you expect it to. Caveat three - using string values and null with set() Imagine that instead of a numerical value we were dealing with a string. Let's say that we record some other detail in our "mozillian" column like the operating system the browser is running on. If we can't find it, we'll write a string like '' which will cause the variable to be set, then we can use isset() to test whether we need to run it. Imagine the previous example, but something like this instead: Code:
if (!isset($vbulletin->session->vars['mozillian']) { if ($some_condtion) { $mozilla = 'PC'; } else { // Didn't find what we were looking for, but we don't want to do this again. $mozilla = ''; } $vbulletin->session->db_fields = array_merge($vbulletin->session->db_fields, array('mozillian' => TYPE_INT)); $vbulletin->session->set('mozillian', $mozilla); } Code:
$vbulletin->session->vars['mozillian'] = trim($vbulletin->session->vars['mozillian']); Examples There are two mods which use these techniques and it was while I was writing and testing these mods that I discovered many of these issues. I thought that by writing this tutorial I might be able to help others save a lot of the time that I wasted learning from my mistakes. The GLA (Geographic Location Awareness) mod demonstrates setting a variable (or three variables to be precise) as strings, and the Remove Spiders from Who's Online mod demonstrates setting an integer (which acts like a boolean). If you study these mods it may help you to understand the practical application of this tutorial. |