Deprecated Behaviour

The inane, sometimes insane, ramblings from the mind of Brenton Alker.

Extending Zend Framework's ViewRenderer Helper to Allow Application and Controller Page Layouts

I intend to do a write-up of my experiences choosing a PHP framework, but for now, I just thought I would share this.

One of the out-of-the-box features that the Zend Framework is missing is “layouts”. These are the templates that contain the elements that are present on every (or at least almost every) page of the application. Things like headers, main menu’s, footers etc.

I looked at a number of different ways to implement this in Zend, the most obvious idea involves adding header and footer calls (possible a helper) in every view. While this will work, it is a lot of duplicated code.

Maugrim the Reaper’s blog contains a fairly comprehensive investigation (in 6 parts so far, the 7th hopefully coming soon) into “Complex Views”. Unfortunately, there is not (as yet) any solid implementation of most of the concepts available there, but it does provide much to think about.

The most elegant implementation (IMHO) of a layout system to meet my needs that I have found (and believe me I’ve looked) comes from Akra’s Devnotes and involves extending the ViewRenderer Action Helper to render the layout template.

This works very similarly to my initial stab at an implementation, but does so in a much cleaner way (I initially had separate top and bottom templates, instead of the call to render the content in the layout). However, it does lack one feature that my implementation had, it only allows application level layouts. I also implemented controller level layouts; A 3-level view structure.

This allows the controllers to contain their own structure within the applications main structure. I extended Rob’s class (as in added to, not in the OO sense) to incorporate this 3-level view structure.

My current code looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

    
    
    <?php
    class Custom_Controller_Action_Helper_ViewRenderer extends Zend_Controller_Action_Helper_ViewRenderer
    {
        /**
         * Name of layout script to render. Defaults to 'layout'.
         *
         * @var string
         */
        protected $_layoutScript = 'layout.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 controller script name to view
            $this->view->controllerScript = $this->_translateSpec(':controller/' . $this->_layoutScript);
            
            // 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();
        }
    }
    

As an example of its use:

The Application Layout (views/scripts/layout.tpl.php) looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title><?php echo (isset($this->title)) ? $this->escape($this->title) : 'Untitled'; ?></title>
</head>
    <body>
        <?php echo $this->render($this->controllerScript); ?>
    </body>
</html>

The Controller (for the default “index” controller) layout (views/scripts/index/layout.tpl.php) looks like this:

1
2
3
4
5
6


<h1>Default Controller</h1>
<h2><?php echo $this->escape($this->title); ?></h2>
<?php echo $this->render($this->actionScript); ?>

And that of course leaves the action’s view to only display the items specific to that action.

UPDATE: This code is broken. During some debugging, I discovered that this method of calling the render function from the view causes the action function to be run twice. It also prevents the action function (or view) from manipulating variables that can be used by the layout script, so preventing things like the action requesting javascript or stylesheet includes in the html head section.

I have re-worked the script to overcome these problems, and will post it soon.