Anatomy of a Textpattern Plugin, part 4

A PHP developer’s guide to writing Textpattern plugins.

Recap

First up, a quick recap.

Part 1 and part 2 of the Anatomy series covered the capabilities of Textpattern plugins, and explain the procedures for compiling, installing and running a plugin.

In Part 3 we looked at Textpattern tag handlers, and created a Deluxe “Hello World” self-closing tag. I’ll include that code here for easy reference, since it’ll make a handy starting point for future tag handlers:

function zem_hello_deluxe($atts) {
   extract(lAtts(array(
      'name'  => 'Alice',
      'wraptag' => '',
      'class' => "hello",
   ),$atts));

   return doTag(htmlspecialchars($name), $wraptag, $class);
}

Testing and debugging

Before you start writing more complex plugins, it’s a good idea to be prepared for some testing and debugging.

I strongly recommend using a separate Textpattern installation and database for testing your plugins. A fatal PHP error can break your site. Even minor warnings can cause cosmetic problems (not to mention embarrasment). A copy of Textpattern installed in a subdomain or subdirectory of your main site is fine.

It’s not a good idea to make significant changes via the plugin editor in the Textpattern administration interface (in admin > plugins). These changes aren’t checked for PHP syntax errors, and a syntax error will prevent your web site from working. If you’re compiling your changes and installing via the copy-paste method, then the compiler has already checked your plugin for PHP syntax errors. If you’re using the plugin cache shortcut, you can use PHP’s command line to check for syntax errors before uploading any changes (see Part 2 for details).

A debugging checklist

Here’s a quick debugging procedure that will help you to avoid the most common errors when developing a plugin. If your plugin isn’t producing the output you expect:

1. Double-check that Production Status is set to Debug

The single most helpful thing you can do when testing or debugging a plugin is to set your Textpattern Production Status to Debugging (in admin > preferences). The available Production Status options are:

  • Testing – sets error_reporting to show errors and warnings (but not notices), and turns on display_errors. Recommended while editing page templates and forms.
  • Debugging – same as Testing, with error_reporting set to display notices as well. Recommended while developing, testing and debugging plugins.
2. Make sure your plugin is installed and active

This is a surprisingly easy mistake to make. If you’re installing your plugin via the admin > plugins tab, remember that Textpattern will disable it each time you install a new version. You’ll need to manually set Active to Yes.

If you’re using FTP or rsync, make sure you’re uploading your plugin to the correct location. If it’s incorrect, you might be running a different version of the code to what you think.

As long as Production Status is set to Debug or Testing, an inactive plugin is usually easy to spot, since it will cause “Unknown tag” errors any time you use its tags in a template. If you need a more obvious reminder, you can place the following code on the page template you’re using for testing (requires Textpattern 4.0.4+):

<txp:if_plugin name="abc_myplugin">
<txp:else />
<h1>abc_myplugin is inactive!</h1>
</txp:if_plugin>
3. Pay attention to error messages

Even a simple Undefined variable or Undefined index notice can help identify buggy code. These often reveal a simple typo in the name of a variable or array index.

Make sure your code doesn’t generate any warnings or notices under normal circumstances, otherwise genuine problems will be harder to find.

You’ll have a considerable advantage identifying errors if you’re using the plugin cache to test your code, rather than installing via the database. Plugins are loaded from the cache directory using a PHP include statement. Any errors they generate will accurately point to the correct file and line number. In Textpattern 4.0.4+, error messages in Debugging mode include a backtrace of the function calls that led up to the error, in reverse order:

tag_error <txp:zem_example/> ->  Notice: Undefined variable: bar  on line 44
/home/example/plugins/zem_example.php:44 zem_example()
textpattern/publish.php:893 zem_example()
textpattern/publish.php:863 processTags()
textpattern/publish.php:863 preg_replace_callback()
textpattern/publish.php:435 parse()
index.php:28 textpattern()

Reading from the bottom up, you can see where code execution began, and follow it through each function call to the exact line in your plugin that triggered the error. This can be particularly helpful for tracking down errors that are indirectly caused by your plugin. A SQL syntax error, for example, will trigger a warning from within the Textpattern database code in txplib_db.php. The buggy line of code will be several steps down in the backtrace, where the database query function was called.

Plugins loaded from the database don’t have a corresponding file, so the error messages they generate are more cryptic. Typically they look something like this:

tag_error <txp:zem_example/> ->  Notice: Undefined variable: foo  on line 11
textpattern/lib/txplib_misc.php(485) : eval()'d code:11 zem_example()
textpattern/publish.php:893 zem_example()
textpattern/publish.php:863 processTags()
textpattern/publish.php:863 preg_replace_callback()
textpattern/publish.php:435 parse()
index.php:28 textpattern()

Line 485 of txplib_misc.php (or thereabouts) is the location of the function that loads and runs plugins from the database. It’s not the cause of the error; the buggy code is on line 11 of the plugin, in the zem_example() function. However, that’s not line 11 of the plugin source file, it’s line 11 of the code part, as displayed on the admin > plugins > edit page, or counting down from the BEGIN PLUGIN CODE line in your plugin source file.

4. Print debug messages

The oldest and simplest debugging technique is no less effective. Use Textpattern’s dmp() function to display messages and data at key points in your code. This is particularly helpful when your plugin includes if statements:

function abc_mytag($atts) {
   dmp(__FUNCTION__);
   dmp($atts);

   if ($something) {
      dmp('$something is true');
      ...
   }
   else {
      dmp('$something is false');
      ...
   }
   dmp($out);
   return $out;
}

The dmp output will immediately confirm several important facts:

  • Whether or not the abc_mytag function is called at all
  • The contents of the $atts array
  • Which of the two code branches is taken
  • The tag output

It’s not as convenient as the step-break debugging offered by modern IDE’s, but just as accurate.

In this series


The improved zem_strcase function would look like this:

function zem_strcase($atts, $thing=’‘) { extract(lAtts(array( ‘type’ => ‘lower’, ‘wraptag’ => ‘’, ‘class’ => ‘’ ),$atts)); $out = parse($thing); if ($type == ‘upper’) $out = strtoupper($out); elseif ($type == ‘title’) $out = ucwords(strtolower($out)); elseif ($type == ‘lower’) $out = strtolower($out); return doTag(htmlspecialchars($out), $wraptag, $class); }

If we would have used ( through cutting and pasting) the code example in FIG. 16, (which I did),
then our test function would produce an error, because the comparison operators are missing from the first two conditional clauses of the control structure, causing to make the plugin all input uppercase, since PHP believes that ‘upper’ and ‘type’ are constants.

Once again, these series of articles are excellent, and reading it more than twice is only beneficial.

One of the advantages of TXP’s extensible plugin architecture is to be able to tie in directly into it’s function libraries and variables, which will then lead to a more condensed , compact and readable code unlike in standalone PHP files.

Disadvantage is that I find it difficult to figure out how to do that correctly. (eg.: global variables)

Now I’m looking forward to the fifth chapter.

regards, marios

marios buttner    Jan 24, 09:29 am    #

Commenting is closed for this article.