Go Back   vb.org Archive > vBulletin Article Depository > Read An Article > vBulletin 3 Articles
FAQ Community Calendar Today's Posts Search

Reply
 
Thread Tools
[Tip] Debugging Your Plugin - how to save time and frustration
mfyvie
Join Date: Mar 2007
Posts: 336

I run a forum for English-speaking people in Switzerland

Zurich, Switzerland
Show Printable Version Email this Page Subscription
mfyvie mfyvie is offline 07-02-2007, 10:00 PM


Debugging Your Plugin - how to save time and frustration


Have you ever tried to write a plugin (or any piece of code) and been frustrated when it doesn't work? If you've been doing this for a while you've no doubt got your own debugging techniques. But when you are starting out, you probably don't really know what to do.

The purpose of this tutorial is to try to "jump start" your debugging approach. It's mostly aimed at people who are just beginning to write their own plugins. Adopting some form of debugging technique can save you significant time. I wish I'd done it from the very beginning!

Before we discuss various debugging techniques, let's consider the types of error situations we may encounter. Then we'll look at ways of avoiding or fixing them. We can divide our errors up into two broad categories:
  • Syntax or parsing errors - PHP doesn't like the format of what you've written
  • Database errors - bad queries will usually throw up an error on the user's browser. This is usually very bad
  • Logic errors - Your program will run, but it won't do what you expect it to
Different errors require different approaches and different tools.

Syntax or parsing errors

In many ways these are the easiest errors to deal with, because PHP will immediately write an entry to your error log. If you are going to be writing plugins, you need to get access to your error log to check if there are syntax or parsing errors.

Your error log depends on your webserver. If you are using apache on linux you'll usually find it in /var/logs/httpd/error_log. It's a good idea to use a command like tail -f /var/log/httpd/error_log to "follow" the log in real time. You can then have the log in one window and load the page on your forum in another. This way you can see immediately if there are any errors.

What will an error look like? Here's an example:
Code:
[client 77.57.16.130] PHP Parse error:  parse error, unexpected ')' in /home/.sites/81/site18/web/online.php(135) : eval()'d code on line 13, referer: http://www.englishforum.ch/online.php?order=asc&sort=username&pp=30&page=1
[client 77.57.16.130] PHP Parse error:  parse error, unexpected ')' in /home/.sites/81/site18/web/online.php(135) : eval()'d code on line 13, referer: http://www.englishforum.ch/online.php?order=asc&sort=username&pp=30&page=1
These error messages can be very useful, in that they contain information to help you narrow down the problem. First of all the error seems to be about an unexpected ')'. Chances are that we've forgotten to put a '(' somewhere else in our statement. If you aren't sure what the error means, google it. There's other important information here, namely the file we were running, in this case online.php. The number in brackets (135) is the line number of online.php where the error occurred. If we look at that line we see it is the hook for online_start. Since you are debugging just after writing your plugin, you probably don't need to know which hook you are using. The important information follows: eval()'d code on line 13. This means that the error is found on line 13 of our plugin.

Bear in mind that the error was encountered on line 13, but the cause may actually lie one or two lines before that. If you forget a semicolon for example, the error usually shows up on the following line.

What if you are on a shared server and you don't have access to the error log? You need to talk to your hosting provider and see if they can somehow provide this information to you (in real time). If not, then you are going to have to install vBulletin on another machine (like your own PC) and do your development work on the local machine. You can find a tutorial on how to do this here.

A better way is to avoid syntax or parsing errors altogether by using a PHP IDE (Integrated Development Environment), in other words, a PHP editor application with built-in syntax checking (an IDE does a lot more, but I'm trying to keep the explanation simple). I use phpedit, but there are many others. This allows me to write directly in the editor and if there are any syntax or parsing errors they are underlined immediately. This means I can ensure that my code is error free, then I paste it into the plugin and test it.

You may also ask if you can break your forum with a syntax error in one of your plugins. Usually the answer to this is no, since the entire plugin won't execute. However, if there something in the plugin that is required by a plugin, which executes later and needs the information for say a database query, this could cause a problem if the value is not set. We'll cover database errors shortly.

Regardless of whether you are doing error checking in a PHP editor, it's still an idea to keep an eye on your error log, especially if you were developing a plugin on your production system (which of course you would never do, right? No of course, not - never).

Database errors

These tend to be show-stoppers. The user will be presented with a big page in their browser explaining that mysql has produced an error and nothing else will happen. It will give the error details directly in the browser though. You need to act quickly, especially if this is affecting every page on your forum. Go back and disable your plugin and ensure that your pages load again, and then to try figure out the problem. If you have your system set up to email the database errors to you, check your email inbox. If this feature isn't set up, then go to your vBulletin settings under "Error Handling & Logging" and check.

The good news is that you'll get a copy of the query that caused the error, along with the approximate point that mysql objected to. If you can't see the error straight away try and copy and paste the failed query from the error message into a tool like *link phpmyadmin (which allows you to submit queries directly), or use the execute query option in the admincp under "Maintenance". I prefer something like phpmyadmin - if you don't have it you should install it or ask your hosting provider to provide access for you.

Fixing mysql query errors is beyond the scope of this document, you'll have to google for the correct syntax, or look for other tutorials on building sql queries.

Logic errors

These are the hardest types of errors to track down. They usually don't produce any error messages, but you don't get the results you expect. If you are working with a series of conditional statements or variables which rely on other variable values, you need to verify that the variables contain the values that you want and that conditionals are branching in the way you expect. Let's consider a small routine:
Code:
$uname = $vbulletin->usersinfo['username'];
if ($uname == "mark")
{
    $foo = 'bar';
}
if ($foo == "bar")
{
    // routine to change our template here
}
When we run our plugin while logged in, our template didn't change the way we wanted it to. Maybe we have a problem with the routine that changes the template, but since that is going to be harder to debug, let's first try and figure out if we actually got to the point where that routine was supposed to be executed. After confirming that you have no syntax or parsing errors (see above), you may wish to alter your routine like this:
Code:
$uname = $vbulletin->usersinfo['username'];
print 'uname was set to: ' . $uname . '\n';
if ($uname == "mark")
{
    print 'We entered the first conditional';
    $foo = 'bar';
    print 'foo was set to: ' . $foo . '\n';
}
if ($foo == "bar")
{
    print 'We entered the second conditional';
    // routine to change our template here
}
print 'We are done';
By using the print statement our text will be output directly to the browser. This should be seen as a "quick and dirty" method, and usually not desirable since users may end up seeing this information on the pages they load. We'll look at a better method in a minute, but for the meantime let's examine the output from this routine:
Code:
uname was set to:
We are done
What about the rest? Now we know that we never made it into either conditional. Why not? Because $uname never contained the value we expected. So obviously we should find out why that didn't happen and examine the line immediately above. You probably already spotted the error - we used $vbulletin->usersinfo['username'] rather than $vbulletin->userinfo['username']. Once we remove the offending "s" and rerun our plugin, this is now what we see:
Code:
uname was set to: mark
We entered the first conditional
foo was set to: bar
We entered the second conditional
We are done
That's better. Did our template edit work? If it did - we are happy. If it didn't, then at least we know we have to debug the next statements and that we did arrive at the template change routine as we expected to.

By using this technique you can save yourself a lot of head scratching. It's much better to know exactly where your program is going and what values are being placed in variables rather than guessing. But printing to the browser is not a very elegant way to test things, especially in a production environment, so let's look at a few ways to make our development and testing process a little more elegant.

Limiting the execution of your plugin

During the testing phase, why have your plugin executed by everybody? If you have used the print statement (as in the example above), then it would be better that they never see the output. Also, if you introduce errors or break logic that affects other parts of your forum then this may affect other functions in an unexpected way. But of course, you'd never develop or test on a production forum, would you? Whenever I'm testing something new I usually wrap the whole plugin in something like this:

Code:
if ($vbulletin->userinfo['userid'] == 1)
{
    // my plugin code here
}
This is extremely simple and means that the plugin will only run whenever I happen to load that page (since my userid is 1), but not for anyone else.

Loading in some additional functions during development

Do you have a little tool kit of functions that make life easier when developing? Why not load them at the top of your plugin during the development and testing phase, then remove them when you are finished. I load my extra functions by inserting this line at the top of each of my plugins:
Code:
include_once(DIR . '/includes/devtools.php');
This file contains the functions I use. If I need a new function, I simply insert it into this file. I've attached a sample of this file to the end of this tutorial containing the two functions about to be covered.

Rather than using the print statement, I use a function called print_log(). Here's a copy of the function:
Code:
function print_log($string)
{
    global $vbulletin;
    // to assist with debugging
    $file = fopen(DIR . "/stats/devlog.log", "a");
    fwrite($file, date("d.m.Y H:i:s") . " " . $vbulletin->userinfo['username'] . " " . $string . "\n");
    fclose($file);
}
It's very simple, and just keeps a running log with whatever I want. I can then watch this log in realtime with tail -f ./stats/devlog.log if I like (for those running linux). Using it within my program is really simple. To take an example from above it would look something like this:
Code:
print_log('foo was set to: ' . $foo);
This will give me an output something like this:
Code:
30.06.2007 07:43:01 mark foo was set to: bar
Note that the date, time and username were added here by the function itself, but you can customise this with whatever you like. Now you can have your all your debug information going to a separate log. This means you can log information about live user sessions easily, without anyone seeing anything in their browser.

Another tool which can be very useful is the dump_hex() function, which is also included in my devtools.php file. I originally copied this routine from this page, but have since extended it to add some more functionality (like an ASCII dump mode and the ability to change the numbers of columns and column widths).

You can look inside the function itself to see the various options, but here's a sample of what happens if I combine it with print_log. Imagine you want to see exactly what is inside a template - not how it is presented to you in the editor, but what is really stored. You might do something like this:

Code:
print_log(dump_hex($vbulletin->templatecache['WHOSONLINE']));
Here's a sample of the first part of the output:
Code:
0000  24 73 74 79 6c 65 76 61  72 5b 68 74 6d 6c 64 6f    $styleva r[htmldo 
0010  63 74 79 70 65 5d 0d 0a  3c 68 74 6d 6c 20 64 69    ctype].. <html di 
0020  72 3d 5c 22 24 73 74 79  6c 65 76 61 72 5b 74 65    r=\"$sty levar[te 
0030  78 74 64 69 72 65 63 74  69 6f 6e 5d 5c 22 20 6c    xtdirect ion]\" l 
0040  61 6e 67 3d 5c 22 24 73  74 79 6c 65 76 61 72 5b    ang=\"$s tylevar[ 
0050  6c 61 6e 67 75 61 67 65  63 6f 64 65 5d 5c 22 3e    language code]\"> 
0060  0d 0a 3c 68 65 61 64 3e  0d 0a 24 68 65 61 64 69    ..<head> ..$headi
Now you can easily discover all the "hidden" characters if you are trying to a search and replace. For example, look at the last line - did you see the values "0d 0a"? These are carriage return and linefeed characters (new line). If you were looking at them in the template editor, you'd simply see a new line, but what if you wanted to locate or replace text with str_replace() within the template? Viewing a hex dump of variables or other data be very helpful sometimes. The other advantage is that you can also output non-printable characters (like binary data). The value in hexadecimal will be shown, but if the character can't be displayed in ASCII, you'll just see a dot character instead.

When viewing hex dumps I highly recommend using an ASCII table. If this is new to you, look up the codes 0d, 0a, 09 (13, 10, and 09 in decimal respectively) and see what they are. Could these be useful characters to spot in a hex dump?

What if we wanted to see the same thing but without the hex information? We could feed an extra parameter to dump_hex to show only the ASCII side, with a wider column. Let's try this:

Code:
printlog(dump_hex($vbulletin->templatecache['WHOSONLINE'], true));
Our output is now formatted differently, with a ruler along the top.
Code:
             1         2         3         4         5         6         7         8
    12345678901234567890123456789012345678901234567890123456789012345678901234567890
       
  0 $stylevar[htmldoctype]..<html dir=\"$stylevar[textdirection]\" lang=\"$stylevar[ 
 80 languagecode]\">..<head>..$headinclude..$metarefresh..<title>" . $GLOBALS['vbull 
160 etin']->options['bbtitle'] . " - $vbphrase[whos_online]</title>..</head>..<body> 
240 ..$header..$navbar....".(($pagenav) ? ("..<table cellpadding=\"0\" cellspacing=\ 
320 "0\" border=\"0\" width=\"100%\" style=\"margin-bottom:3px\">..<tr valign=\"bott 
400 om\">...<td align=\"$stylevar[right]\">$pagenav</td>..</tr>..</table>..") : ("") 
480 )."....<table class=\"tborder\" cellpadding=\"$stylevar[cellpadding]\" cellspaci 
560 ng=\"$stylevar[cellspacing]\" border=\"0\" width=\"100%\" align=\"center\" id=\" 
640 woltable\">..<tr>...<td class=\"tcat\" colspan=\"$colspan\"><div class=\"smallfo 
720 nt\">....<span style=\"float:$stylevar[right]\">.....<a href=\"$reloadurl\"><str
Note that if we were trying to locate special characters (like tab or new line), this wouldn't be very useful, but for all ASCII printable characters, it will be.

Advanced debugging techniques

The best way to debug is with a proper set of debugging tools like those found in a PHP IDE (Integrated Development Environment). This allows you to step through the code, set breakpoints and display variable values at various points inside your program. However, if you were already using this type of debugging, you wouldn't be reading nor need this tutorial. Therefore a discussion about this is out of scope for this document.

Some more small development hints

There might be times when you just want to try out various bits of PHP code. Rather than experimenting inside a plugin, it makes more sense to write and perfect your routines in a small standalone program. If you haven't already installed PHP on your home machine, do so. If you are running vBulletin on a server and you have shell access to that server, you probably have access to the PHP command line executable. Type "php" and see what happens. If so, you can write small programs in a text editor and run them on the server.

You can extend this concept further and actually make small PHP pages that hook into vbulletin. There is an excellent tutorial on this here. It's very easy and it means you can access vBulletin variables and classes from within a totally separate file. This makes certain types of testing very easy and worry-free.

Remember that testing small routines in a proper PHP editor with good debugging tools is the easiest option. You can use the advanced debugging features to step through your code. When it's ready - try it as a plugin in your vBulletin environment.

What next?

Hopefully this tutorial will help you debug your vBulletin plugins more quickly. Do you have any other useful functions or techniques that you use on a regular basis when debugging for vBulletin? Why not share them with us?
Attached Files
File Type: php devtools.php (4.0 KB, 152 views)
Reply With Quote
  #2  
Old 07-03-2007, 08:23 PM
Paul M's Avatar
Paul M Paul M is offline
 
Join Date: Sep 2004
Location: Nottingham, UK
Posts: 23,748
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

Nice Article, moved to vbulletin articles.
Reply With Quote
  #3  
Old 07-26-2007, 12:59 AM
Antivirus's Avatar
Antivirus Antivirus is offline
 
Join Date: Sep 2004
Location: Black Lagoon
Posts: 1,090
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

Very nice! Well - written
Reply With Quote
  #4  
Old 08-19-2008, 08:19 PM
TCattitude's Avatar
TCattitude TCattitude is offline
 
Join Date: Oct 2004
Location: Chile
Posts: 195
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

Never seen this before. Thanks for the tips and sharing
Reply With Quote
  #5  
Old 08-20-2008, 12:44 PM
Triky's Avatar
Triky Triky is offline
 
Join Date: Mar 2007
Location: [Italy]
Posts: 728
Благодарил(а): 0 раз(а)
Поблагодарили: 0 раз(а) в 0 сообщениях
Default

Thanks.
Reply With Quote
Reply


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT. The time now is 06:41 PM.


Powered by vBulletin® Version 3.8.12 by vBS
Copyright ©2000 - 2024, vBulletin Solutions Inc.
X vBulletin 3.8.12 by vBS Debug Information
  • Page Generation 0.05349 seconds
  • Memory Usage 2,293KB
  • Queries Executed 19 (?)
More Information
Template Usage:
  • (1)SHOWTHREAD
  • (1)ad_footer_end
  • (1)ad_footer_start
  • (1)ad_header_end
  • (1)ad_header_logo
  • (1)ad_navbar_below
  • (1)ad_showthread_beforeqr
  • (14)bbcode_code
  • (1)footer
  • (1)forumjump
  • (1)forumrules
  • (1)gobutton
  • (1)header
  • (1)headinclude
  • (1)modsystem_article
  • (1)navbar
  • (4)navbar_link
  • (120)option
  • (5)post_thanks_box
  • (2)post_thanks_box_bit
  • (5)post_thanks_button
  • (1)post_thanks_javascript
  • (1)post_thanks_navbar_search
  • (1)post_thanks_postbit
  • (5)post_thanks_postbit_info
  • (4)postbit
  • (1)postbit_attachment
  • (5)postbit_onlinestatus
  • (5)postbit_wrapper
  • (1)spacer_close
  • (1)spacer_open
  • (1)tagbit_wrapper 

Phrase Groups Available:
  • global
  • inlinemod
  • postbit
  • posting
  • reputationlevel
  • showthread
Included Files:
  • ./showthread.php
  • ./global.php
  • ./includes/init.php
  • ./includes/class_core.php
  • ./includes/config.php
  • ./includes/functions.php
  • ./includes/class_hook.php
  • ./includes/modsystem_functions.php
  • ./includes/functions_bigthree.php
  • ./includes/class_postbit.php
  • ./includes/class_bbcode.php
  • ./includes/functions_reputation.php
  • ./includes/functions_post_thanks.php 

Hooks Called:
  • init_startup
  • init_startup_session_setup_start
  • init_startup_session_setup_complete
  • cache_permissions
  • fetch_threadinfo_query
  • fetch_threadinfo
  • fetch_foruminfo
  • style_fetch
  • cache_templates
  • global_start
  • parse_templates
  • global_setup_complete
  • showthread_start
  • showthread_getinfo
  • forumjump
  • showthread_post_start
  • showthread_query_postids
  • showthread_query
  • bbcode_fetch_tags
  • bbcode_create
  • showthread_postbit_create
  • postbit_factory
  • postbit_display_start
  • post_thanks_function_post_thanks_off_start
  • post_thanks_function_post_thanks_off_end
  • post_thanks_function_fetch_thanks_start
  • fetch_musername
  • post_thanks_function_fetch_thanks_end
  • post_thanks_function_thanked_already_start
  • post_thanks_function_thanked_already_end
  • post_thanks_function_fetch_thanks_bit_start
  • post_thanks_function_show_thanks_date_start
  • post_thanks_function_show_thanks_date_end
  • post_thanks_function_fetch_thanks_bit_end
  • post_thanks_function_fetch_post_thanks_template_start
  • post_thanks_function_fetch_post_thanks_template_end
  • postbit_imicons
  • bbcode_parse_start
  • bbcode_parse_complete_precache
  • bbcode_parse_complete
  • postbit_attachment
  • postbit_display_complete
  • post_thanks_function_can_thank_this_post_start
  • tag_fetchbit_complete
  • forumrules
  • navbits
  • navbits_complete
  • showthread_complete