Working with Layouts in Yii

May 16, 2012
The Yii Book If you like my writing on the Yii framework, you'll love "The Yii Book"!

Using the Model-View-Controller (MVC) design pattern, the look of a Yii-based site is naturally controlled by the View files. These files are a combination of HTML and PHP that help to create the desired output. Specific pages in a site will use specific View files. In fact, the View files are designed to be broken down quite atomically, such that, for example, the form used to both create and edit an employee record is its own file, and that file can be included by both create.php and update.php. As with most things in OOP, implementing atomic, decoupled functionality goes a long way towards improving reusability. But the individual View files are only part of the equation for rendering a Web page. Individual view files get rendered within a layout file. And although I’ve mentioned layouts a time or two in my writings on Yii, it’s a subject that deserves its own post.

To be clear, layouts are a type of View file. Specifically, whereas other View files get placed within a directory for the corresponding Controller (i.e., the SiteController pulls from views/site), layout files go within views/layouts. But while the other View files are associated with individual Controllers (and therefore, individual pages), layouts are communal, shared by all the pages. Simply put, a layout file is the parent wrapper for the entire site’s templating system. I’ll explain…

The Premise of Templates

When you begin creating dynamic Web sites using PHP, you’ll quickly recognize that many parts of an HTML page will be repeated throughout the site. For example, the opening and closing HTML and BODY tags. Even the site you’re looking at now has repeating elements: the header, the navigation, the footer, etc. To create a template system, you would pull all of those common elements out and put them into one (or more) separate files. Then each specific page can include these files around the page-specific content:

include('header.html');
// Add page-specific content.
include('footer.html'); 

This is the approach I would use on non-framework-based sites. It’s easy to generate and maintain. If you need to change the header for the entire site, you only need to edit the one file.

Templates in Yii

When using a framework, you’ll probably end up with a “bootstrap” approach, where all of the pages are accessed through one file. In the case of Yii, that bootstrap file is index.php. Part of its job is to manufacture the necessary HTML for the requested resource. Whereas the non-framework approach pulls the template files into the page-specific file, Yii pulls all the files from includes and assembles them together. Of these files, the layout files constitute all the common elements; everything that’s not page-specific. If you look at a Yii-generated site, you’ll see that views/layouts/main.php begins with the DOCTYPE and opening HTML tag, then has the HTML HEAD and all its jazz, then starts the BODY, and finally has the footer material and the closing tags. In the middle of the body of the code, you’ll see this line:

<?php echo $content; ?>

This is a magic line as it pulls in the page-specific content. If the site you’re looking at now used Yii, the value of $content would be all the HTML that makes up this post you’re reading. For a Yii example, when the user is looking at site/login, the SiteController‘s actionLogin() method will be called. That method will render the views/site/login.php View page, pulling that file’s contents into the main layout file at that echo $content location. That’s what’s going on behind the scenes.

So here, then, is the first key concept: if you want to change the general look of your Web site, edit the layout file (views/layouts/main.php). If you were to take your HTML mockup for your site, drop in the echo $content; line at the right place, and save it as views/layout/main.php, you will have created a custom look for your Web app. That is the basic principle and it’s essentially that simple.

To take layouts a step further, many sites will use variations on a theme. For example, the first Yii-based site I created used one header for the home page and another header for other pages, with all the remaining common elements being consistent. Or, I worked on an e-commerce project where one category of products used layout A and another category used layout B. If you have this need, there are a couple of ways of going about it.

Creating a Second Layout

The first and most obvious route is to create a second layout file. In the example where the site’s home page used a variation on the template, I created views/layouts/home.php. It was based upon main.php, with the necessary edits. To dynamically switch layouts, I changed the value assigned to the layout property within the proper Controller method:

// protected/controllers/SiteController.php
public function actionIndex() {
    $this->layout = 'home';

And that’s all there is to it. When views/site/index.php would be rendered, it’d use the views/layouts/home.php template.

Although this approach is easy to understand and implement, it has a downside: a LOT of HTML is being repeated between the two layout files. If I needed to change the navigation, I had to edit both files in the same way. This leads us to option B.

Hijacking the Content

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 simply create variations on how the page-specific content gets rendered. These files do so by hijacking the layout process. For example, here is the entirety of column1.php:

<?php $this->beginContent('//layouts/main'); ?>
<div id="content"><?php echo $content; ?></div>
<?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'); ?>
<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>
<?php $this->endContent(); ?>

The trick here is the call to beginContent(). Remember how the layout file echoes the page-specific content in the correct place? Well, this call to beginContent() says that we’re about to start rendering the content. The method is provided with the primary layout file to use as the parent (i.e., the layout that encapsulates this content). All that’s happened here is that the content has been hijacked and replaced with slightly modified content. So the content in views/site/login.php gets pulled into column.php, where it’s wrapped within other content, and the combination of that content gets passed to main.php.

There are two tricks to this: first, beginContent() must point to //layouts/main.php. (The // just says to start in the Views directory.) Second, the layout that the Controller thinks it’s using is column1.php, not main.php. I personally think this approach is a bit complicated, particularly for the Yii newbie (yiibie?), but it is useful in situations where the content around the page-specific content needs to be adjusted dynamically.

If you were to open components/Controller.php, you would see this line:

public $layout='//layouts/column1';

That one line says that all Controllers (which inherit from Controller) will use column1.php as its layout. If you were to change this one line to column2, then all Controllers would use that as the default layout. If you were to change this one line to main, then all Controllers would use main.php as the default layout, without the intermediary column layouts.

Again, this is a bit more convoluted than is healthy for newbies, but layouts are just wrappers to page-specific content, whether you use a single layout or intermediary layouts such as column1 and column2. If you’d like a visual representation, there’s a useful image provided among the comments in this article.

Quick Guide

To summarize what’s been covered here…

To change the entire look for the entire site, edit layouts/main.php, but be sure to use the echo $content line where appropriate.

To change the default layout for every Controller, edit this line in components/Controller.php:

public $layout='//layouts/column1';

To change the default layout for every View in an individual Controller, add this line to that Controller’s definition:

class SiteController extends Controller {
public $layout = 'column2';

To change the layout used for a single action, add this line to the corresponding action:

// protected/controllers/SiteController.php
public function actionIndex() {
    $this->layout = 'home';

And remember that column1.php and column2.php just hijack the page-specific content before it gets passed on to the main.php layout file.

I hope this post has been helpful and let me know if you have any questions or problems with layouts in Yii. (And by the way, if you like this post, I’m going to be writing a book on Yii later this summer. Subscribe to my newsletter or follow me on Twitter to stay tuned!)