Contrary to what my tutorial says, I'm one of those people that doesn't want to have to render my common header and footer templates in every single view script file. I prefer a standard site-wide layout script file that will display the content of the action script files within it.
The Zend_Controller_Action_Helper_ViewRenderer action helper is a great bit of code that automates rendering a view template based on which action has been called. This is very useful, but renders the action template, not my layout template. To solve this, I am experimenting with extending the Zend_Controller_Action_Helper_ViewRenderer and overriding it so that it know about my layout template. I also prefer to use the view suffix "tpl.php" for my view scripts, so I've made my class automatically set my preferred view suffix.
On to the code...
The master site template is called site.tpl.php and lives in the views/scripts/ directory. A simplistic breakdown of it looks like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<link rel="stylesheet" href="< ?php echo $this->baseUrl; ?>/css/site.css"
type="text/css" media="screen" />
$this->actionScript is the script associated with the current action which is automatically determined by Zend_Controller_Action_Helper_ViewRenderer, so in the case of the index action with the index controller, the action script is views/scripts/index/index.tpl.php.
As Zend_Controller_Action_Helper_ViewRenderer doesn't know about site.tpl.php and we want to render that instead, we extend like this:
<?php
class Controller_Action_Helper_ViewRenderer
extends Zend_Controller_Action_Helper_ViewRenderer
{
/**
* Name of layout script to render. Defaults to 'site.tpl.php'.
*
* @var string
*/
protected $_layoutScript = 'site.tpl.php';
/**
* Constructor
*
* Set the viewSuffix to "tpl.php" unless a viewSuffix option is
* provided in the $options parameter.
*
* @param Zend_View_Interface $view
* @param array $options
* @return void
*/
public function __construct(Zend_View_Interface $view = null,
array $options = array())
{
if (!isset($options['viewSuffix'])) {
$options['viewSuffix'] = 'tpl.php';
}
parent::__construct($view, $options);
}
/**
* Set the layout script to be rendered.
*
* @param string $script
*/
public function setLayoutScript($script)
{
$this->_layoutScript = $script;
}
/**
* Retreive the name of the layout script to be rendered.
*
* @return string
*/
public function getLayoutScript()
{
return $this->_layoutScript;
}
/**
* Render the action script and assign the the view for use
* in the layout script. Render the layout script and append
* to the Response's body.
*
* @param string $script
* @param string $name
*/
public function renderScript($script, $name = null)
{
$this->view->baseUrl = $this->_request->getBaseUrl();
if (null === $name) {
$name = $this->getResponseSegment();
}
// assign action script name to view.
$this->view->actionScript = $script;
// render layout script and append to Response's body
$layoutScript = $this->getLayoutScript();
$layoutContent = $this->view->render($layoutScript);
$this->getResponse()->appendBody($layoutContent, $name);
$this->setNoRender();
}
}
All we need to do now is modify our bootstrap so that the new ViewRenderer is used rather than the default one. I do this in index.php before the first instantiation of the front controller:
<?php
// Use our ViewRenderer action helper
$viewRenderer = new Controller_Action_Helper_ViewRenderer();
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
That's all there is to it. My action scripts now just contain the HTML specific to the action and my layout template is automatically rendered with the action's HTML in the right place!
Obviously this is my first cut on this and so is fairly rough at the edges. Thoughts and improvements always welcome!



June 4th, 2007 at 21:37 #
I am in specific cotroler which need's to use specific template.. how to call
->setLayoutScript('mynew.tpl.php') ?
June 5th, 2007 at 07:20 #
Hi Vaidas,
From within your controller, you can do:
< ?php
$viewRenderer = $this->getHelper('ViewRenderer');
$viewRenderer->setLayoutScript('newlayout.tpl.php');
Regards,
Rob...
June 5th, 2007 at 10:14 #
Funny enough, it seems that we want to blog about the same topics all the time.
Good thing I checked your blog before starting my own article ;).
June 6th, 2007 at 06:00 #
Andries,
You'll now have to post a blog entry about where I've gone wrong!
Regards,
Rob...
June 6th, 2007 at 12:55 #
Cool stuff, Rob. Coming from a Java background, I was looking for something similar to the tiling that you can do in frameworks such as Struts. What you've posted should allow me to do what I want to do, so thanks!
Regards,
William
June 6th, 2007 at 20:57 #
I like this!
However, in a conventional modular layout, whilst it seems to work for the default module, for any other it hits: Fatal error: Uncaught exception 'Zend_View_Exception' with message 'script 'site.phtml' not found in path' in /home/zf/src/ZendFramework-20070604-5096/library/Zend/View/Abstract.php:853 Stack trace: #0
Not sure how I should deal with that. As a bodge, symlinking to the site.phtml from the module's scripts dir fixes it.
Any idea how I could properly fix the script path from the right place for all cases?
Btw. I used .phtml only to be consistent with the manual. I hope ZF makes up it's mind on recommended file extensions (I've commented on it in the draft coding standard).
June 11th, 2007 at 09:51 #
Hello Rob,
I was hoping that I could get some info about your menu view helper.
I imagine that it outputs html to create your menu. What is the benefit of using a view helper as opposed to doing something like render(menu.phtml)? Would you mind sharing your code for that helper?
Thanks,
Mark
June 11th, 2007 at 23:32 #
Hi,
Just to follow up on my June 6th post, I needed to addScriptPath() early in the bootstrap to ensure it would be at the back of the script path stack.
It's also no sweat to turn off your extended functionality in the viewRenderer with a simple method like say setNoLayout() and call it from an action method in the controller with say:
Zend_Controller_Action_HelperBroker::getExistingHelper('viewRenderer')->setNoLayout();
Useful when an action doesn't want it's output wrapped up in HTML, like maybe batch work or image serving.
June 13th, 2007 at 05:24 #
Hi Mark,
$this->menu() is hypothetical at the moment as the one I actually have is written in Smarty. The Smarty one takes an array and turns it into a nested unsigned list. CSS then does the magic to make it look like a menu.
Regards,
Rob...
June 14th, 2007 at 10:18 #
Hi Rob,
great guide thx!
quick question... is it possible to have the layout file/s in a dir located on the main level (ex. /templates)?
the idea is to keep the script views in the standard ZF directories and have a separate top level /template dir including only templating layouts
do you think is possbile?
thanks in advance
Giulio
June 14th, 2007 at 14:43 #
Giulio,
try doing something like this:
$layoutContent = $this->view->render('layouts/'.$layoutScript);
take a look at the values in $this->view->getScriptPaths() to make any further adjustments accordingly...
Clayton
June 14th, 2007 at 19:59 #
Guilio,
I think Clayton has beat me too it!
You could try something like:
$layoutContent = $this->view->render('../../templates/'.$layoutScript);
Regards,
Rob...
June 15th, 2007 at 09:41 #
thanks for the replies
i solved in another way: inside the class Controller_Action_Helper_ViewRenderer i added $this->view->addScriptPath('./templates/'); inside the function renderScript()
seems to work fine
June 19th, 2007 at 15:21 #
Very nice indeed but what if you want to use some AJAX?
For example, I have a registration page with an ajax request to see if a username is still free
my call would be to /register/checkusername
Lets say I just want to output a json with "free" or "already registered"
do you need to create another bootstrap for this or is there a better solution?
thanks!
June 20th, 2007 at 10:37 #
I've been doing some further reading and found this to work but it just doesn't seem right..
$viewRenderer = $this->getHelper(‘ViewRenderer’);
$viewRenderer->setLayoutScript(‘empty.tpl.php’);
$this->getResponse()->setHeader('Content-Type', 'text/xml');
$this->getResponse()->setBody("render();
is there a better solution?
June 20th, 2007 at 10:39 #
I don't want to spam your blog but something went wrong with the code:
$this->getResponse()->setBody('some xml ...');
$this->render();
June 20th, 2007 at 17:23 #
M.
To be honest, I haven't a clue.
Sorry,
Rob...
June 20th, 2007 at 20:34 #
By implementing your approch I get 2 resulting outputs ; the first one - as expected is the general template corresponding to the site.tpl.php, but right after that I get a second one corresponding to the invoqued action-script (let's say index.phtml).
The cause is that somehow both viewRenderers live in the helpBroker; the extended one we create but also the default one which is dispatched right after the first one.
The question now is, how do I kill the default one without harming my own renderer? *Obviously setting a custom view doesn't prevent the instanciation of the default one, as opposite to what Brady says here http://blog.astrumfutura.com/archives/290-Having-a-bad-ViewRenderer-day-in-your-ZF-app.html*
June 20th, 2007 at 20:52 #
My mistake guys,
The problem was that my own class had a different name - it was ..._MyViewRenderer and not ..._ViewRenderer. That's way there were two different helpers in the broker.
Sorry
June 23rd, 2007 at 13:50 #
How can I use your approach together with smarty?
I used to $this->view->subtemplate = ('file.tmp') and than inside the template I called it with {incluce file=$subtemplate}
June 23rd, 2007 at 19:55 #
Daniel,
Sorry, not sure. Would have to look into it. At the moment, we are on the fence as to whether to use Smarty in our ZF apps or not.
Regards,
Rob...
June 23rd, 2007 at 23:14 #
i have it working for now - but only via the full absolute path including in every action, that cannot be the right thing.
I finally ended up with a ZF-conform layout structure for modules - having a "modulename"/views/scripts/"controller"/action file-structure.... -
I would rather have a kind structure like a views directory where I have the same strucute like the web, where i can style all the necessary templates and call them with a simple makeSubtemplate.....or whatever.
Not even thinking about things like a global menu or some modules that only need to be accessed by a few sites
July 10th, 2007 at 19:24 #
I'm new to all this and confused...
Where do I save the class file: Controller_Action_Helper_ViewRenderer
??
Thanks
Simon
July 11th, 2007 at 05:16 #
Simon,
in Controller/Action/Helper/ViewRenderer.php.
Usually, you would prefix with a "vendor" name, such as Akra. In which case the full class is Akra_Controller_Action_Helper_ViewRenderer which lives in lib/Akra/Controller/Action/Helper/ViewRenderer.php.
Regards,
Rob...
July 19th, 2007 at 13:05 #
what if i have some viewHelpers that need javaScript. How can I incle that in "site.tpl.php"?.
Regards..
July 20th, 2007 at 17:54 #
Thanks so much for this example, it was just what I was looking for! Works like a charm.
October 1st, 2007 at 13:54 #
thanx Akrabat, you're the one ^^
November 9th, 2007 at 11:48 #
This is great, and exactly what I was after, but I've un into a small problem.
The Helper is throwing up the following error:
Controller_Action_Helper_ViewRenderer::$_request in /var/www/proficcymod/lib/LLGC/Controller/Action/Helper/ViewRenderer.php on line 64
PHP Fatal error: Call to a member function getBaseUrl() on a non-object in /var/www/proficcymod/lib/LLGC/Controller/Action/Helper/ViewRenderer.php on line 64
November 12th, 2007 at 19:17 #
I just removed that line and it worked just fine ;)
November 17th, 2007 at 17:26 #
Regarding M's question (15-17), why not just call, from the controller,
$this->viewRenderer->setLayoutScript(false);
and then in your implementation of renderScript, do this first:
if(!$this->getLayoutScript) {
parent::renderScript($script, $name);
}
It seems that would be the easiest way of disabling the layout script functionality completely.
November 30th, 2007 at 04:31 #
Hey, I just started working with ZF.. So far everything is okay... Except, I really don't like the fact that if I work with modules My views have to be in "modules/moduleName/views/scripts/:controller/:action" .
I was wondering if it was actually possible to make it this way:
"application/views/moduleName/:controller/:action"
It would mean a lot if you could help,
Angel
July 3rd, 2008 at 10:50 #
@Dan: You can replace the line with the following:
$this->view->baseUrl = $this->getRequest()->getBaseUrl();