PHP types, exceptions, and Smarty templates

Continuing on with the implementation of a custom CMS for the ministry website, I’ve been doing a lot more type checking than I used to do with PHP.  That’s actually one of the things that has always bothered me about languages like PHP; as a programmer I make a lot of mistakes and it’s a real boon if my programming language supports double checking me without any extra intervention, but PHP doesn’t offer much in the way of enforcing variable types.  Starting with PHP 5.1 we’ve had “type hinting”, which means I can force a parameter to a function/method to be an object of a certain type or an array.  Well, this doesn’t really help if my function needs a string, an int, or an array containing strictly typed elements.

What I’ve been doing to get around this is using type hinting wherever I can, then doing extra manual checks on the types of other parameters via the is_* family of functions.  If I get an incorrect type, I throw an InvalidArgumentException.  I’ve been using exceptions extensively in this project because of the multi-layered nature of it.  If I relied on return values alone for monitoring errors there would be layer after layer of if-statements and checks passing data back up the call stack, all the while making it very difficult for my functions and methods to return useful data to their callers without the use of output parameters.

One of the most recent things I’d done was to set up the page loading code to generalize error handling.  I am currently using a layout similar to this (not very pretty, I know—I’m working on it):

There’s actually a good bit more to it than that, but I’ve cut things out to make it less distracting.  Basically I get my page data, feed it to the template requested, and grab the parsed template output using $smarty->fetch().  If there is an exception thrown I check to see if the real exception message is safe to show the user or whether I need to use a general one, then I fetch the error page template to show the user instead.  Sounds OK, right?

When I tried testing this code be requesting an invalid photo album on the photo gallery page, I got two instances of the page header then the error message and page footer.  I thought I’d goofed up somewhere and included something wrong.  After more testing, I found that if the exception was thrown before the call to $smarty->fetch() (for instance if contentManger couldn’t find the page I fed it) there was no problem.  That got me to thinking about output buffering.

I knew smarty used output buffering in the implementation of fetch(), but I didn’t initially considering the implications that brought with it.  I haven’t looked at the code so I don’t know exactly how it’s written but the gist is something like this (again, my interpretation and that oversimplified):

See what’s happening? In the parseTheTemplate() section of the code, however smarty actually calls it, my template plugins get run as part of parsing the template. If one of my plugins throws an exception it gets passed back up the call stack until it gets caught by a catch block. What gets skipped? Yep, ob_get_clean(). That means any output that’s already been printed is still in the output buffer.  On the subsequent call to $smarty->fetch() it gets returned in with the rest of the output.  In order to fix it, all I had to do was add a call to ob_clean() like this:

No more problems.  Smarty could have handled this on it’s own by manually clearing the output buffer at the beginning of fetch() before anything was parsed but I’m sure there is a reason why it isn’t.  Perhaps there are people putting things in the output buffer before calling fetch() on purpose.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: