While at Drupalcamp Copenhagen I got into a few conversations about Drupal aha! moments, particularly around theming. After trying to talk in the abstract about the things that finally "clicked" for me when I was learning Drupal, I offered to write it up with some specifics. So, for my Danish buddies and any other curious Drupalers, these are my two biggest aha! moments that accelerated my Drupal theming ninja skills: "getting" the theme system and working with forms. I've already written about modifying forms
and since "getting the theme system" is a big chunk, I'm going to focus just on that moment in this article.
Now "getting" the theme system could mean a lot of things, and what I mean is that once I understood what Drupal does by default and the way it expects me to override that, a lot of things fell into place for me. Instead of trying to control the firehose at full blast, I could see where the spigot was and how to turn the water down. I actually had this realization at a Lullabot workshop, back before I became a 'bot. It came about because I was formally introduced to PHPtemplate engine. In Drupal 5, which was current at the time, this was through a tour of the phptemplate.engine file. In Drupal 6 PHPtemplate is tied more closely to core and so in this article I'm going to be looking at theme.inc. For me, this was probably the biggest aha! moment I've ever had with Drupal. I'm not going to try to explain the whole Drupal theme system. There is a great guide on Drupal.org
, as well as a few theme books out there, that do a good job of covering the big picture and the nitty-gritty details. We even have two videos
that walk you through the wonders of theming. Here, I just want to talk about the little nuggets that happened to tie things together for me.
Drupal default output
Before I dive in too far, I want to do a quick review of theming basics, which amounts to "overriding stuff." Drupal gives you HTML to start with and most of theming is figuring out what the default is and then how to change that. In Drupal 6 it is a bit easier to see where some of the default HTML output is coming from because we've moved a fair amount of it to template files (*.tpl.php). We also have the super-duper handy and awesome Theme developer module, which is part of Devel
. That should be in every themer's toolkit, and will get you headed in the right direction for overriding all the little bits that make you nuts. The big pieces that we work with in theming pretty much break down to files and functions.
Most people are familiar with these files, ending with tpl.php, in your theme's folder, where you are overriding
the default templates. The original, default files are sprinkled throughout your site in various module folders. This is true of both core and contrib. If you look in your core module folders, e.g. /modules/node, you will see that a fair few of them have template files. This is true for say the comment and forum modules, in addition to node. The default page and block template files are located in the core system module folder (/modules/system). For contrib, it works the same way and any module can provide a default template file in its folder. Once you know where it is coming from, overriding it is as easy as copying (not moving) that template file into your theme folder, clearing the cache and then doing what you want to it.
So we can see where things like the page.tpl.php and node.tpl.php HTML are coming from, but what about the variables that they use, like $site_name and $submitted? If you are working from a contributed theme to build your own, you may not have the handy variables listing that the default templates, pointed to above, will give you. You can always go back and crack open a core default template file to see which variables it can use. Alternatively, you can use a tiny bit of PHP to get a handy list printed out. This ends up being even better because various modules can add their own variables to the mix and it's nice to get a "live" list.
You can pop this little bit of PHP at the top of any template file and get a wonderful, kinda ugly, but useful, list of variables (feel free to use your function of preference: print_r, dpm, etc.):
<?php print '<pre>' . var_dump(get_defined_vars()) . '</pre>'; ?>
The other method of default output, which makes you get into the nitty gritty a bit is theme functions. This is how a module provides some default HTML output, but it is not tidied up in a separate template file for you. These are any function in a module's code that begins with theme_. Most often if HTML output is in a function rather than a template, it is because it has some logic mixed in, so chances are these will require a little more care when overriding. The basic plan is the same "copy and own it" model: copy the theme function into your theme's template.php file, change theme_* to yourthemename_*, clear the cache, and edit away.
Now that we see what we are working with, let's dive in a bit more and see where the hell some of the magic stuff is coming from. For me, that was the trick. Knowing I can change things is powerful, but seeing where they came from and learning from that experience is what changed my perception from "theming is magic voodoo" to "I can master this." Instead of memorizing magical incantations, I found my own little guidebook to learn, giving me solid ground under my feet.
One thing that never really occurred to me when I started theming was to open up my Drupal core folders and files to see what was going on. I just stayed on the "surface" with my own theme and pondered at the mystery of how these things actually worked. As a general rule, I advise everyone to open things up and take a look around at least once, just for orientation if nothing else. The main core file I want to look at here is theme.inc, located in the top-level /includes folder. Having a gander through the comments in this file, a few in particular, can help get more of an idea of what is going on behind the scenes. I'm not going to go through every line here. These are just my personal aha!s where the curtain was pulled aside.
The theme() function, theme.inc line 491
This is the master funnel for display in Drupal. The theme() function is what sets up this system of overrides and lets you jump in where you need to. If you open up Drupal's index.php file, which drives your whole site, you will see that it has very few lines of code in there and one key piece is line 36:
print theme('page', $return);
. Lots of stuff happens in your site, but without the theme() function you don't get much on the screen.
To properly hook into the theme system, you should always use theme() when you want to display something directly from a function. For example, the Nice menus module provides some handy theme functions that you can use straight up, without necessarily needing to override
them. Since they are just PHP functions, the first inclination if you want to use them is to just do something like
. While that does "work" in the sense that the proper thing will be displayed, you have just bypassed Drupal's theme override system. The proper way to call any theme_* function in Drupal to display its output is
print theme('nice_menu_primary_links', 'right');
The family of default theme_* functions, theme.inc line 1026
The minions of the master theme() function are the actual working functions that build up and output the HTML. These are referred to as theme functions (as opposed to the
theme() function) and the function name always begins with theme_* regardless of the module's name. Any module (including core) that wants to output HTML somewhere uses a theme function which you can always override, as noted above. In theme.inc Drupal core provides a whole bunch of default ones that you can override, just like any other theme function. Since they are not created by a particular module they can sometimes seem mysteriously hidden. One thing I love about finding the original theme function that controls something is that, not only can you override that function, but you can see what code is being used to create the default to begin with. Just browsing through these core theme functions helped me learn what Drupal has available for me and best practices for creating my own output. I should note that you can see a nice big list of the core theme functions at http://api.drupal.org/api/group/themeable/6, and it is awesomely searchable, but knowing where those things come from in my own Drupal install took a lot of the mystery out of it for me and made me feel more in control. Once I got oriented in my own installation, using the API reference felt a lot more solid to me. (Maybe that's just me, but there you have it.)
template_preprocess() and friends, theme.inc 1705
You may have noticed in the earlier section about template variables that I talked about how to find out what is available, but not how to do much with them. This is where template_preprocess() comes in. The preprocess functions let you "prepare" things before heading out to the template files. This is probably my favorite thing to "get" with theming. You can not only override all those darned little variables if you need to, but you can create your own too. Grokking this made me feel like a theming goddess. In your template.php you can add your own themename_preprocess_* functions and add variables with abandon. Just like knowing where default theme functions come from taught me a lot, the same thing is true of those pesky variables that never quite did what I wanted out of the box. By looking at the ones in theme.inc (as well as the ones in various modules), I can again see what Drupal is doing and see what I want to tweak about it in my template.php file. For example, if I don't like the pipe in the default $head_title variable, I can see where that is coming from in
$variables['head_title'] = implode(' | ', $head_title);
(theme.inc, line 1807) and then simply override it in yourthemename_preprocess_page() like so,
$variables['head_title'] = implode(' :: ', $head_title);
Using your theme files correctly
One thing that is bandied about, but not always understood or implemented, is that you should put the logic in your template.php file of your theme. I was initially confused about how to do that, when all I wanted was to add a bit of PHP to a particular tpl.php file and be done. The idea here is that the template files (*.tpl.php) should mostly contain HTML, some
and some basic if/else logic. If you're writing a 10 line chunk of PHP in your tpl.php, you're doing it wrong.
At its simplest, if you want to add some new output to your tpl.php, you can simply create a new variable for it in template.php, as discussed just above, and then pop that nice, clean variable into the tpl.php file. For example, let's say I'd like to have a custom bit of output that grabs the current day and displays a message like "Happy Tuesday!" In my template.php file, I can add my logic to the right preprocess hook, say the page, so I can use it in page.tpl.php. So I'd have something like:
$variables['happyday'] = 'Happy ' . format_date(time(), 'custom', 'l') . '!';
and then in my page.tpl.php, I can just pop in a little <?php print $happyday; ?> right where I want it and it'll work, like magic.
You can do the same thing even if you have a bunch more logic/PHP to it. You can encapsulate a whole chunk of functionality in its own function within template.php and then make a variable that just calls that internal function for the output it needs. The idea here is to keep all of your logic and heavy lifting centralized in that one template.php file (certainly makes it easier to find) and keep the tpl.php files nice and clean. This is especially important if you have someone working on the HTML and CSS for the theme that is not familiar with PHP. The separation not only makes them feel more comfortable with the tools they know, but lessens the chance of a PHP booboo from someone not quite sure what is happening in there.
Go break some stuff
OK, so that is just the tip of the iceberg, but these are the things that made me much more confident exploring Drupal theming. As I mentioned at the beginning, there are lots of resources out there that you can work through to get more details and examples. Another great way to monkey around is to grab an existing theme that has some interesting stuff going on in its template.php file and change things around to see what you can do. Something like 960 Robots
would do, or for more complex stuff look at a theme like Nitobe
, which has all kinds of juicy stuff to break. A lot of people start off theming with the Zen theme
, which is very well commented and worth a read, but it is based around sub-theming which can sometimes be confusing when you are just trying to learn the basics of the theme system. Zen can be useful to build out themes, but if you want to poke and break things, a straight-up theme may work better in terms of being clear of where you are and where things are coming from. At the end of the day, theming in Drupal becomes much more clear as you get comfortable with what Drupal is doing under the hood and by just playing with it. You will find your own aha! moments as you go. Feel free to share yours in the comments.