Monthly Archives: January 2009

On Smarty and Objects, Part 3

Smart?This is the thrilling conclusion to my past couple posts on this topic. Really it’s just a summary and some closing remarks. Also, I just realized I forgot to mention in my previous posts: all of this is in the context of Smarty 2.6.x.

Hopefully you already read Parts One and Two. But if not, basically I went over my original Smarty usage pattern when I first started XBB several years ago (wow, has it really been that long?), then I covered my new Smarty usage pattern, the problems I encountered and solutions.

XBB, being a bulletin board system, has a lot of dynamic data being displayed on each page, especially hierarchical data structures. This makes for lots of non-trivial presentation logic. Originally I went with very small template “chunks” that were assembled by XBB’s controller scripts. The benefit to this was the templates were complete devoid of any “business logic” (and objects in general!). The downsides were an explosion of template files, an explosion of template variables, and too much presentation logic leaking into places where it didn’t belong (anything that isn’t a template).

My new Smarty usage pattern relies on Smarty’s object binding support, specifically via their assign_by_ref() method.

By assigning my objects directly to template variables, I get around all of the downsides to my previous Smarty usage: I get all of the presentation logic pushed back into the templates where it belongs, I have fewer template files, and I have fewer template variables.

Again, here are the couple of “gotchas” I ran into when assigning objects to template variables:

  1. You cannot reference static class members (like class constants) as method parameters in template syntax. For example, this will not work: {$foo->someMethod(Class_Bar::SOME_CONSTANT)}. The way around this was to simply use standard PHP constant definitions to define your constants and access them in the template via the standard Smarty $smarty.const syntax, like: {$foo->someMethod($smarty.const.SOME_CONSTANT)}
  2. When using objects that were passed to the template via assign_by_ref(), do not put spaces after the opening parentheses and before the closing parentheses if you need to pass arguments to methods in template syntax. For example, this will not work: {$foo->someMethod($smarty.const.SOME_CONSTANT)}, but this will work: {$foo->someMethod($smarty.const.SOME_CONSTANT)}. Note the lack of spaces around the argument in the latter.

So if you want to use your objects directly in Smarty templates, just keep these points in mind and it should be smooth sailing!

Now, in my searches for answers to these problems I encountered, I discovered a couple posts on the Smarty forums where people basically disagreed with this practice of assigning and using objects directly in templates as a bad practice (or using class constants directly in the template code). They claim (in a nutshell) that you are polluting your presentation layer with business logic.

I disagree with this viewpoint (at least in the case of XBB). Unless I’m missing something completely, in the case of XBB, the trade-off between putting these object calls directly into the templates versus strictly passing in unstructured data was not worth it. In fact it seemed to swing the problem the other way: presentation logic was now polluting the “business logic” layer. Maybe I’m just misinterpreting the opinions in those forum posts (or taking them out of context, etc.), but I think it’s perfectly reasonable to directly use the “business objects” in templates assuming it’s strictly for presentation logic purposes. I guess by not passing the objects to the templates, you don’t have to rely on the assumption/convention that no business logic will leak into the templates, but I still think it’s a better way of dealing with the problem of getting data to the templates for display purposes.

At any rate, I hope you enjoyed these past few posts, and if you’re a PHP developer using Smarty I hope you found some useful nuggets of info here. I take back what I said in my first post that my experience with Smarty’s object support has been less than pleasant. While it was certainly a bit frustrating at times to get around the issues I encountered, now that I have things working the way I want them, I’m happy again =]. I still recommend using Smarty if you’re developing any sort of non-trivial web application in PHP. Of course make sure to read Smarty’s documentation to ensure it’s right for your case.

And if anyone would like to discuss this question of how to handle this separation of business logic and presentation logic with Smarty, feel free to comment. Or if you have any feedback on either of my Smarty usage patterns, feel free to comment as well.

Now it’s time for me to get back to coding!

On Smarty and Objects, Part 2

Smart?Last time, in Part 1, I gave you the background information on my Smarty usage, and an overview of my original pattern for passing data to Smarty templates. Now I’ll show some real code examples and go into details of the problems I encountered using Smarty’s object support. A bit of an update, though: since my first post, I accidentally stumbled upon a way to get my particular use case for object binding in templates to work correctly. However, since I think it’s still a bit of a gotcha, I’d still like to share it.

I’ll start off with a code example of my old style for passing data to Smarty templates that I referred to in Part 1. This script was the old XBB “index.php” that displayed summary information of all of the Categories and the Forums those Categories held. Again, I don’t know if this is best practice or even worst practice, it’s just what worked for me at the time. I’ve left out some extraneous code (require statements, comments, etc.) to focus on the parts concerning Smarty and passing data to templates to keep things relatively short; hopefully most of the variable names are self explanatory (at least that’s what I was shooting for! =]). In these examples, the $boardTemplate object is an instance of the Smartyclass itself.

<?php
$boardTemplate->assign('current_time', BuBOL_BBUtil::getDate(TIME_FORMAT, BuBOL_BBUtil::getGmtTimestamp(), TIME_OFFSET));
 
$boardTemplate->assign('total_members',   BuBOL_Board::getInstance()->getConfigValue('total_members'));
$boardTemplate->assign('total_threads',   BuBOL_Board::getInstance()->getConfigValue('total_threads'));
$boardTemplate->assign('total_posts',     BuBOL_Board::getInstance()->getConfigValue('total_posts'));
$boardTemplate->assign('new_member_id',   BuBOL_Board::getInstance()->getConfigValue('new_member_id'));
$boardTemplate->assign('new_member_name', BuBOL_Board::getInstance()->getConfigValue('new_member_name'));
 
$boardTemplate->display('header_page.tpl');
$boardTemplate->display('header_index.tpl');
 
if (0 >= BuBOL_Board::getInstance()->getNumCategories())
{
    $boardTemplate->display('no_categories.tpl');
}
else
{
    try
    {
        $allCategories = BuBOL_Board::getInstance()->getAllCategories();
 
        $boardTemplate->display('view_cat_headers.tpl');
 
        foreach ($allCategories as $currentCategory)
        {
            $boardTemplate->assign('category_about', $currentCategory->getAbout());
            $boardTemplate->assign('category_id',    $currentCategory->getId());
            $boardTemplate->assign('category_name',  $currentCategory->getName());
 
            $boardTemplate->display('row_category.tpl');
 
            if (0 >= $currentCategory->getTotalForums())
            {
                $boardTemplate->display('no_forums.tpl');
            }
            else
            {
                try
                {
                    $theseForums = $currentCategory->getAllForums();
 
                    foreach ($theseForums as $currentForum)
                    {
                        $forumLatestPostDate = BuBOL_BBUtil::getDate(TIME_FORMAT, $currentForum->getLatestPostDate(), TIME_OFFSET);
 
                        $boardTemplate->assign('forum_about',               $currentForum->getAbout());
                        $boardTemplate->assign('forum_id',                  $currentForum->getId());
                        $boardTemplate->assign('forum_name',                $currentForum->getName());
                        $boardTemplate->assign('forum_count_threads',       $currentForum->getTotalThreads());
                        $boardTemplate->assign('forum_count_posts',         $currentForum->getTotalPosts());
                        $boardTemplate->assign('forum_last_post_date_raw',  $currentForum->getLatestPostDate());
                        $boardTemplate->assign('forum_last_post_date',      $forumLatestPostDate);
                        $boardTemplate->assign('forum_last_post_id',        $currentForum->getLatestPostId());
                        $boardTemplate->assign('forum_last_thread_id',      $currentForum->getLatestThreadId());
                        $boardTemplate->assign('forum_last_thread_subject', $currentForum->getLatestThreadSubject());
 
                        $boardTemplate->assign('forum_last_member_id',   $currentForum->getLatestPoster()->getId());
                        $boardTemplate->assign('forum_last_member_name', $currentForum->getLatestPoster()->getDisplayName());
 
                        $boardTemplate->display('row_forum.tpl');
                    }
                }
                catch (Exception $e)
                {
                    $boardTemplate->display('no_forums.tpl');
                }
            }
        }
    }
    catch (Exception $e)
    {
        $boardTemplate->display('no_categories.tpl');
    }
}
 
$boardTemplate->display('footer_index.tpl');
?>

Now you may find other problems with this code, but the point I’m trying to get across is: look at the number of template variables I’m setting to get information to the templates (there’s 21 in case you were curious), look at the number of referenced templates being displayed (there’s 10). And look at all the presentation logic in this script. What’s more is that this was the smallest XBB script I could find in the old code base. This is less than ideal in my opinion; it just looks way too complex and busy. And most of the information I’m passing is similar to what Martin Fowler called Data Clumps in his “Refactoring” book.

Wouldn’t it be great if I could just pass the object itself (e.g. the $currentForum object) directly to the template and let the template worry about what to do with the data? Well, it turns out one can do just that using Smarty’s object binding support.

Now, as I mentioned before, after I discovered Smarty’s object support in templates, I decided to take advantage of it in my new version of XBB. If you notice in the code above, the PHP script decides when to display template “chunks” like header_page.tpl and footer_page.tpl. As you may have guessed, these 2 templates are the standard header and footer for every page in XBB. These are presentation details, so there really is no reason for the PHP script to be concerned with when these templates need to be displayed. That became my first change: pushing header and footer template references into the templates that needed them. A small, worthy change in my opinion, but that doesn’t have much to do with object binding support.

My next change was to fix my Data Clumping problem mentioned earlier. To get rid of my Data Clump, I need to give the entire $currentForum object to the template. Looking at Smarty’s objects page, you’ll notice there’s essentially 2 methods to pass objects to templates:

  1. register_object() – Smarty documentation
  2. assign()/assign_by_ref() – assign_by_ref()‘s syntax and functionality is very similar to assign(), but assign_by_ref()saves you the extra in-memory copy of the object, Smarty documentation

I’m not a fan of their object access syntax when using register_object(), it just looks clunky in my opinion when you need to pass parameters into method calls, and it’s a few more characters to type anyway:

<?php
// binding with register_object():
$smarty->register_object('currentForum', $currentForum);
// access in the template: (assuming I had to pass something into the method)
// {$currentForum->getDisplayName p1='disableShouting'}
 
// versus
 
// binding with assign_by_ref():
$smarty->assign_by_ref('currentForum', $currentForum);
// access in the template:
// {$currentForum->getDisplayName('disableShouting')}
?>

Now that I’d settled on using assign_by_ref() to pass objects to the templates, I merrily went about converting all of my scripts and grinning the whole way as my PHP scripts shrank with more and more presentation logic moving to the templates where it belonged. Then I hit my first speed bump.

You’ll notice in my first example script above, the first several template variables are sort of global configuration values, i.e. calls to getConfigValue(). Now I did refactor that a bit into a “ConfigurationService” class, but regardless, I need that data passed into the template. I didn’t like those magic strings (total_memberstotal_threads, etc.) floating around, so I pushed those into class constants.

Side note: why did go with class constants instead of general PHP constants (i.e. created with define())? I like the pseudo-namespacing one gets with class constants instead of cluttering up the global constant “namespace”.

Well, as I soon discovered, Smarty doesn’t like static references to class constants in method calls within templates. Here’s an example of bad template code:

<title>{$configService->getValueFor(Xbb_Domain_ConfigurationKeys::BB_TITLE_KEY)}</title>

This bit of template code will cause Smarty to error out like:

Fatal error: Smarty error: [in header_page.tpl line 10]: syntax error: unrecognized
tag: $configService->getValueFor(Xbb_Domain_ConfigurationKeys::BB_TITLE_KEY)
(Smarty_Compiler.class.php, line 446) in ... on line ...

Rats… well, that’s slightly annoying, but I was able to workaround it by simply defining global constants for my configuration keys and using the constant in the template code. Not as elegant as I would have liked, but I can live with it. Here’s the workaround:

<title>{$configService->getValueFor($smarty.const.XBB_KEY_BB_TITLE)}</title>

For those who don’t know, the $smarty.const.... is the template syntax for accessing generally defined constants in templates (Smarty documentation).

However, even after working around that issue, I ran into another problem. That last template syntax snippet still causes Smarty to fail with an error very similar to the previous error. I didn’t understand what was going on, so I thought maybe there’s some limitation with using objects passed to templates via the assign()/assign_by_ref() call. It was then I broke down and switched by object bindings to use register_object() instead. It was ugly syntax in the template code, but at least my templates started compiling and rendering again:

<title>{$configService->getValueFor p1=$smarty.const.XBB_KEY_BB_TITLE}</title>

After a while I noticed something strange: even though my templates were compiling and rendering again, the calls to$configService weren’t actually returning anything! It was as if Smarty was completely ignoring my method calls altogether. I did some debugging on the XBB & BuBOL side to make sure my “controller” and “model” code was really working. It was working, so the problem had to be in the presentation layer (in the Smarty templates). I decided to crack open one of the compiled Smarty templates (the raw PHP code Smarty generates at run-time) and see what the heck was going on. Here’s what I saw in the case of my template code above:

<title><?php echo $this->_tpl_vars['configService']->getValueFor; ?></title>

That doesn’t look right: Smarty’s generated code was indeed trying to call my $configService object, but it wasn’t passing anything to the getValueFor() call like I told it to in the template code.

Side note: what’s also interesting is my ConfigService class has a check to make sure the given property name you’re trying to retrieve isn’t null or empty. Surprisingly this check didn’t throw an exception in this case. Not sure how the PHP engine is handling this case where no arguments are given to a method call that is expecting one parameter (and no default values are defined). Weird…

It was at that point I punched my desk in frustration (I don’t consider myself a violent person, honestly =]), decided to give up using Smarty’s object binding support, and figured I’d just go back to my old Smarty usage patterns detailed in the first code snippet above. I also ran over here to create some blog posts detailing my adventures with Smarty’s object support, hoping to help others from wasting time with the same problems I had encountered.

After I made my first blog post on this topic (and had a good night’s sleep), I went back to search around on the web for a solution to this problem. I know Smarty is a powerful and useful template engine and I figured it had to support this use case or at least someone else had to have run into this; I refused to believe what I was trying to do wasn’t supported. Unfortunately, my search didn’t yield much at all on the topic.

Then, just last night, I was tinkering with the templates in XBB again, flipping back and forth between calls toregister_object() and assign_by_ref() trying to find some way to get my object binding to work. Then by accident, I found it:

<title>{$configService->getValueFor($smarty.const.XBB_KEY_BB_TITLE)}</title>

Look familiar? It looks like my first attempt after I got rid of the static class constant reference with one key difference. Here’s the gotcha:

There are no spaces between the parentheses and the method argument

I couldn’t believe it, heh. This template code worked perfectly. Now I had my cleaner template syntax (by using assign_by_ref() again) and my $configService call was returning the expected value.

I always put a space after the open parentheses and before the closing parentheses when I write method calls with arguments, as per my coding standard. I’ll have to update my coding standard with this case. Or I suppose I could submit a patch to Smarty at some time (I imagine it’s just a regular expression in their compiler that needs to be updated).

At any rate, since this post seems to be getting a tad bit long, I’m going to break it off here and start a Part 3 posting with a conclusion and summary.

On Smarty And Objects, Part 1

Smart?For those of you who follow this site, you know I’ve been doing quite a bit of development work in PHP again lately, especially with BuBOL. Well, recently I’ve had some experiences with the Smarty template engine and it’s integration with objects. I figured this was a nice excuse for me to make another blog post to share some of my experiences. This is the first of a multi-part posting, primarily because it’s getting late and Maggie is asking me to come to bed.

As I said, I’ve been working on BuBOL again and finally got enough of the plumbing done recently to start wiring up front-end XBB pages again. For those who don’t know, XBB is basically the prototype bulletin board software I’m writing and using as a driver for BuBOL’s features and BuBOL’s design in general. At any rate, from a fairly early stage XBB’s presentation layer has been done using Smarty templates.

I realize PHP itself is essentially a template engine and that some folks consider Smarty to be overkill in many cases – they conveniently have a “right for me” page – but I like it’s feature-set and the way it forces you to split out your presentation layer from the rest of your application.

Anyway, I’ve been using Smarty since pretty much day one in XBB. The way I originally used Smarty was to pass every value to the templates through assign() calls as string values, i.e. no data structures, including arrays. This worked, functionally, however it led to a few questionable side effects:

  1. An explosion of small template files. These “chunks” were tedious to edit since the HTML for any given page was spread over several template files. This was especially tedious since I was any good at CSS layouts (I’m still not that good, heh) and I used lots of embedded HTML tables to do my formatting =]
  2. Dozens upon dozens of assigned template variables. I’d frequently forget when I added a template variable for some value and end up duplicating template variables.
  3. Presentation logic began leaking out of the templates.

Maybe I just wasn’t using Smarty the best way for my particular use cases, I don’t know; I never released that code so I never really received any feedback on it. The last point really bothered me though as my “controller” scripts (if XBB can be seen as an MVC implementation where BuBOL was the model, Smarty was the view, and XBB was the controller) seemed too complex. But since I didn’t really have any better ideas at the time I went with it, since I was still fairly quickly producing XBB functionality.

Fast forward a few years to today. BuBOL has undergone (yet another) significant overhaul, so I figured I may as well take this opportunity to re-think my Smarty usage and push the presentation logic back where it belongs… in the presentation layer! Also I thought this would be a nice time for me to learn more about Smarty’s object support.

Admittedly, my experiences with Smarty’s object support have been less than pleasant, but again, maybe I’m just doing something wrong. But for now I have to wrap this up, so I’ll leave you in suspense until I post my actual experiences in Part 2 of “On Smarty and Objects”, coming soon! =]