The Plugin Walkthrough: Creating Your Own Tables

Movable Type provides the versatile mt_plugindata table, used by plugins to store all types of data. However as your plugin begins to grow in size, continuing to store data in mt_plugindata could start to become unwieldily and so today we'll explore how to create and work with custom tables. Custom tables have several advantages over mt_plugindata including:

  • Much quicker data storage/retrieval - every time data is stored or retrieved from mt_plugindata it needs to be serialized or unserialized.

  • Being able to index columns - for example, if you wished to list all the objects you store for blog_id 1, with mt_plugindata you would need to load all your objects and then skip the ones whose blog_id isn't 1. With a custom table, the blog_id column could be indexed and you would be able to load just those records with a blog_id of 1.

  • With custom tables, joining separate objects together is a piece of cake. Movable Type already does this with entries and authors (separate objects that are joined on mt_entry.entry_author_id and mt_author.author_id) or entries, categories and placements (mt_entry.entry_id, mt_category.category_id, mt_placement.entry_id and mt_placement.category_id).

  • New with Movable Type 3.31, creating a custom table (and hence a custom class) allows you to make your objects taggable using MT::Taggable (I'll cover this in another article, promise!).

First off, to create a table in the database, you will need to create a subclass of MT::Object. The documentation contains a good example of how to subclass MT::Object but misses a few key ingredients which makes our job as plugin developers infinitely easier.

column_defs

If you remember, Movable Type 3.2 shipped with a new upgrader. With Movable Type 3.31, the upgrader was enhanced such that plugins could hook into it. The first step, however, is to use the column_defs key instead of (or in addition to) the columns key. With column_defs, rather than simply listing your columns, you will need to define their properties including column type, length, attributes and null status. The quickest way to find out what you can use with column_defs is to look at existing MT::Object subclasses (the best are MT::Blog and MT::Entry as they have the largest number of columns), however here's a quick example:

column_defs => {
    'id' => 'integer not null auto_increment',
    'foo_id' => 'integer',
    'name' => 'string(25)',
    'status' => 'smallint',
},

defaults

Another new key introduced was defaults. As the name suggests, it allows you to specify the default value for columns and again the easiest way to understand how it works is to look at existing MT::Object subclasses. Here's a quick example highlighting defaults:

column_defs => {
    'id' => 'integer not null auto_increment',
    'foo_id' => 'integer',
    'name' => 'string(25)',
    'status' => 'smallint',
},
default => {
    'status' => '3'
}

Automating the Installation

With Movable Type 3.3 plugins can now have MT automatically create and maintain their tables. To do so, when registering the plugin, simply specify two new arguments, schema_version and object_classes. The first is similar to version but is specific to your database scheme (i.e. you could add new features to your plugin but retain the same database stucture), the second is just a list of your MT::Object classes:

my $plugin = new MT::Plugin({
    name => "Example Plugin",
    version => 1.5,
    author_name => "Conan the Barbarian",
    schema_version => 1.2
    object_classes => [ 'MT::Foo', 'MT::Bar' ]
});
MT->add_plugin($plugin);

When a plugin with a new schema_version is uploaded to MT_DIR/plugins/, Movable Type will trigger the upgrade procedure which will install (or update) your plugin's tables.

Upgrade Functions

New with Movable Type 3.3, upgrade functions. These are functions that are fired when a plugin is installed or upgraded and is triggered by a change in schema_version. You can register these functions using the aptly named upgrade_functions key whilst registering your plugin:

my $plugin = new MT::Plugin({
    name => "Example Plugin",
    version => 1.5,
    author_name => "Conan the Barbarian",
    schema_version => 1.2,
    object_classes => [ 'MT::Foo', 'MT::Bar' ],
    upgrade_functions => {
        'my_plugin_fix_field_a' => {
            version_limit => 1.1,   # runs for schema_version < 1.1
            code => \&plugin_field_a_fixer
        }
    }
});
MT->add_plugin($plugin);

sub plugin_field_a_fixer {
    # do stuff
}

However, at the time of writing, a bug in Movable Type 3.3 prevents upgrade functions from running if your plugin hasn't previously defined a schema_version. To fix this, add the following code to your plugin's .pl file (thanks to Tim Appnel and Brad Choate)

sub init {
    my $plugin = shift;
    $plugin->SUPER::init(@_);
    MT->config->PluginSchemaVersion({})
    unless MT->config->PluginSchemaVersion;
}

If you're like me, however, your code may not be perfect on the first go and you'll want to run the upgrade functions until they do work perfectly. The easiest way I've found to re-trigger the upgrade functions is to trick MT into thinking that the plugin's tables haven't yet been installed. This involves editing the database and removing the PluginSchemaVersion line for your plugin:

  1. Open the mt_config table. You should only have one record in the table
  2. Edit the config_data column for this record - you'll notice that there are various configuration items here, one per line
  3. Remove the PluginSchemaVersion line that relates to your plugin, for example with MT Blogroll, I'd have to remove the line that reads PluginSchemaVersion Blogroll/Blogroll.pl=2.2
  4. Save config_data and go back to Movable Type, you should be prompted to reinstall your plugin's tables (and hence the upgrade functions will fire).

This takes care of installing and maintaining your tables. To learn more about how to use your new tables and classes, read the new online developer documentation for MT::Object.

Leave a comment

Preview