Using Content Decorators in the Yii Framework

April 3, 2013
The Yii Book If you like my writing on the Yii framework, you'll love "The Yii Book"!

Content decorators are a less heralded but interesting feature of the Yii framework. Content decorators allow you to hijack the view rendering process and add some additional stuff around the view file being rendered. In this excerpt from Chapter 7, “Working with Controllers,” of “The Yii Book“, I’ll explain what content decorators are and how you use them. (This does assume you have some familiarity with Yii and how it renders the complete layout.)

This is an excerpt from Chapter 6, “Working with Views,” of “The Yii Book“.

Content decorators are created by invoking the controller’s beginContent() and endContent() methods. The beginContent() method takes as an argument the view file into which this content should be inserted (i.e., treat this output as the value of $content in that view):

<!--?php $this--->beginContent('//directory/file.php'); ?>
// Content.
<!--?php $this--->endContent(); ?>

This feature is best used for handling more complex embedded or nested layouts, although it can be used in other ways, too (there’s an interesting example in Alexander Makarov’s book).

For example, say you wanted some pages in your site to use the full page width for the content:

The page-specific content goes across the entire width of the browser.

The page-specific content goes across the entire width of the browser.

But some pages should have a sidebar:

The page-specific content shares the browser width with a sidebar.

The page-specific content shares the browser width with a sidebar.

You could create two different layout files and use each accordingly. However, most of the common elements would then be unnecessarily repeated in two separate files, making maintenance a bit harder. The solution is to decorate the page specific content with the sidebar on those pages that ought to have it. Rather than showing you new code that illustrates this point, I’ll recommend that you turn to the files generated by yiic, which does this very same thing.

More recent versions of Yii will automatically create three layout files when you create a new Web app:

  • views/layouts/column1.php
  • views/layouts/column2.php
  • views/layouts/main.php

Although all three are layout files, you don’t have three different template files. The main.php file still creates the DOCTYPE and HTML and HEAD and so forth. The column1.php and column2.php files are decorators that create variations on how the page-specific content gets rendered. Here is the entirety of column1.php:

<!--?php $this--->beginContent('//layouts/main'); ?></pre>
<div id="content"> </div>
<pre>
<!--?php $this--->endContent(); ?>

Again, you have the magic echo $content line there, but all column1.php does is wrap the page-specific content in a DIV.

The column2.php file starts off the same, but adds another DIV (which includes some widgets) before $this->endContent():

<!--?php $this--->beginContent('//layouts/main'); ?></pre>
<div class="span-19">
<div id="content"> </div>
<!-- content --></div>
<div class="span-5 last">
<div id="sidebar"><!--?php $this--->beginWidget('zii.widgets.CPortlet',
 array('title'=>'Operations'));
$this->widget('zii.widgets.CMenu', array(
 'items'=>$this->menu,
 'htmlOptions'=>array('class'=>'operations'),
));
$this->endWidget();
?></div>
<!-- sidebar --></div>
<pre>
<!--?php $this--->endContent(); ?>

To be absolutely clear on what’s happening, the protected/components/Controller.php class sets column1.php as the default layout file. When, say, the actionIndex() method of the SiteController class is invoked, it renders the protected/views/site/index.php file. The rendered result from index.php will be passed to the layout file, column1.php. This means the $content variable in column1.php represents the rendered result from index.php.

Then, because column1.php uses beginContent() the rendered result from column1.php will be passed to the main.php layout file (because it’s provided as an argument to beginContent()). This means the $content variable in main.php represents the rendered result from *column1.php.

Personally I think this default layout approach is a bit complicated for the Yii newbie (yiibie?), but it is invaluable in situations where the content around the page-specific content needs to be adjusted dynamically.