<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="http://feeds.feedburner.com/~d/styles/rss2full.xsl" type="text/xsl" media="screen"?><?xml-stylesheet href="http://feeds.feedburner.com/~d/styles/itemcontent.css" type="text/css" media="screen"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0" xml:base="http://bendiken.net">
<channel>
 <title>Arto Bendiken on Drupal</title>
 <link>http://bendiken.net/taxonomy/term/1/0</link>
 <description />
 <language>en</language>
<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/bendiken/drupal" type="application/rss+xml" /><item>
 <title>The Universal Timeline Aggregator</title>
 <link>http://feeds.feedburner.com/~r/bendiken/drupal/~3/68550559/the-universal-timeline-aggregator</link>
 <description>	&lt;p&gt;&lt;img alt="Timeline" src="http://bendiken.net/images/2006/20061229-timeline-sundial.png" width="150" height="150" border="0" align="left" style="margin-right: 15px;" /&gt;&lt;/p&gt;

	&lt;p&gt;For those who haven&amp;#8217;t yet come across it somewhere on the web, may I recommend checking out the &lt;a href="http://simile.mit.edu/timeline/"&gt;Timeline&lt;/a&gt; widget developed by &lt;a href="http://people.csail.mit.edu/people/dfhuynh/"&gt;David F. Huynh&lt;/a&gt; of the &lt;a href="http://simile.mit.edu/"&gt;&lt;span class="caps"&gt;MIT&lt;/span&gt; Simile&lt;/a&gt; project. It&amp;#8217;s a snazzy DHTML/JavaScript tool for visualizing chronological events on a scrollable, graphical timeline &amp;#8212; sort of a Google Maps for temporal information.&lt;/p&gt;

	&lt;p&gt;I&amp;#8217;ve been working quite a bit with the Simile widget lately, co-developing (with &lt;a href="http://drdonohue.com/"&gt;David Donohue&lt;/a&gt;) a &lt;a href="http://drupal.org/project/timeline"&gt;module&lt;/a&gt; that integrates the widget into &lt;a href="http://drupal.org/"&gt;Drupal&lt;/a&gt;, allowing Drupal sites to display any &lt;a href="http://drupal.org/project/cck"&gt;CCK&lt;/a&gt; / &lt;a href="http://drupal.org/project/views"&gt;Views&lt;/a&gt; content as graphical timelines.&lt;/p&gt;

	&lt;p&gt;Today, inspired by &lt;a href="http://apassant.net/blog/"&gt;Alexandre Passant&amp;#8217;s&lt;/a&gt; &lt;a href="http://apassant.net/home/2006/07/rss2timeline/"&gt;RSS2Timeline&lt;/a&gt; implementation, I sat down to code up a generic web service that can take any Atom or &lt;span class="caps"&gt;RSS&lt;/span&gt; feed and convert it into a &lt;a href="http://en.wikipedia.org/wiki/JSON"&gt;JSON-based&lt;/a&gt; event source for the Timeline widget. My goal was to make it absolutely trivial to embed live Atom/&lt;span class="caps"&gt;RSS&lt;/span&gt; timelines into blogs and whatnot, so that anyone with basic &lt;span class="caps"&gt;HTML&lt;/span&gt; skills could use timelines without having to go through the relatively complex technical setup the widget requires.&lt;/p&gt;

	&lt;p&gt;I hereby present the &lt;strong&gt; &lt;a href="http://timeline.to/"&gt;Universal Timeline Aggregator&lt;/a&gt; &lt;/strong&gt;, available at &lt;strong&gt; &lt;a href="http://timeline.to/"&gt;http://timeline.to/&lt;/a&gt; &lt;/strong&gt; (I&amp;#8217;ve been snapping up Tonga&amp;#8217;s &lt;a href="http://en.wikipedia.org/wiki/.to"&gt;dot-to domains&lt;/a&gt; since getting the ultimate &lt;a href="http://en.wikipedia.org/wiki/Vanity_domain"&gt;vanity domain&lt;/a&gt;, &lt;a href="http://ar.to/"&gt;ar.to&lt;/a&gt;, as a Christmas present; the &amp;#8220;to&amp;#8221; preposition works rather nicely for the present purpose, too.)&lt;/p&gt;

	&lt;p&gt;&lt;!--break--&gt;&lt;/p&gt;

	&lt;p&gt;Here&amp;#8217;s a screen capture (and live example, if you click on it) of the sort of timeline display you can create in a minute or two using the &lt;strong&gt; &lt;a href="http://timeline.to/"&gt;timeline.to&lt;/a&gt; &lt;/strong&gt; service:&lt;/p&gt;

&lt;div class="screenshot"&gt;
&lt;a href="http://timeline.to/?url=http%3A%2F%2Fdev.rubyonrails.org%2Ftimeline%3Fchangeset%3Don%26max%3D50%26format%3Drss" target="_blank"&gt;&lt;img alt="Ruby on Rails Trac" src="http://bendiken.net/images/2006/20061229-timeline-trac-rails.png" width="500" height="400" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;

&lt;span&gt;Timeline view of &amp;#8220;recent Ruby on Rails development&amp;#8221;:http://dev.rubyonrails.org/timeline.&lt;/span&gt;
&lt;/div&gt;

	&lt;p&gt;Embedding a live, interactive Atom or &lt;span class="caps"&gt;RSS&lt;/span&gt; timeline into any site is now as easy as copying and pasting the following &lt;span class="caps"&gt;HTML&lt;/span&gt; snippet, with the appropriate modifications:&lt;/p&gt;

&lt;pre&gt;&lt;code class="html"&gt;&amp;lt;iframe src="http://timeline.to/http://www.mysite.com/rss.xml"
  width="500" height="400"
  scrolling="no" frameborder="1"
  marginwidth="0" marginheight="0"&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

	&lt;p&gt;Just replace &lt;tt&gt;http://www.mysite.com/rss.xml&lt;/tt&gt; with a real &lt;span class="caps"&gt;URL&lt;/span&gt; address to an Atom or &lt;span class="caps"&gt;RSS&lt;/span&gt; feed, and modify the width and height as you like. &lt;a href="http://diveintomark.org/archives/2002/08/15/ultraliberal_rss_locator"&gt;Autodiscovery&lt;/a&gt; of feeds is supported to a reasonable extent, so in most cases you won&amp;#8217;t even need the exact &lt;span class="caps"&gt;URL&lt;/span&gt; to the feed; the website&amp;#8217;s &lt;span class="caps"&gt;URL&lt;/span&gt; address itself will do.&lt;/p&gt;

	&lt;p&gt;Here&amp;#8217;s another example as a screen capture, this time showing the popup box that opens when a timeline event is clicked:&lt;/p&gt;

&lt;div class="screenshot"&gt;
&lt;a href="http://timeline.to/?url=http%3A%2F%2Fwww.scheme.dk%2Fplanet%2F" target="_blank"&gt;&lt;img alt="Planet Scheme" src="http://bendiken.net/images/2006/20061229-timeline-planet-scheme.png" width="500" height="400" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;

&lt;span&gt;Timeline view of &lt;a href="http://www.scheme.dk/planet/"&gt;Planet Scheme&lt;/a&gt;, showing a preview of a blog entry.&lt;/span&gt;
&lt;/div&gt;

	&lt;p&gt;Starting out on the &lt;strong&gt; &lt;a href="http://timeline.to/"&gt;timeline.to&lt;/a&gt; &lt;/strong&gt; implementation today, I had to actually pause a moment to contemplate which technology to use: Python, Ruby or &lt;span class="caps"&gt;PHP&lt;/span&gt; &amp;#8212; it wasn&amp;#8217;t quite as clear-cut a decision as usual.&lt;/p&gt;

	&lt;p&gt;Case in point, while I haven&amp;#8217;t done much Python coding recently (since defecting to the Ruby camp), the language does have some excellent libraries and frameworks going for it &amp;#8212; including arguably the best Atom/&lt;span class="caps"&gt;RSS&lt;/span&gt; parser in existence, the &lt;a href="http://feedparser.org/"&gt;Universal Feed Parser&lt;/a&gt; written by &lt;a href="http://diveintomark.org/"&gt;Mark Pilgrim&lt;/a&gt;. Considering the &lt;a href="http://diveintomark.org/archives/2002/08/20/how_liberal_is_too_liberal"&gt;staggering number&lt;/a&gt; &lt;a href="http://diveintomark.org/archives/2003/01/22/parse_at_all_costs"&gt;of malformed and invalid feeds&lt;/a&gt; out there, a good parser is essential (I won&amp;#8217;t get further, right this moment, into the delicious irony of having to parse &lt;span class="caps"&gt;XML&lt;/span&gt; formats using regular expressions).&lt;/p&gt;

	&lt;p&gt;On the other hand, Ruby also has (at least) two relatively comprehensive and decent libraries for feed parsing, &lt;a href="http://rubyforge.org/projects/feedtools/"&gt;FeedTools&lt;/a&gt; and &lt;a href="http://rubyforge.org/projects/syndication/"&gt;Syndication&lt;/a&gt;. Unfortunately, in my experience neither library is quite up there with the Universal Feed Parser yet, and neither seems &lt;a href="http://rubyforge.org/tracker/index.php?func=detail&amp;#38;aid=4843&amp;#38;group_id=775&amp;#38;atid=3061"&gt;particularly active&lt;/a&gt; recently.&lt;/p&gt;

	&lt;p&gt;In the end, underdog &lt;span class="caps"&gt;PHP&lt;/span&gt; won out on this project on purely practical points: since my &lt;a href="http://drupal.org/project/timeline"&gt;Timeline module&lt;/a&gt; for &lt;a href="http://drupal.org/"&gt;Drupal&lt;/a&gt; is written in &lt;span class="caps"&gt;PHP&lt;/span&gt;, it makes sense to try and reuse code both ways between the &lt;strong&gt; &lt;a href="http://timeline.to/"&gt;timeline.to&lt;/a&gt; &lt;/strong&gt; service and Drupal.&lt;/p&gt;

	&lt;p&gt;Investigating the current best way to parse both Atom and &lt;span class="caps"&gt;RSS&lt;/span&gt; feeds with &lt;span class="caps"&gt;PHP&lt;/span&gt;, I learned of a new feed parser library for &lt;span class="caps"&gt;PHP&lt;/span&gt; called &lt;a href="http://simplepie.org/"&gt;SimplePie&lt;/a&gt;, which has been gaining a lot of momentum lately (indeed, it seems to be on track for eventually surpassing &lt;a href="http://magpierss.sourceforge.net/"&gt;MagpieRSS&lt;/a&gt; as the de-facto &lt;span class="caps"&gt;RSS&lt;/span&gt; parser for &lt;span class="caps"&gt;PHP&lt;/span&gt;). The SimplePie developers are apparently in the process of porting the 3000+ unit tests from Pilgrim&amp;#8217;s parser, which certainly seems a promising prospect for creating a truly robust parser.&lt;/p&gt;

	&lt;p&gt;SimplePie is also bundled with the &lt;a href="http://drupal.org/project/feedparser"&gt;Feedparser&lt;/a&gt; Drupal module, so again, it all just makes sense. The library turned out to be quite painless to work with, and has, so far, been able to parse all the feeds I&amp;#8217;ve thrown at it. (I did have to disable SimplePie&amp;#8217;s ad-removal feature, as that was eating up the entry descriptions on some Atom feeds.)&lt;/p&gt;

	&lt;p&gt;&lt;a href="http://bendiken.net/contact"&gt;Feedback&lt;/a&gt; on the &lt;strong&gt; &lt;a href="http://timeline.to/"&gt;timeline.to&lt;/a&gt; &lt;/strong&gt; service is welcome. If there&amp;#8217;s sufficient interest, I will consider adding further functionality such as &lt;a href="http://en.wikipedia.org/wiki/ICalendar"&gt;iCalendar&lt;/a&gt; support, and perhaps &lt;a href="http://en.wikipedia.org/wiki/Mashup_%28web_application_hybrid%29"&gt;mashup&lt;/a&gt; features allowing multiple feeds and data sources to be combined into a single timeline display.&lt;/p&gt;</description>
 <comments>http://bendiken.net/2006/12/29/the-universal-timeline-aggregator#comment</comments>
 <category domain="http://bendiken.net/tags/atom">atom</category>
 <category domain="http://bendiken.net/tags/drupal">Drupal</category>
 <category domain="http://bendiken.net/tags/json">JSON</category>
 <category domain="http://bendiken.net/tags/mashups">mashups</category>
 <category domain="http://bendiken.net/tags/projects">projects</category>
 <category domain="http://bendiken.net/tags/rss">RSS</category>
 <category domain="http://bendiken.net/tags/services">services</category>
 <category domain="http://bendiken.net/tags/simplepie">SimplePie</category>
 <category domain="http://bendiken.net/tags/timeline">timeline</category>
 <pubDate>Fri, 29 Dec 2006 09:37:02 -0800</pubDate>
 <dc:creator>Arto</dc:creator>
 <guid isPermaLink="false">49 at http://bendiken.net</guid>
<feedburner:origLink>http://bendiken.net/2006/12/29/the-universal-timeline-aggregator</feedburner:origLink></item>
<item>
 <title>Easier Drupal Debugging with Trace</title>
 <link>http://feeds.feedburner.com/~r/bendiken/drupal/~3/56954571/easier-drupal-debugging-with-trace</link>
 <description>	&lt;p&gt;&lt;img alt="Druplicon" src="http://bendiken.net/images/misc/druplicon.crystal.small.png" width="147" height="163" border="0" align="right" /&gt;&lt;/p&gt;

	&lt;p&gt;&lt;strong&gt; &lt;a href="http://drupal.org/project/trace"&gt;Trace&lt;/a&gt; &lt;/strong&gt; is a project that started, like these things tend to, as an innocent, experimental &lt;a href="http://drupal.org/"&gt;Drupal&lt;/a&gt; module I hacked together to implement all of the &lt;a href="http://api.drupal.org/api/4.7/group/hooks"&gt;core hooks&lt;/a&gt; exported by Drupal.&lt;/p&gt;

	&lt;p&gt;I was at the time working on &lt;a href="http://drupal.org/project/boost"&gt;Boost&lt;/a&gt; and wanted to gain a deeper understanding of the conditions and exact sequence in which the Drupal module hooks were invoked, so I took the &lt;span class="caps"&gt;API&lt;/span&gt; listing and wrote out the skeleton for each hook, sprinkling logging statements to capture and output the arguments passed to the hooks by Drupal.&lt;/p&gt;

	&lt;p&gt;&lt;!--break--&gt;&lt;/p&gt;

	&lt;p&gt;The initial output looked like the following; the comma-separated list after each hook name is the list of modules which implement that particular hook in my development installation:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[HOOK] hook_init: boost
[HOOK] hook_menu: filter, node, system, user, watchdog, boost, ...
[HOOK] hook_node_grants: 
[HOOK] hook_db_rewrite_sql: node
[HOOK] hook_node_info: page, story
[HOOK] hook_nodeapi: boost, comment, menu, path, taxonomy
[HOOK] hook_db_rewrite_sql: node
[HOOK] hook_nodeapi: boost, comment, menu, path, taxonomy
[HOOK] hook_link: node, comment, taxonomy
[HOOK] hook_nodeapi: boost, comment, menu, path, taxonomy
[HOOK] hook_db_rewrite_sql: node
[HOOK] hook_nodeapi: boost, comment, menu, path, taxonomy 
[HOOK] hook_link: node, comment, taxonomy  
[HOOK] hook_nodeapi: boost, comment, menu, path, taxonomy
[HOOK] hook_db_rewrite_sql: node
[HOOK] hook_nodeapi: boost, comment, menu, path, taxonomy
[HOOK] hook_link: node, comment, taxonomy
[HOOK] hook_footer: 
[HOOK] hook_help: filter, node, system, user, watchdog, boost, ...
[HOOK] hook_exit: boost
&lt;/code&gt;&lt;/pre&gt;

	&lt;h3&gt;Tracing Drupal Hook Invocations&lt;/h3&gt;

	&lt;p&gt;In the course of things it dawned on me that there was, of course, a much better way to do all this &amp;#8212; namely by dynamically defining the hook functions on the fly, and injecting into each a call to &lt;a href="http://php.net/debug_backtrace" target="_blank"&gt;&lt;tt&gt;debug_backtrace()&lt;/tt&gt;&lt;/a&gt; to obtain not only the arguments passed to the function (which could be gotten with &lt;tt&gt;func_get_args()&lt;/tt&gt;, after all) but also the full &lt;a href="http://en.wikipedia.org/wiki/Stack_trace"&gt;stack trace&lt;/a&gt; (call history) that led to the hook invocation.&lt;/p&gt;

	&lt;p&gt;Saddened as I was to obliterate the couple of hundred lines of code I had at that point, I took the plunge and was left with three dozen lines of code that did the same thing, just better &amp;#8212; the joys of exploratory programming in a (somewhat) dynamic language.&lt;/p&gt;

	&lt;p&gt;As a secondary effect, this also gave me a facile way to implement a kill switch that rather thoroughly disabled the module when necessary:&lt;/p&gt;

&lt;div class="screenshot"&gt;&lt;img alt="" src="http://bendiken.net/images/2006/20061203-trace-status.png" width="375" height="148" border="0" /&gt;&lt;br /&gt;
&lt;span&gt;The Trace master kill switch.&lt;/span&gt;&lt;/div&gt;

	&lt;h3&gt;Stack Traces for Drupal Hooks&lt;/h3&gt;

	&lt;p&gt;Next, I wrote helper functions to output the stack traces in a sensible, programmer-friendly format (in fact, using &lt;span class="caps"&gt;PHP&lt;/span&gt; syntax), so that the output looked like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[HOOK] hook_menu: filter, node, system, user, watchdog, boost, ...
       trace_menu(FALSE)
       call_user_func_array('trace_menu', array(FALSE)) @ `includes/module.inc':203
       module_invoke_all('menu', FALSE) @ `includes/menu.inc':1196
       _menu_append_contextual_items() @ `includes/menu.inc':220
       menu_get_menu() @ `includes/menu.inc':391
       menu_execute_active_handler() @ `index.php':15
&lt;/code&gt;&lt;/pre&gt;
Since the list of hooks to tap into had become metadata, it was natural enough to externalize all that into an INI file, while at the same time adding support for non-core hooks. This called for a user interface for controlling which hooks to activate and trace:
&lt;div class="screenshot"&gt;&lt;img alt="" src="http://bendiken.net/images/2006/20061203-trace-hooks.png" width="375" height="288" border="0" /&gt;&lt;br /&gt;
&lt;span&gt;Trace settings for hook invocations.&lt;/span&gt;&lt;/div&gt;

	&lt;h3&gt;Error Logging with Stack Traces&lt;/h3&gt;

	&lt;p&gt;When coding the stack tracing, another light bulb went on about how this could be valuable for debugging &lt;span class="caps"&gt;PHP&lt;/span&gt; errors &amp;#8212; not just in a development environment, where other debugging solutions might be available as well, but even on production servers. If one could get useful stack traces from a production box, that would aid tremendously in tracking down elusive &lt;span class="caps"&gt;PHP&lt;/span&gt; errors from environments which could be running rather disparate PHP/Apache/MySQL stacks when compared to the development environment.&lt;/p&gt;

	&lt;p&gt;This turned out to be trivial to implement &amp;#8212; simply a matter of overriding Drupal&amp;#8217;s error handler to add stack frame collection and tracing, and chaining back to the original error handler afterwards. No harm done, except for more milliseconds lost on each error in the name of higher programmer productivity &amp;#8211; good motivation to minimize the number of errors ;-)&lt;/p&gt;

&lt;div class="screenshot"&gt;&lt;img alt="" src="http://bendiken.net/images/2006/20061203-trace-errors.png" width="375" height="157" border="0" /&gt;&lt;br /&gt;
&lt;span&gt;Trace settings for &lt;span class="caps"&gt;PHP&lt;/span&gt; error tracing.&lt;/span&gt;&lt;/div&gt;

	&lt;p&gt;To reduce the time spent hunting for the causes of errors, I also added an option to output the actual source code line that triggered the error, just preceding the full stack trace up to that point. The following example illustrates one line in the Drupal 4.7 code base that looks like it could use a call to &lt;a href="http://php.net/isset" target="_blank"&gt;&lt;tt&gt;isset()&lt;/tt&gt;&lt;/a&gt; to prevent triggering a number of spurious, if ultimately harmless, &lt;span class="caps"&gt;PHP&lt;/span&gt; notices:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[NOTICE] E_NOTICE: Undefined index:  sort @ `modules/comment.module':1677
         if ($_GET[$setting]) {
         _comment_get_display_setting('sort') @ `modules/comment.module':707
         comment_render((stdClass)array('nid' =&gt; '2', 'vid' =&gt; '2', 'type' =&gt; 'story', ...))
         node_show((stdClass)array('nid' =&gt; '2', 'vid' =&gt; '2', 'type' =&gt; 'story', ...))
         node_page()
         call_user_func_array('node_page', array()) @ `includes/menu.inc':418
         menu_execute_active_handler() @ `index.php':15
&lt;/code&gt;&lt;/pre&gt;

	&lt;h3&gt;Extensible Trace Output Drivers&lt;/h3&gt;

	&lt;p&gt;The module sort of snowballed from there. Upon consideration of how to best perform trace output logging in production environments, I came to the conclusion that it would be a good idea to add an abstraction layer between the actual tracing-related function calls and the subsequent physical output, which had until then been exclusively written to a dedicated log file.&lt;/p&gt;

	&lt;p&gt;This led to the concept of output targets or &amp;#8220;drivers&amp;#8221;, with &lt;tt&gt;File&lt;/tt&gt; being the default target:&lt;/p&gt;

&lt;div class="screenshot"&gt;&lt;img alt="" src="http://bendiken.net/images/2006/20061203-trace-driver.png" width="375" height="187" border="0" /&gt;&lt;br /&gt;
&lt;span&gt;Trace output driver settings.&lt;/span&gt;&lt;/div&gt;

	&lt;p&gt;In addition to &lt;tt&gt;File&lt;/tt&gt;, currently implemented is also a &lt;tt&gt;Syslog&lt;/tt&gt; driver. The idea of syslog support is that you can have the Trace module activated (with minimal tracing options, e.g. only trace errors), on your production server and tie the output to your existing system log analysis and reporting facilities.&lt;/p&gt;

	&lt;p&gt;Two other drivers I&amp;#8217;ve been considering implementing are a &lt;span class="caps"&gt;TCP&lt;/span&gt; socket driver and an e-mail backend. The former could, by its ephemeral nature, be useful in speeding up &amp;#8220;deep tracing&amp;#8221; (with the &lt;tt&gt;File&lt;/tt&gt; backend, having all trace options enabled kills performance both due to the volume of data being written to disk as well as the constant &lt;tt&gt;fsync()&lt;/tt&gt;&amp;#8216;ing), while the latter would, again, be useful for production environments: imagine your mission-critical Drupal application sending you a bug report e-mail when a &lt;span class="caps"&gt;PHP&lt;/span&gt; error occurs; said e-mail containing a wealth of information on the page request, server state, and a full stack trace of the code that caused the error.&lt;/p&gt;

	&lt;p&gt;The way that the output driver concept is currently implemented is quite flexible, by the way &amp;#8212; the module actually defines a new hook, &lt;tt&gt;hook_trace()&lt;/tt&gt;, which is invoked for each tracing event. (This hook itself can, obviously, not be traced.) Any modules that implement this hook &amp;#8212; &lt;a href="http://drupal.org/project/devel"&gt;Devel&lt;/a&gt; would be a good future candidate &amp;#8212; have access to all trace output as it happens; the existing Trace output drivers are concrete implementations of the hook that just happen to be provided by default.&lt;/p&gt;

	&lt;h3&gt;Tracing Database Queries&lt;/h3&gt;

	&lt;p&gt;I also tapped into Drupal&amp;#8217;s undocumented &lt;span class="caps"&gt;SQL&lt;/span&gt; query tracing facilities (which ostensibly exist to support the &lt;a href="http://drupal.org/project/devel"&gt;Devel&lt;/a&gt; module) and added output for each query executed during the page request:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[QUERY] drupal_lookup_path: SELECT COUNT(pid) FROM url_alias
[QUERY] drupal_lookup_path: SELECT src FROM url_alias WHERE dst = ...
[QUERY] module_list: SELECT name, filename, throttle, bootstrap ...
[QUERY] cache_clear_all: DELETE FROM cache WHERE cid = 'variables'
[QUERY] cache_get: SELECT data, created, headers, expire FROM ...
[QUERY] node_access_view_all_nodes: SELECT COUNT(*) FROM ...
[QUERY] pager_query: SELECT COUNT(*) FROM node n WHERE n.promote  ...
[QUERY] pager_query: SELECT n.nid, n.sticky, n.created FROM node ...
[QUERY] drupal_lookup_path: SELECT dst FROM url_alias WHERE src ...
...
&lt;/code&gt;&lt;/pre&gt;
In the interests of brevity, I haven't included any timing information in any of the above examples of the trace output. In actuality, however, each trace log message includes a microsecond-precision time delta measured against the start time of the page request. (The actual accuracy, though, perhaps being somewhat more coarse-grained due to time lost to tracing.)
The timing information for SQL queries was a bit of a pain to implement using the currently available query debugging facility in Drupal 4.7. As a result, the timestamp for each SQL query is, for the moment, nothing but a best-guess value. Not that this slight inaccuracy will matter for most uses, but once I've wrapped up version 1.0 of Trace I plan to submit a Drupal core patch that will not only make the tracing simpler (from the core point of view) but allow for significantly increased functionality, too.

	&lt;h3&gt;Additional Debug Output&lt;/h3&gt;

	&lt;p&gt;Just to supplement the information available in the trace output, an option was added for dumping the contents of the &lt;span class="caps"&gt;PHP&lt;/span&gt; superglobals at the start of the page request, and for including further debug information (such as the &lt;span class="caps"&gt;HTTP&lt;/span&gt; headers that were set during processing of the page request) at the of the response:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[REQUEST] 2006-12-03 22:46:12.306834 GET / HTTP/1.1
[DEBUG  ] $_ENV = array('PATH' =&gt; '/usr/bin:/bin', 'PWD' =&gt; '/', ...)
[DEBUG  ] $_SERVER = array('HTTP_ACCEPT' =&gt; '*/*', ...)
[DEBUG  ] $_COOKIE = array('DRUPAL_UID' =&gt; '1', 'PHPSESSID' =&gt; ...)
[DEBUG  ] $_REQUEST = array('DRUPAL_UID' =&gt; '1', 'PHPSESSID' =&gt; ...)
[DEBUG  ] $_GET = array('q' =&gt; 'node')
[DEBUG  ] $_SESSION = array('node_overview_filter' =&gt; array())
...
(other output omitted)
...
[DEBUG  ] drupal_set_message() = NULL
[DEBUG  ] drupal_set_header() = array('Content-Type: text/html; charset=utf-8')
[STATS  ] Total SQL queries: 59
[RESPNSE] 2006-12-03 22:46:12.453383 200 OK
&lt;/code&gt;&lt;/pre&gt;

	&lt;h3&gt;Improving the Signal-to-Noise Ratio&lt;/h3&gt;

	&lt;p&gt;Now that things were rolling, I was getting plenty of trace output &amp;#8211; in fact, &lt;strong&gt;way&lt;/strong&gt; too much. A single page request for my development site&amp;#8217;s front page would easily generate 30 megabytes of output. Obviously, it was time to put some effort into cutting down on unnecessary output.&lt;/p&gt;

	&lt;p&gt;First, I added a setting for filtering which &lt;span class="caps"&gt;SQL&lt;/span&gt; queries would be traced and which summarily ignored:&lt;/p&gt;

&lt;div class="screenshot"&gt;&lt;img alt="" src="http://bendiken.net/images/2006/20061203-trace-queries.png" width="375" height="165" border="0" /&gt;&lt;br /&gt;
&lt;span&gt;Trace settings for database queries.&lt;/span&gt;&lt;/div&gt;

	&lt;p&gt;More importantly, however, it was getting to be a pain to browse the administrative interface with tracing enabled; to solve this, I implemented a filter to control trace activation based on page paths and wildcards, in a manner very similar to how block visibility is determined in Drupal:&lt;/p&gt;

&lt;div class="screenshot"&gt;&lt;img alt="" src="http://bendiken.net/images/2006/20061203-trace-filter.png" width="375" height="211" border="0" /&gt;&lt;br /&gt;
&lt;span&gt;Trace page filter settings.&lt;/span&gt;&lt;/div&gt;

	&lt;p&gt;The module&amp;#8217;s fine-grained advanced settings are also instrumental in determining how much output will actually be generated:&lt;/p&gt;

&lt;div class="screenshot"&gt;&lt;img alt="" src="http://bendiken.net/images/2006/20061203-trace-advanced.png" width="375" height="418" border="0" /&gt;&lt;br /&gt;
&lt;span&gt;Additional trace settings.&lt;/span&gt;&lt;/div&gt;

	&lt;h3&gt;Current Status and Future Directions&lt;/h3&gt;

	&lt;p&gt;At present, I have mostly finished the features that I want for a release one-oh of the module &amp;#8212; with the notable lack of watchdog message tracing, which has not yet been implemented. The module should already be stable enough to serve as an everyday asset for intrepid Drupal developers, and any help in testing it would certainly be most appreciated.&lt;/p&gt;

	&lt;p&gt;Beyond 1.0, there are a number of directions the module could take. For one thing, I&amp;#8217;m in the process of experimenting with some of PHP&amp;#8217;s &lt;a href="http://php.net/register_tick_function" target="_blank"&gt;more advanced features&lt;/a&gt; which might provide rather powerful facilities that Trace could take advantage of. It is not inconceivable that something like a JavaScript-based, real-time &lt;span class="caps"&gt;PHP&lt;/span&gt; breakpointer or debugger could be built on top of these language constructs and the foundation already laid in Trace.&lt;/p&gt;

	&lt;p&gt;Before embarking on such flights of fancy, though, I&amp;#8217;m interested in hearing feedback on how the module has been, or could be, useful for &lt;strong&gt;you&lt;/strong&gt;. What&amp;#8217;s the most valuable use case, in your opinion? Is it about troubleshooting production environments, empowering Drupal developers, or both? If you have any thoughts on the matter, please leave a comment or &lt;a href="http://bendiken.net/contact"&gt;contact me&lt;/a&gt; directly.&lt;/p&gt;</description>
 <comments>http://bendiken.net/2006/12/03/easier-drupal-debugging-with-trace#comment</comments>
 <category domain="http://bendiken.net/tags/debugging">debugging</category>
 <category domain="http://bendiken.net/tags/drupal">Drupal</category>
 <category domain="http://bendiken.net/tags/modules">modules</category>
 <category domain="http://bendiken.net/tags/php">PHP</category>
 <category domain="http://bendiken.net/tags/projects">projects</category>
 <category domain="http://bendiken.net/tags/trace">trace</category>
 <pubDate>Sun, 03 Dec 2006 14:28:33 -0800</pubDate>
 <dc:creator>Arto</dc:creator>
 <guid isPermaLink="false">48 at http://bendiken.net</guid>
<feedburner:origLink>http://bendiken.net/2006/12/03/easier-drupal-debugging-with-trace</feedburner:origLink></item>
<item>
 <title>Static Page Caching for Drupal 4.7</title>
 <link>http://feeds.feedburner.com/~r/bendiken/drupal/~3/76552839/static-page-caching-for-drupal</link>
 <description> &lt;p&gt;This weekend I tackled coding up a &lt;a href="http://drupal.org/"&gt;Drupal&lt;/a&gt; feature I&amp;#8217;ve been sorely missing on many a past project: static page caching. My yet-to-be-named module is a replacement for Drupal 4.7&amp;#8217;s built-in caching. Instead of storing the pre-generated cached pages in the database, the module stores them in a cache directory on the file system.&lt;/p&gt;

	&lt;p&gt;Big deal, right? Actually, as I&amp;#8217;ll demonstrate below, it makes all the difference.&lt;/p&gt;

	&lt;p&gt;&lt;!--break--&gt;&lt;/p&gt;

	&lt;p&gt;What Drupal&amp;#8217;s built-in caching does is just cut down on the code that needs to run on each page request, while reducing the database access to a single query that retrieves the cached page to display. This in itself provides a marked speed-up, sure enough, but still necessitates invoking &lt;span class="caps"&gt;PHP&lt;/span&gt; on each page request, and what&amp;#8217;s worse, opening a connection to the backend database. Those connections become a scarce resource once the going gets tough.&lt;/p&gt;

	&lt;p&gt;In contrast, my cache module exports Drupal pages into plain old static &lt;span class="caps"&gt;HTML&lt;/span&gt; files. When a page request can be satisfied from the static cache, &lt;span class="caps"&gt;PHP&lt;/span&gt; is bypassed in its entirety, and the web server serves the cached file straight from the disk at whatever ultimate top speed it is capable of (most &lt;span class="caps"&gt;HTTP&lt;/span&gt; servers these days, including &lt;a href="http://www.stdlib.net/~colmmacc/Apachecon-EU2005/scaling-apache-handout.pdf"&gt;behemoth Apache&lt;/a&gt;, are able to saturate a 100 Mbit/s pipe when serving static files from an adequate server box).&lt;/p&gt;

	&lt;p&gt;Here&amp;#8217;s what this looks like in practice, from a quick benchmark on my PowerBook (1.67GHz PowerPC G4). The yellow bars represent page requests served from Drupal&amp;#8217;s standard, database-backed cache storage, and the bluish bars shooting waaay to the right of them are how many page requests can be served when we throw off the yoke of &lt;span class="caps"&gt;PHP&lt;/span&gt; and shed our dependance on SQL:&lt;/p&gt;

&lt;div class="screenshot"&gt;&lt;img alt="Page requests per second (mean)" src="http://bendiken.net/images/2006/20060528-drupal-benchmark1.jpg" width="500" height="375" border="0" /&gt;&lt;/div&gt;

	&lt;p&gt;As you can see, even in this trivial benchmark, the performance boost was more than an order of magnitude.&lt;/p&gt;

	&lt;p&gt;&lt;small&gt;&lt;em&gt;(This was a makeshift benchmark on an underpowered laptop. The benchmark consisted of timing a Drupal installation&amp;#8217;s 16K front page using &lt;a href="http://www.joedog.org/JoeDog/Siege"&gt;Siege&lt;/a&gt;. This was with Drupal 4.7.1, &lt;span class="caps"&gt;PHP&lt;/span&gt; 4.4.1, Apache 2.2.2 and MySQL 4.1, the latter three binaries as compiled and installed from &lt;a href="http://www.darwinports.org/"&gt;DarwinPorts&lt;/a&gt;, all running pretty much with their stock settings. Additionally, I threw &lt;a href="http://eaccelerator.net/"&gt;eAccelerator&lt;/a&gt; 0.9.5b2 into the mix as well, to give &lt;span class="caps"&gt;PHP&lt;/span&gt; performance a hand, since without it, Drupal&amp;#8217;s database-backed caching had trouble completing the higher concurrency levels.)&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

	&lt;p&gt;Requests per second isn&amp;#8217;t the whole story, either. The above chart doesn&amp;#8217;t show the detrimental increase the standard caching experienced with regards to page response times (i.e. the time the visitor has to wait for a page to finish loading) as the load factor grew. In the chart below, shorter bars signify faster page loads:&lt;/p&gt;

&lt;div class="screenshot"&gt;&lt;img alt="Page response time in seconds (mean)" src="http://bendiken.net/images/2006/20060528-drupal-benchmark2.jpg" width="500" height="375" border="0" /&gt;&lt;/div&gt;

	&lt;p&gt;The best thing about caching a Drupal site as static pages is that it&amp;#8217;s dead simple, in every respect: it&amp;#8217;s trivial to setup, easy to manage, and facile to scale. Throw in a good lightweight web server (&lt;a href="http://www.lighttpd.net/"&gt;Lighttpd&lt;/a&gt; comes to mind), and you&amp;#8217;ll scale to the very limits of your hardware, with few arbitrary road blocks to hold you back. That&amp;#8217;s not the case with Drupal&amp;#8217;s database caching, which requires you to &lt;a href="http://drupal.org/node/51263"&gt;tune&lt;/a&gt; the MySQL configuration, and more often than not, mess around with operating system settings, such as file descriptor limits, to handle even moderate traffic.&lt;/p&gt;

	&lt;p&gt;As a testament to the above, it took me over an hour to configure the MySQL 4.1 daemon on my laptop in order to enable the database-backed cache to even pass the 100x concurrency level benchmark. Before I figured out how to adjust the measly limit of 256 file descriptors that Mac OS X gives processes by default, every other page request was failing with Drupal complaining of not being able to connect to the database. Unless you happen to have stocked away some sysadmin experience, a situation like this could have you scratching your head for a bit.&lt;/p&gt;

	&lt;p&gt;What&amp;#8217;s worse, on a shared host mucking around with system-level stuff is often not even possible nor permitted. Many hosts also impose implicit, arbitary limits on the &lt;span class="caps"&gt;CPU&lt;/span&gt; time that you are allowed to consume on a daily basis, meaning that if you run a popular site with a dynamic &lt;span class="caps"&gt;CMS&lt;/span&gt; like Drupal, you may be facing a forced upgrade to a dedicated server.&lt;/p&gt;

	&lt;p&gt;Serving out static pages, on the other hand, is what web servers do best. You can run even a very popular site, as a static version, off a shared host without receiving that dreaded e-mail from the billing department.&lt;/p&gt;

	&lt;p&gt;Come to think of it, with this module, you might e-mail &lt;strong&gt;them&lt;/strong&gt; for a discount since you wouldn&amp;#8217;t even be using, or needing, your &amp;#8220;fair share&amp;#8221; of server resources. Better yet, depending on what kind of site you run, you might install Drupal on your own computer, downgrade to a cheaper hosting account that doesn&amp;#8217;t provide &lt;span class="caps"&gt;PHP&lt;/span&gt; &amp; MySQL support in the first place, and just upload the generated &lt;span class="caps"&gt;HTML&lt;/span&gt; files via &lt;span class="caps"&gt;FTP&lt;/span&gt; or &lt;a href="http://samba.anu.edu.au/rsync/"&gt;rsync&lt;/a&gt;. The static page caching is multisite-compatible, meaning you could keep a single, private &amp;#8220;master&amp;#8221; copy of Drupal to manage and publish all your sites.&lt;/p&gt;

	&lt;p&gt;So, what&amp;#8217;s the catch, you ask? Well, the usefulness of static page caching really depends directly on what kind of site you run; namely, how &amp;#8220;dynamic&amp;#8221; your site is. Obviously, if you make use of forms or features that submit information from the visitor back to Drupal (e.g. comments, the &lt;a href="http://drupal.org/project/feedback"&gt;feedback&lt;/a&gt; module, etc.), your site can&amp;#8217;t be exported into a 100% static format, since something still needs to handle receiving the form submissions (or to put it technically, &lt;a href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol"&gt;POST&lt;/a&gt; requests are not cached).&lt;/p&gt;

	&lt;p&gt;A similar example would be if you have, for instance, a &lt;a href="http://drupal.org/project/quotes"&gt;quotes&lt;/a&gt; block and consider it very important that it updates with a random quote on every page request, instead of every 5 minutes (say), as would be the case using static caching.&lt;/p&gt;

	&lt;p&gt;Other than the above considerations, the module&amp;#8217;s first and foremost current limitation, shared with Drupal&amp;#8217;s standard caching, is that only pages served to anonymous visitors are cached; requests from logged-in users are passed through to Drupal in a normal fashion. This means that if you run a large community where users need to register and login to participate, most of the user interaction will still need to be dynamic. (I have figured out a way to keep a user-specific cache, as well, but I&amp;#8217;m not yet sure the implementation is worth the effort or complexity. If you think otherwise, please leave a comment to that effect, or drop me a &lt;a href="mailto:arto.bendiken@gmail.com"&gt;private e-mail&lt;/a&gt;.)&lt;/p&gt;

	&lt;p&gt;What the static page cache is ideal for, then, are personal blogs, corporate sites, portals, directories &amp; the like, where most of the site is targeted at anonymous users, and only the occasional feature (say, posting a comment or sending feedback) will need to bypass the cache and be handled by Drupal. For these kind of sites, you can probably benefit from static caching in over a good 95% of your site.&lt;/p&gt;

	&lt;p&gt;I have a long list of client sites running Drupal 4.6 that I&amp;#8217;ll be upgrading to 4.7 as soon as possible in order to let them benefit from this module. But first, I will need to test the module on a couple different shared hosts, and of course, implement it on this site. I&amp;#8217;ll polish the module up for general distribution soon after I complete the above. (For the time being, if you wish to give it a spin, please &lt;a href="mailto:arto.bendiken@gmail.com"&gt;drop me a line&lt;/a&gt;.)&lt;/p&gt;

	&lt;p&gt;Till then, here are a couple of screenshots to tide everyone over:&lt;/p&gt;

&lt;div class="screenshot"&gt;&lt;img alt="The extended cache configuration in Drupal's settings screen." src="http://bendiken.net/images/2006/20060528-drupal-cache-admin1.jpg" width="500" height="375" border="0" /&gt;&lt;br /&gt;
&lt;span&gt;The extended cache configuration in Drupal&amp;#8217;s settings screen.&lt;/span&gt;&lt;/div&gt;

&lt;br clear="all" /&gt;

&lt;div class="screenshot"&gt;&lt;img alt="The administrative interface for the Ajaxy cache rebuild process." src="http://bendiken.net/images/2006/20060528-drupal-cache-admin2.jpg" width="500" height="375" border="0" /&gt;&lt;br /&gt;
&lt;span&gt;The administrative interface for the Ajaxy cache rebuild process.&lt;/span&gt;&lt;/div&gt;

&lt;br clear="all" /&gt;

	&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; (2006/06): some additional details are available from &lt;a href="http://lists.drupal.org/pipermail/development/2006-June/016784.html"&gt;my post to the Drupal development mailing list&lt;/a&gt;.&lt;/p&gt;

	&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; (2006/10): there&amp;#8217;s now a &lt;a href="http://drupal.org/project/boost"&gt;project site and issue tracker&lt;/a&gt; on &lt;a href="http://drupal.org/"&gt;drupal.org&lt;/a&gt;. Please note that the appropriate place to post support requests is in the issue tracker, not as comments to this page (support request comments will get summarily ignored).&lt;/p&gt;

	&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; (2007/07): &lt;a href="http://codesorcery.net/"&gt;Justin Miller&lt;/a&gt; has &lt;a href="http://codesorcery.net/2007/07/23/boost-your-drupal-site/"&gt;a great write-up about using Boost on Drupal 5.x&lt;/a&gt;, with lotsa technical details and a very cool logo to boot.&lt;/p&gt;

	&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; (2007/08): Boost is &lt;a href="http://drupal.org/project/boost"&gt;now available for Drupal 5.x&lt;/a&gt;, too. Many thanks to &lt;a href="http://drupal.org/user/7013"&gt;Alexander Grafov&lt;/a&gt; for the initial porting work.&lt;/p&gt;

	&lt;p&gt;&lt;!--break--&gt;&lt;/p&gt;</description>
 <comments>http://bendiken.net/2006/05/28/static-page-caching-for-drupal#comment</comments>
 <category domain="http://bendiken.net/tags/caching">caching</category>
 <category domain="http://bendiken.net/tags/drupal">Drupal</category>
 <category domain="http://bendiken.net/tags/modules">modules</category>
 <category domain="http://bendiken.net/tags/performance">performance</category>
 <category domain="http://bendiken.net/tags/php">PHP</category>
 <category domain="http://bendiken.net/tags/projects">projects</category>
 <pubDate>Sun, 28 May 2006 12:34:53 -0700</pubDate>
 <dc:creator>Arto</dc:creator>
 <guid isPermaLink="false">33 at http://bendiken.net</guid>
<feedburner:origLink>http://bendiken.net/2006/05/28/static-page-caching-for-drupal</feedburner:origLink></item>
<item>
 <title>Drupal vs Mambo</title>
 <link>http://feeds.feedburner.com/~r/bendiken/drupal/~3/76672740/drupal-vs-mambo</link>
 <description> &lt;p&gt;I originally wrote this article for the Xaneon Development site way back in August 2005. After Xaneon shut down and this article became unavailable, a number of people e-mailed me to ask for it, so I dug up my local copy and decided to publish it here. Note that since this was written, Mambo has become &lt;a href="http://www.joomla.org/"&gt;Joomla&lt;/a&gt; and both Drupal and Joomla have evolved and come out with new releases. Nevertheless, for what it&amp;#8217;s worth, here follows the article in its original form.&lt;/p&gt;

	&lt;p&gt;&lt;!--break--&gt;&lt;br /&gt;
&lt;hr /&gt;&lt;/p&gt;

	&lt;p&gt;&lt;a name="page1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Since our announcement regarding the impending demise of XE2, we&amp;#8217;ve received a ton of e-mail from people asking us to elaborate on the reasons we decided to &amp;#8220;defect&amp;#8221; from &lt;a href="http://www.opensourcematters.org/" target="_blank"&gt;Mambo&lt;/a&gt; to &lt;a href="http://www.drupal.org/" target="_blank"&gt;Drupal&lt;/a&gt;. Instead of answering this question over and over, we&amp;#8217;ve decided to publish a brief (well, that was the intention, anyway&amp;#8230;) overview of what has led us to, ultimately, abandon Mambo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Table of Contents:&lt;/b&gt;&lt;br /&gt;

&lt;ul&gt;
&lt;li&gt;General Comparison (page 2)&lt;/li&gt;
&lt;li&gt;Where Mambo Shines (page 2)&lt;/li&gt;
&lt;li&gt;Technical &amp;amp; Architectural Differences (page 3)&lt;/li&gt;
&lt;li&gt;Application Programming Interface (page 3)&lt;/li&gt;
&lt;li&gt;Internationalization (page 4)&lt;/li&gt;
&lt;li&gt;Search Engine Friendly URLs (page 5)&lt;/li&gt;
&lt;li&gt;Multiple Sites with One Installation (page 6)&lt;/li&gt;
&lt;li&gt;Fine-Grained Access Control (page 6)&lt;/li&gt;
&lt;li&gt;Other Considerations (page 7)&lt;/li&gt;
&lt;li&gt;In Conclusion (page 7)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, when making any comparison such as this, the first thing to realize is that &lt;b&gt;it&amp;#8217;s unlikely that one tool will fit every need and purpose&lt;/b&gt;. To an extent, comparing Mambo and Drupal is comparing apples to oranges. They are two very different systems, with widely differing goals and intended audiences. They just both happen to fit under the convenient heading of &amp;#8220;content management systems&amp;#8221;, meaning you can create decent websites with either one of them.&lt;/p&gt;

&lt;p&gt;Also, the people on our team probably don&amp;#8217;t represent the broad cross-section of Mambo users out there; our needs don&amp;#8217;t necessarily fit their needs. We are. for the most part, people with strong technical backgrounds. Some of our reasons for abandoning Mambo may not resonate at all with the &amp;#8220;average user&amp;#8221;, if there is such a person. Unless you are a programmer, or have an equally deep technical understanding, take everything we say with a grain of salt.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s start off with some background: we&amp;#8217;ve used Mambo for a long time, and we&amp;#8217;ve written a multitude of components, modules and mambots for various versions of Mambo, as well as contributed patches and improvements to existing Mambo add-ons. Most of our own projects were coded for internal purposes or limited distribution only, but some, like our popular XE2 component, which provided Search Engine Friendly (&lt;span class="caps"&gt;SEF&lt;/span&gt;) URLs for Mambo, were released to the Mambo community under open-source terms.&lt;/p&gt;

&lt;p&gt;It was only recently, in the last few months, that we &amp;#8220;became aware&amp;#8221; of Drupal and overcame the initial threshold of trying it out. Funnily enough, this was mostly to steal some good ideas from their &lt;span class="caps"&gt;SEF&lt;/span&gt; implementation; instead, Drupal ended stealing us away from Mambo. So it goes sometimes. Read on for what caused us to abandon all the time and effort we had invested into the Mambo platform&amp;#8230;&lt;/p&gt;

&lt;p align="right"&gt;&lt;em&gt;(...continued on next page)&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

	&lt;p&gt;&lt;a name="page2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;General Comparison&lt;/h3&gt;

&lt;p&gt;Mambo has, for a long time, been ahead of competing &lt;span class="caps"&gt;CMS&lt;/span&gt; projects with regards to marketing. Mambo&amp;#8217;s public image is pretty, appealing and very marketable to management. Mambo has no doubt benefited from the sponsoring company and trademark owner Miro&amp;#8217;s advertising dollars in this regard.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Of course, this was all before the recent hostile project takeover by Miro; while there are people who will argue that any publicity is good publicity, we&amp;#8217;ll just have to sit and see how this particular move will play out.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Drupal, in contrast, has been a community project from the start. There&amp;#8217;s been no single strong corporate sponsor (though that&amp;#8217;s changing with entities such as CivicSpace throwing their weight behind the project) to hold sway over the project, and the overall image has been somewhat less glitzy and more downplayed.&lt;/p&gt;

&lt;h3&gt;Where Mambo Shines&lt;/h3&gt;

&lt;p&gt;Mambo is certainly &amp;#8220;easy on the eyes&amp;#8221;: most people react very favorably to seeing the administration interface for the first time. Another aspect where Mambo is definitely ahead of the game is in installation friendliness, as well as add-on management (installation and uninstallation of components, modules, etc.)&lt;/p&gt;

&lt;p&gt;In comparison, Drupal requires one to manually unzip add-ons on the server, possibly create the necessary &lt;span class="caps"&gt;SQL&lt;/span&gt; tables from supplied scripts, and there is no friendly installation &amp;#8220;wizard&amp;#8221; to guide you through first-time installation. (This is all evolving, though; it shouldn&amp;#8217;t be many months before there is a comprehensive installation system available in Drupal.)&lt;/p&gt;

&lt;p&gt;If the above points are very important considerations to you, as they certainly might be to less technically-savvy users, then you may not really benefit from this article. As stated, our team hails from a quite different user segment.&lt;/p&gt;

&lt;p align="right"&gt;&lt;em&gt;(...continued on next page)&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

	&lt;p&gt;&lt;a name="page3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Technical &amp;amp; Architectural Differences&lt;/h3&gt;

&lt;p&gt;Now we come to the real meat of the matter. During our sojourn into the dark art known as Mambo &lt;span class="caps"&gt;SEF&lt;/span&gt;, we&amp;#8217;ve necessarily become quite familiar with the internal workings of Mambo. To be candidly honest, it&amp;#8217;s not exactly impressive.&lt;/p&gt;

&lt;p&gt;Mambo is a very &lt;em&gt;limiting&lt;/em&gt; design. Pretty much the only &amp;#8220;hook&amp;#8221; into Mambo&amp;#8217;s core, to allow any significant third-party extension to Mambo&amp;#8217;s base functionality without modifying the core files, is the &lt;span class="caps"&gt;SEF&lt;/span&gt; support. (A cynical person might argue that even this &amp;#8220;hook&amp;#8221; exists only for the benefit of a certain commercial &lt;span class="caps"&gt;SEF&lt;/span&gt; extension developed by a former core developer.)&lt;/p&gt;

&lt;p&gt;When we originally designed Xaneon Extensions for Mambo, the most interesting aspect for us was to provide a general &amp;#8220;core extension&amp;#8221; framework which would allow multiple components to utilize XE2 to tap into the core without disturbing the &lt;span class="caps"&gt;SEF&lt;/span&gt; support or interface. Essentially, a &amp;#8220;multiplexing&amp;#8221; of the one and only core hook, for the benefit of all. XE2&amp;#8217;s own code is &lt;strong&gt;very&lt;/strong&gt; extensible, allowing other developers or site administrators to override functionality as they need to. Hopefully someone else will take this idea, or even our code, and run with it.&lt;/p&gt;

&lt;p&gt;Drupal&amp;#8217;s architecture, in comparison to Mambo&amp;#8217;s, is like a breath of fresh air. First off, reading through the core code was a pleasant experience: it&amp;#8217;s not only cleanly designed, and with extensibility constantly in mind; it&amp;#8217;s also well commented and &lt;a href="http://drupaldocs.org/" target="_blank"&gt;documented&lt;/a&gt;. The design is incredibly flexible from the view point of an add-on developer coming from Mambo: there is no need for separate &amp;#8220;components&amp;#8221;, &amp;#8220;modules&amp;#8221; and &amp;#8220;mambots&amp;#8221;, as one Drupal module can perform the tasks of each, and much more besides. There is a general-purpose, all-pervasive &amp;#8220;hook&amp;#8221; system in place, allowing modules to override functionality in the entire lifecycle of content objects (known as &amp;#8220;nodes&amp;#8221; in Drupal), as well as perform actions at certain points in the handling of page requests.&lt;/p&gt;

&lt;h3&gt;Application Programming Interface&lt;/h3&gt;

&lt;p&gt;In Drupal &lt;strong&gt;there is actually an API&lt;/strong&gt; for modules to use; this is a very important point. One reason for the inconsistent quality of Mambo components, and the proliferation of non-standard user interfaces, is no doubt that there &lt;strong&gt;are&lt;/strong&gt; no standards, or at least no significant high-level programming interface that would facilitate the creation of third-party add-ons. Essentially, to create a Mambo add-on, one needs to start from scratch and reinvent the wheel every time. It gets frustrating after a while, even though the developer may learn to refactor and reuse some of his code.&lt;/p&gt;

&lt;p align="right"&gt;&lt;em&gt;(...continued on next page)&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

	&lt;p&gt;&lt;a name="page4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nowhere are the above-mentioned design considerations more visible than in two add-on features we consider absolutely essential for both Mambo and Drupal: internationalization (i18n) and search engine friendly (&lt;span class="caps"&gt;SEF&lt;/span&gt;) &lt;span class="caps"&gt;URL&lt;/span&gt; addresses. Both require fairly low-level &amp;#8220;hooks&amp;#8221; into core functionality to function.&lt;/p&gt;

&lt;h3&gt;Internationalization&lt;/h3&gt;

&lt;p&gt;There is i18n support available in both systems, but in both Mambo and Drupal some patches are required to the core; this in our opinion is inexcusable, in both cases. Only unilingual users and websites would &lt;strong&gt;not&lt;/strong&gt; need i18n, and those kind of websites are a luxury on the European side of the Big Pond, at least.&lt;/p&gt;

&lt;p&gt;However, compare the patching required, and how the add-on is implemented in each case: that is, in Mambelfish versus Drupal&amp;#8217;s i18n module. In such a comparison, Drupal&amp;#8217;s clean design shines through very favorably: the i18n module is much less of a &amp;#8220;dirty hack&amp;#8221; than what&amp;#8217;s required for Mambo, and builds upon the superb localization feature of Drupal. In fact, Drupal&amp;#8217;s i18n patch is only about two dozen one-line code additions, and would be trivial to integrate into the Drupal core in a future release, should Drupal&amp;#8217;s project leadership decide to do so.&lt;/p&gt;

&lt;p&gt;More so, now having lots of practical experience building multilingual sites with both Mambo and Drupal, Drupal&amp;#8217;s i18n works very well and as it is intended to, and feels fully integrated into the system, not &amp;#8220;hackish&amp;#8221; in any way.&lt;/p&gt;

&lt;p&gt;This is not, however, intended to knock down the developer of Mambelfish; having reviewed the code and contributed patches to Mambelfish, we think he&amp;#8217;s done pretty much the best job that&amp;#8217;s possible in the constraints Mambo&amp;#8217;s architecture places on him, and should rather be commended for his ingenuity in providing an adequate solution to a difficult problem.&lt;/p&gt;

&lt;p align="right"&gt;&lt;em&gt;(...continued on next page)&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

	&lt;p&gt;&lt;a name="page5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Search Engine Friendly URLs&lt;/h3&gt;

&lt;p&gt;This is a subject that has become intimately familiar to us. One of our earliest requirements for a &lt;span class="caps"&gt;CMS&lt;/span&gt; was adequate support for free-form, human-readable URLs. At the time we started to use Mambo, there really wasn&amp;#8217;t any solution to this aside from some hacks shared in the Mambo forum, and a commercial &lt;span class="caps"&gt;SEF&lt;/span&gt; add-on for Mambo developed by one of the core developers.&lt;/p&gt;

&lt;p&gt;To solve the problem, we wrote Xaneon Alias Manager 1.0, which allowed us to define friendly URLs manually for all of Mambo&amp;#8217;s content pages. While it worked quite well, and hundreds of websites are still using &lt;span class="caps"&gt;XAM&lt;/span&gt; today, it was a labor-intensive solution for larger sites. Eventually, out of dissatisfaction with having to maintain the friendly URLs manually grew Xaneon Extensions 2.0 (XE2) for Mambo, which introduced automated friendly URLs for both content items and component-generated paths.&lt;/p&gt;

&lt;p&gt;Unfortunately, while achieving a good measure of automation, the component&amp;#8217;s complexity skyrocketed. Mambo itself is simply not designed for friendly URLs, and what&amp;#8217;s worse, most of the third-party components for Mambo don&amp;#8217;t take any measures to even try to support friendly URLs. As a result, writing a &amp;#8220;perfect&amp;#8221; &lt;span class="caps"&gt;SEF&lt;/span&gt; solution for Mambo is an incredibly labor-intensive task. We welcome anyone to try it as a Sunday-afternoon exercise&amp;#8230;&lt;/p&gt;

&lt;p&gt;Over the last year, many people have privately expressed their concerns to us about the fact that a Mambo core developer is charging for a &lt;span class="caps"&gt;SEF&lt;/span&gt; component that should, in their view, be integrated in Mambo&amp;#8217;s core. From our own &amp;#8220;inside perspective&amp;#8221;, we can, on one hand, understand this developer charging for his component, since we know the work involved in developing a comprehensive Mambo &lt;span class="caps"&gt;SEF&lt;/span&gt; solution. (Come to think of it, charging for ours might have motivated us to actually finish it.) On the other hand, we must agree that Mambo should come with significantly better &lt;span class="caps"&gt;SEF&lt;/span&gt; support out-of-the-box, and that the current situation is curious, to say the least. We do believe the (former) core team to be in serious error on this matter.&lt;/p&gt;

&lt;p&gt;Fortunately, the situation with Drupal is much better. &lt;span class="caps"&gt;SEF&lt;/span&gt; support, on a level comparable to our Alias Manager component, is fully integrated into the Drupal core, and there is an open-source add-on available which adds automated friendly URLs in a manner comparable to XE2. There are a few small convenience features we are considering implementing and contributing, but already Drupal&amp;#8217;s &lt;span class="caps"&gt;SEF&lt;/span&gt; support is more integrated and usable than we could ever achieve in Mambo without patches to the core. It simply works as intended, and the code and solution is elegant.&lt;/p&gt;

&lt;p align="right"&gt;&lt;em&gt;(...continued on next page)&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

	&lt;p&gt;&lt;a name="page6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Multiple Sites with One Installation&lt;/h3&gt;

&lt;p&gt;Imagine if you could set up several sites with just one installation; all installed add-ons and themes would be instantly available for use in all these sites, and any security patches you need to apply (after all, all software has bugs, as we know all too well), need only be applied once.&lt;/p&gt;

&lt;p&gt;And, to make it all even sweeter, imagine if you selectively share the configuration settings and databases tables between these sites so that, for instance, you could share user accounts between some sites, or maintain only one large content archive, selectively using and sharing articles between your sites. &lt;strong&gt;Wouldn&amp;#8217;t this be great?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Guess what. Drupal does all of the above, and it works perfectly. It&amp;#8217;s not too bad to set up, and once done, saves a bunch of time and effort.&lt;/p&gt;

&lt;p&gt;We figured out a way to do most of the above in Mambo as well, since XE2 has some access to the relevant &lt;span class="caps"&gt;PHP&lt;/span&gt; variables before handing control over to Mambo&amp;#8217;s core. The multi-site support in current beta versions of XE2 is buggy, but the concept should be sound. It would take a lot of work to bring it up to Drupal&amp;#8217;s current level, though.&lt;/p&gt;

&lt;h3&gt;Fine-Grained Access Control&lt;/h3&gt;

&lt;p&gt;As far as access controls go, this is simply a no-contest. We all know how lacking Mambo&amp;#8217;s access control scheme is; hopefully there will one day be an improvement to it.&lt;/p&gt;

&lt;p&gt;Drupal allows us to define arbitary roles (for instance, &amp;#8220;moderator&amp;#8221;) for users, and assign permissions to these roles on a module-by-module basis (for example, one selection is &amp;#8220;moderate comments&amp;#8221;). It&amp;#8217;s intuitive, very fine-grained and very flexible. There is even a module available that will automatically handle giving a user a new role after they&amp;#8217;ve paid for a subscription with PayPal, as well as many other add-ons that allow true micromanagement of the website&amp;#8217;s access control policies, should that be necessary.&lt;/p&gt;

&lt;p align="right"&gt;&lt;em&gt;(...continued on next page)&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

	&lt;p&gt;&lt;a name="page7"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Other Considerations&lt;/h3&gt;

&lt;p&gt;Let&amp;#8217;s just say the list of goodies Drupal delivers us is pretty long. We haven&amp;#8217;t even discussed Drupal&amp;#8217;s templating system (keywords: &lt;span class="caps"&gt;XHTML&lt;/span&gt;, &lt;span class="caps"&gt;CSS&lt;/span&gt;, total customizability, and optionally, Smarty) or the unlimited content hierarchies you can create with the built-in Drupal module called, appropriately enough, &amp;#8220;taxonomy&amp;#8221;.&lt;/p&gt;

&lt;p&gt;We could go on for two dozen pages. It wouldn&amp;#8217;t be difficult at all. But we don&amp;#8217;t need to convince anyone, and our reasons aren&amp;#8217;t necessarily reasons why someone else would switch. It would be silly to get religious about something like a content management system. As always, just pick the right tool for the job at hand. For us, that has for now in most cases proven to be Drupal.&lt;/p&gt;

&lt;h3&gt;In Conclusion&lt;/h3&gt;

&lt;p&gt;The bottom line is simply that Drupal allows us to be significantly more productive. In our experience, we are able to put together complex sites in a fraction of the time it would have taken us with Mambo. We can use Drupal modules such as Flexinode, which brings to mind some of the power of Lotus Notes, to quickly solve needs that would have cost us a lot of custom programming (or buying a custom component) in Mambo. We can take advantage of Drupal&amp;#8217;s excellent templating to produce truly unique-looking sites with not much effort.&lt;/p&gt;

&lt;p&gt;This site, xaneon.com, is at the moment still a Mambo installation, running our Xaneon Extensions for Mambo. It&amp;#8217;s the last bastion of the old, standing against the coming wave of change. It&amp;#8217;s unlikely to hold out much longer, though&amp;#8230;&lt;/p&gt;

&lt;p&gt;We don&amp;#8217;t regret the time we spent with Mambo; we will always have a soft spot for it and appreciate the many friends and acquaintances we made along the way. We sympatize with the current plight of the &amp;#8220;true&amp;#8221; Mambo core development team and wish them the best of fortunes in their new endeavour. As we&amp;#8217;ve stated previously, we might pop in at &lt;a href="http://www.opensourcematters.org/" target="_blank"&gt;Open Source Matters&lt;/a&gt; from time to time to catch up on how the &amp;#8220;rebel Mambo&amp;#8221; project is doing. &lt;strong&gt;Goodbye, and thanks for all the fish!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References and discussion regarding this article:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://drupal.org/handbook/drupal/gallery" target="_blank"&gt;Some good examples of websites built with Drupal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://drupal.org/cases" target="_blank"&gt;Drupal-related case studies for different types of websites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://drupal.org/node/15223" target="_blank"&gt;Why Linux Journal converted to Drupal and how it went&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://forum.opensourcematters.org/index.php/topic,1676.0.html" target="_blank"&gt;Forum discussion at opensourcematters.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://drupal.org/node/29819" target="_blank"&gt;Forum discussion at drupal.org&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
 <comments>http://bendiken.net/2006/02/08/drupal-vs-mambo#comment</comments>
 <category domain="http://bendiken.net/tags/articles">articles</category>
 <category domain="http://bendiken.net/tags/content-management">content management</category>
 <category domain="http://bendiken.net/tags/drupal">Drupal</category>
 <category domain="http://bendiken.net/tags/joomla">Joomla</category>
 <category domain="http://bendiken.net/tags/php">PHP</category>
 <category domain="http://bendiken.net/tags/search-engine-friendly">search engine friendly</category>
 <pubDate>Wed, 08 Feb 2006 06:15:00 -0800</pubDate>
 <dc:creator>Arto</dc:creator>
 <guid isPermaLink="false">10 at http://bendiken.net</guid>
<feedburner:origLink>http://bendiken.net/2006/02/08/drupal-vs-mambo</feedburner:origLink></item>
</channel>
</rss>
