The idea here is being able to route ked_core to a static template w/o executing any action or having defined one. For example, if you want your header to just be static HTML and want that in a separate header.tpl file, there’s no reason you should have to define controller->header() just to access that.
I went through several iterations on this code (some of it is in svn, though not all – I had to start the repository over b/c I forgot to add trunk/ branches/ and tags/
) so here is my approximate thought process.
Step 1: Check the .tpl exists
I should have had this check anyway, but before I did not realize the need – there needed to be an action defined for the view to be rendered anyway, so I focused on that, and that seemed to take care of itself. The following line would just throw an Exception
$reflector = new ReflectionMethod($this, $action);which would then just be handled and displayed nicely with ked’s exception handler.
But now I really do need to actually handle this on its own, since the action might not even exist – WHAT WILLS WAE DO THARN?!
The Wrong Way:
if (ked::$smarty->template_exists($tpl)) { ...
No. That is clearly a file system operation that will be run every single time, even though we know that 99% of the time, unless the developer is a total and complete dunce, the template is there. Then there’s the additional fs read to load the template.
The Right Way:
The right way looks sort of hackish, but bear with me. Smarty is really dumb about how $smarty->display() works. It never returns anything. If it fails to load the template, it prints a warning. Can’t really check to see if it succeeded in any robust way, so this will do:
...
ob_start();
ked::$smarty->display($tpl);
$viewContent = ob_get_contents();
ob_end_clean();
if (!$this->_skipAssign) ked::$smarty->clear_all_assign();
if (strpos($viewContent, "<b>Warning</b>: Smarty error: unable to read resource:")!==false)
throw new kedException('The view "'.$this->_view.'" was not found')
...
I just check for the presence of the warning and throw an exception if it’s there. Now, normally, I am not a fan of throwing exceptions, but in this spot it’s ok, and I’ll explain later why. The short explanation: because we’re not catching it.
Step 2: Make _execute Not Choke On Absent Action
The Wrong Way:
This is where I originally threw an exception, but a comment by David Hall made me pause.
...
try {
$reflector = new ReflectionMethod($this, $action);
} catch (Exception $e) {
$this->_skipAssign = true; //tell _render to skip smarty->assign related steps for this.
return false;
}
...
So here, we just try to create a ReflectionMethod as we normally would for the action, but if we see an exception, we just return; out of _execute and go on to _render. It’s sort of sloppy (because what if ReflectionMethod’s __construct() threw an exception for some other reason) and the real problem is this: we are creating and catching an exception in framework code. Every time an actionless view is called, this will happen. David pointed out that the exception is itself an object and can be quite heavy.
The Right Way:
This caused me to revert to my original intuition for how to do this:
...
if (!method_exists($this, $action)) {
$this->_skipAssign = true; //tell _render to skip smarty->assign related steps for this.
return false;
}
...
We just check if the method exists; if not, we set a variable that tells _render to forego assigning template variables that would have been set in the action had it existed and return false. (Right now, just returning would suffice, but I feel a disturbance in the force that tells me that this false value will come in handy somewhere down the line).
Step 3 (bonus): Make _render More Efficient
I alluded to this in an earlier code snippet: now that we can know for sure if an action has been executed, we can do this:
...
ob_start();
ked::$smarty->display($tpl);
$viewContent = ob_get_contents();
ob_end_clean();
if (!$this->_skipAssign) ked::$smarty->clear_all_assign();
if (strpos($viewContent, "<b>Warning</b>: Smarty error: unable to read resource:")!==false)
throw new kedException('The view "'.$this->_view.'" was not found');
$this->viewContent = $viewContent;
$this->_skipAssign = false; //reset this to make sure subsequent actions don't get tainted
...
We check the bool we set earlier and forego template var assignment if it’s set to true. Once again, the exception is fine because we’re not catching it, and this is meant for exceptional cases that are supposed to grind our application to a halt. Unlike the case in _execute, this wont’ be called every time there’s an actionless view – just in the case of an error.
What Did We Gain From All This?
Well, I listened to a bunch of really fucking good music on my ipod while doing this, so that’s good enough for me. But seriously, I also ordered like 6 Threadless shirts.
Also, as a side benefit, the framework now supports actionless views without any extra file system operations. It is also wise enough not to bother assigning template variables if the template is static. You also don’t have to define anything special in the controller (in fact, nothing at all is needed there) or in the template to accomplish it – just put the static .tpl file in the right folder under inc/tpl/views.
I also used the word “tainted” in the comments… which is pretty close to “taint”… which is the area between the ass and the balls. That’s a wrap on the day.
You can move stuff around using
svn mv ...in a Subversion repository.Arrgh. I wish I had researched the problem more than just asking dhall
Yeah… I don’t know what I was smoking that night that I forgot subversion has mv…. And note I never said you had to delete your repository, I just gave you a really stupid way of avoiding it.