views:

103

answers:

3

I have a layout with 4 separate "chunks". They are:

  • A nav panel with a menu and breadcrumbs. This is constructed using Zend_Navigation.
  • A sidebar, which shows general "news" by default
  • A content area, where the main output from each controller action would be placed
  • A header area, which is the above the navigation, which normally displays just some stock text and a photo.

The content area fits in with the traditional single view model that the documentation for Zend_Application states, but the other three do not. They all have reasonable default views to use, but a controller needs to be able to override them if needed. For example, it makes sense for an administration page to override the "newsy" view to display a log of recent administrative actions taken on the system.

The Zend_Layout/Zend_Application examples, however, all assume a single view (they call <?php echo $this->layout()->content; ?>.

How can one accomplish this kind of overriding of the layout? The only solution I've thought of would be to store overridden Zend_Views inside Zend_Registry, but that seems like holding things together with duct tape ;)

+3  A: 

Normally you would have a layout which contains your header area, nav panel, content area and sidebar. The content area would be loaded using the $this->layout()->content snippet you included, the other chunks could either be called in the main layout or included via. partials. If you go with the latter approach, your layout file might look something like this:

<html>
<head>
<title><?=$this->headTitle()?></title>
</head>

<body>

<?=$this->render('_header.phtml')?>

<?=$this->render('_navigation.phtml')?>

<?=$this->render('_sidebar.phtml')?>

<?=$this->layout()->content?>

</body>
</html>

If you find your partials are including more PHP code than HTML, you might consider writing a helper instead. So you might create a sidebar helper along these lines:

<?php
class My_View_Helper_Sidebar extends Zend_view_Helper_Abstract
{
    public function sidebar()
    {
        $html = '';

        // code to generate side bar here

        return $html;
    }
}
?>

and then instead of rendering a partial as I did above, you would call your sidebar helper:

<?=$this->sidebar()?>

It's fairly easy for controllers to override layouts, so you might create an admin layout that looks like my example above but without the sidebar, and perhaps with a different header. You'd then get your admin controller to use this instead of the default.

I can think of few situations where you would need to configure multiple Zend_View objects, which you implied you were considering.

Tim Fountain
+1 for the effort in this answer, but the problem with multiple layouts is that it forces me to duplicate a TON of things which are in the layout -- copy/pasted code is evil ;)
Billy ONeal
What kind of things would you need to duplicate? Normally these things can be centralised in partials, unless it's the render partial calls themselves you're talking about being duplicated.
Tim Fountain
@Tim: The things around where I want to dump in the content are relatively complicated -- all positioning and that kind of setup I want abstracted into the layout. Ideally the only thing controllers should need to provide are items specific to their function.
Billy ONeal
@Tim: Hmmm.. basically the layout is not like NAV, CONTENT, SIDEBAR, it's more like <200 lines of crap> NAV <<200 lines of crap> CONTENT <200 lines of crap> SIDEBAR <200 lines of crap.
Billy ONeal
+2  A: 

I believe what you are referring to are called "Named segments". Zend Framework's response object has support for these so called "named segments", and will allows you to segregate body content into different segments.

For example take the following layout file:

<div id="nav">
    <?php echo $this->layout()->nav ?>
</div>
<div id="content">
    <?php echo $this->layout()->content ?>
</div>

Here you have 2 named segments, being "content" and "nav". By default, output from your view scripts will be rendered to the "content" segment. In order to render output to the "nav" segment, you could do the following in a controller:

<?php

$response = $this->getResponse();
$response->insert('nav', $view->render('nav.phtml'));

?>

This feature gets particularly useful when used in conjunction with the ActionStack action helper. Take for instance that you are at an administration page, and you would like to override the "nav" section, then you could determine per controller which part you want render to that segment. The best way to explain this would be by a code example:

<?php

class PageController extends Zend_Controller_Action
{
    public function barAction()
    {
        // this would render the output of NavController::menuAction()
        // to the "nav" segment (note how we set the response segment in the
        // NavController in order to do this)
        $this->_helper->actionStack('menu', 'nav');
    }
}

class NavController extends Zend_Controller_Action
{
    public function menuAction()
    {
        $this->_helper->viewRenderer->setResponseSegment('nav'); 
        // do stuff
    }
}

?>
Andries Seutens
Some notes on the action stack: http://www.rmauger.co.uk/2009/03/why-the-zend-framework-actionstack-is-evil/ . If someone really wanted to go down the create-page-blocks route I'd suggest looking at the placeholder helpers, but really partials are easier unless you need the complexity.
Tim Fountain
I agree with the arguments made in the article you posted, the techniques outlined above are not perse ideal, but they answer the initial question.
Andries Seutens
+1, because this looks like exactly what I need to do. One question: Is it possible to set default responses somehow, (i.e. in the `Bootstrap`)? Only a small number of action controllers really need to change the default settings, and it seems shameful to duplicate all of that.
Billy ONeal
You could use a controller plugin to set the defaults. There is an example in the manual: http://framework.zend.com/manual/en/zend.controller.response.html#zend.controller.response.namedsegments
Andries Seutens
Ah - in that case I don't even need the "actionstack" that @Tim is ripping on here.
Billy ONeal
A: 

in $response->insert('nav',$view->render('nav.phtml') what does $view represents........

niriza
Huh? Did you mean that as a question?
Billy ONeal