Six Apart News & Events

PHP Dynamic Publishing: Architecture Overview

This is the first in a series of tutorials regarding the API for developing Movable Type plugins for the new PHP dynamic rendering engine.

This tutorial applies to the current release of MT, which is version 3.11. Subsequent 3.x releases should also be compatible (and releases beyond that, but I cannot say for sure).

PHP Dynamic Publishing Architecture

The first thing we should cover is the architecture behind it all. In terms of PHP scripts, the following shows the order of invocation from request to response.

  1. HTTP request
  2. .htaccess rule / error document
  3. mtview.php
  4. MT Class (MT.php)
  5. MTViewer Class, aka Smarty (MTViewer.php)
  6. PHP
  7. HTTP response with output

Let's examine each stage of this process:

  1. Requests are sent to the web server from a client (usually a web browser or an RSS/Atom client perhaps). The web server attempts to resolve the request. If it can resolve the request to a physical file, it will and returns the appropriate file. This allows for a mix of static and dynamic publishing.

  2. If the request cannot be resolved by the web server, it will adjust the request according to the active rules (.htaccess for Apache, error documents for IIS). The default .htaccess rule set for dynamic publishing basically instructs Apache to forward any request that cannot be resolved to a physical file or directory to the "mtview.php" script. The error document approach on IIS accomplishes the same thing (although less gracefully since it "corrupts" the log file with requests for mtview.php and nothing else).

  3. If the rules point the web server to use the "mtview.php" script for the weblog, the request is passed to it for handling. When Apache does this, it sets the original request in a server variable named "REQUEST_URI". IIS simply invokes the mtview.php script sending the original URL as a query parameter.

  4. The "mtview.php" script loads the primary MT class which is responsible for the bulk of the URL resolution logic. The URL is resolved and a Smarty object is instantiated to render the page.

  5. The MTViewer/Smarty object created in step 4 is given the data it needs to build the page. Initially this is just the active weblog ID, perhaps a start and end timestamp and what archive type is being used (if any). The template is loaded for the active request and processed. If the template has not already been compiled by Smarty into native PHP code, this is done next (and the result of the template compilation is stored in the "templates_c" directory).

  6. Smarty loads the compiled template (a PHP script) and executes it.

  7. As the template is executed, the output is sent back to the client.

If any error occurs in this process, the MT "Dynamic Pages Error Template" is used to relay the error to the end user.

mtview.php

mtview.php is the primary script for invoking the PHP publishing engine. When you run the mtview.php script, it simply does this (Please note: within these examples, 'MT_DIR' is a placeholder for the actual path to the Movable Type directory. Please substitute this with the Movable Type directory on your server):

1     $mt = new MT(1, 'MT_DIR/mt.cfg'); # your blog id here
2     $mt->view();

The view method takes the default course of action, which is to determine the active request and attempt to serve it based on locating the URL in the Movable Type database.

The MT Class

Now, what all is available in the "MT Class" piece of the puzzle? Here are the primary methods it provides:

class MT

  • init_plugins(): configures for available plugins
  • context(): retrieves Smarty instance (actually a MT-flavored subclass of Smarty)
  • db(): retrieves the database instance
  • configure(): loads the MT configuration file
  • configure_paths(): sets up MT path locations
  • view(): mainline for dynamic publishing
  • resolve_url(): returns database records for a given URL
  • display(): utility function that displays a particular Smarty template
  • fetch(): utility function that returns output of a particular Smarty template
  • error_handler(): custom error handling method
  • doConditionalGet(): handles conditional GET operations

The nice thing about the MT class is that it is, in fact a class. Which means you can customize it. So if you don't like the way it does particular things, you can change it to your liking. Or, if you don't want to delve that deep, you can do certain things from the calling script (mtview.php) to customize what happens between the request and serving the output.

The MTDatabase classes

The database access layer for MT/PHP is the biggest departure from the MT/Perl architecture. It is very simple by comparison. We are utilizing the ezSQL package as the base class since it provides an abstraction layer between different database vendors (MySQL, PostgreSQL, SQLite). But instead of having separate objects for each table in the MT database, we are simply retrieving PHP arrays (hashes of data). Therefore, there is no "load" method at the table level for retrieving entries or other types of data. Instead, there is a method in the database class itself for fetching each type of data. This simplification was done in order to save precious milliseconds of CPU time, because in dynamic publishing, every millisecond counts.

There is an MTDatabaseBase class which is sort of an abstract class. The vendor-specific subclass is what is actually instantiated. Those classes are: MTDatabase_mysql, MTDatabase_postgres, MTDatabase_sqlite (the MySQL class is the only one that is functional at this time). The vendor-specific classes override or implement base class methods where there are vendor-specific differences in the SQL query syntax.

Here are some of the methods available in the database class:

class MTDatabaseBase (inherits from the ezSQL class)

  • resolve_url(): returns relevant contextual data necessary to serve a given URL.
  • load_index_template(): fetches mt_template data for a particular index template
  • load_special_template(): fetches mt_template data for a particular class of template
  • get_template_text(): returns the template source for a particular named template.
  • get_archive_list(): workhorse function for the <MTArchiveList> tag.
  • archive_link(): returns the URL for a given timestamp and archive type.
  • fetch_blog(): returns an individual blog record for the given blog ID.
  • blog_entry_count(): returns the number of entries for a given blog ID.
  • fetch_entry(): returns an individual entry record for the given entry ID.
  • fetch_entries(): returns an array of entry records that match the requested criteria.
  • entry_link(): returns the URL for a given entry ID and archive type.
  • entry_comment_count(): returns the number of comments for a given entry ID.
  • entry_ping_count(): returns the number of pings for a given entry ID.
  • fetch_category(): returns an individual category record for the given category ID.
  • fetch_categories(): returns an array of category records that match the requested criteria.
  • category_link(): returns the URL for a given category ID.
  • fetch_author(): returns an individual author record for the given author ID.
  • fetch_comments(): returns an array of comment records that match the requested criteria.
  • fetch_pings(): returns an array of ping records that match the requested criteria.

The fetch methods listed above attempt to cache as much as possible. For example, if multiple entry records are fetched with fetch_entries, it will precache the comments and pings for those entries (doing a single select statement for all entry IDs rather than a separate query for each entry). In some cases, this gathers more data than necessary, but in most cases, it saves on execution time.

The MTViewer class

The last major piece to cover is the MTViewer class. This class is a descendant of the Smarty class.

class MTViewer (inherits from the Smarty class)

  • add_global_filter(): registers a global filter, just as you would do with MT/Perl.
  • error(): triggers an error during runtime.
  • this_tag(): within plugin routines, this returns the active MT tag name.
  • stash(): much like the MT stash method. Lets you place/retrieve data on the stash.
  • localize(): saves the state of a list of elements that are in the stash.
  • restore(): restores the state of a list of previously 'localized' stash elements.
  • tag(): invokes a MT tag handler and returns the result.

In addition to the above methods, this class also customizes the way templates are processed. It declares a custom Smarty prefilter called 'mt_to_smarty' which takes care of translating MT templates into Smarty compatible syntax.

Customizing mtview.php

As I said, the main MT class is an object, so it lends itself to customization if you require it. For example, you may want to override the resolve_url method to have it try alternative search methods to find an entry based on the request. A little adjustment to the mtview.php script would be in order:

 <?php
 include("MT_DIR/php/mt.php");
 class MyMT extends MT {
     function &resolve_url($path) {
         $data =& parent::resolve_url($path);
         if (!$data) {
             # now try harder!
             $data =& $this->fuzzy_resolve_url($path);
         }
10         return $data;
11     }
12     function &fuzzy_resolve_url($path) {
13         # fuzzy logic to locate a given $path follows...
14     }
15 }
16 
17 $mt = new MyMT(1, "MT_DIR/mt.cfg")# use OUR MT class
18 $mt->view();
19 ?>

And consider this PHP snippet:

1 <?php
2     include_once("MT_DIR/php/mt.php");
3     $mt = new MT(10);   # blog id 10 is my 'linkblog'
4     $ctx =& $mt->context();
5     $ctx->caching = 2# each cache file has its own lifetime
6     $ctx->cache_lifetime = 60 * 30# cache for 30 minutes
7     $output = $mt->fetch("mt:My Linkblog");
8     echo $output;
9 ?>

Now, here we have invoked the MT dynamic publishing engine to render a specific index template ("My Linkblog" -- the "mt:" prefix causes it to be pulled from the database. Smarty also provides a "file:" prefix which is also the default resource type if you want to load templates from the file system), causing the output to be cached for up to 1800 seconds (30 minutes). This can be done from any PHP script. You could publish your entire site statically and still pull a portion of MT data this way using the PHP engine.

Within dynamic templates, you have convenient access to your data. Having a full scripting language at your disposal may alleviate the need for creating MT-specific plugins. You could just use the wealth of functions available through PHP itself or other PHP libraries/modules that already exist. The following example illustrates how you can access your MT content directly from PHP, manipulate it, then output it in whatever manner you choose:

1 <MTEntries lastn="10">
2   <?php
3     $title = $this->tag('MTEntryTitle');
4     $raw_body = $this->tag('MTEntryBody', array('convert_breaks' => '0'));
5     $raw_body = preg_replace('/[^A-Za-z0-9\s]/', '', strip_tags($raw_body));
6     $raw_words = preg_split('/\s+/', $raw_body);
7     echo $title . " (approx. word count: " . count($raw_words).")";
8   ?>
9 </MTEntries>

Using MT Tags in PHP

One shortcoming (at least for now) is that you cannot mix MT tags inside PHP code blocks. This was possible in a static publishing model, but with dynamic publishing, the MT tags are translated into PHP code. So putting a MT tag within a PHP code block will cause the template-to-PHP translation to output a PHP open tag within another PHP block, which would result in a syntax error. Therefore, the 'tag' method shown above is the recommended way to invoke tags within PHP code blocks.

And with the Smarty framework, there are even more ways to customize the rendering of your page. Another feature available through Smarty is the output filter. An output filter is something that runs after the template has been executed. If, for example, you want to apply a text filtering process over your entire page (not just individual entry text), you can load an output filter to do that.

I created a file in my PHP plugins directory named "outputfilter.smartypants.php" with this in it (You can find the PHP version of SmartyPants here.):

1 <?php
2   include_once("smartypants.php");
3   function smarty_outputfilter_smartypants($text, &$ctx) {
4       return SmartyPants($text);
5   }
6 ?>

Then, within my mtview.php script, I simply load the filter:

1   # put this above the $mt->view() step...
2   $ctx =& $mt->context();
3   $ctx->load_filter('output', 'smartypants');

Now I get educated quotes for the entire web page, rather than just the MT content. Fortunately, the SmartyPants parser gracefully ignores HTML, scripts and so forth.

Smarty templating works too

Since we're using Smarty as the underlying engine to process our templates, that means that you can actually use the full Smarty template syntax if you prefer. You can even mix Smarty template code and MT template syntax together (the default delimiters for Smarty are changed from '{' and '}' to '{{' and '}}' since the single brace delimiters cause problems if you have JavaScript within your template.) And you can invoke PHP script as well.

For instance, Smarty has a neat little function tag called "cycle". Cycle lets you cycle through a list of values. Each time cycle is used within a loop, it will output the next one in the set. Here's how you might use it:

1 <MTEntries>
2   <tr bgcolor="{{cycle values="#eeeeee,#d0d0d0"}}">
3     <td><$MTEntryTitle$></td>
4   </tr>
5 </MTEntries>

This would alternate the background color of the rows of the table for each entry output.

I encourage you to read the excellent documentation available for Smarty to discover everything that is at your disposal.

Conclusion

I hope this first tutorial has given you a good picture of what the PHP engine essentially does. And hopefully you can now better appreciate the power and flexibility it provides you when you need to publish dynamic data from Movable Type.

Comments