Article
Learn symfony: a Beginner's Tutorial
Refactoring
You probably noticed that both the photoSuccess.php and the indexSuccess.php templates contain the same portion of code that's dedicated to displaying a photo detail. If you're aware of the Don't Repeat Yourself (D.R.Y.) principle, which is one of the pillars of agile programming, you'll know that this means the code has to be refactored.
What we'll do is move the common code into another script, and include that script in the two templates. At the same time, we'll detail the tags a little bit, so that users can click on each tag to display the list of pictures that have this tag. So, create a file called _photo_description.php in the public/templates/ directory, containing the following code:
// in apps/frontend/modules/public/templates/_photo_description.php
"<?php echo $photo->getDescription() ?>"
published on <?php echo $photo->getCreatedAt('d/m') ?>,
tagged
<?php foreach($photo->getTags() as $tag): ?>
<?php $tag=$tag->getName(); echo link_to($tag, 'public/tag?tag='.$tag) ?>
<?php endforeach; ?>
Here, we see again the link_to() helper (this time, pointing to a public/tag action with a tag parameter), and we use properties of the $photo object. But, wait -- in order for this script to access the symfony helpers and the template variables, it must be smarter than a normal included script. That's why we won't use a simple include() to call it in indexSuccess.php and photoSuccess.php; instead, we'll use an include_partial() as follows:
// in apps/frontend/modules/public/templates/photoSuccess.php
<div id="main">
<?php echo link_to('back to the photo list', 'public/index',
'style=display:block;float:right;') ?>
<h1>Picture details</h1>
<a href="/uploads/<?php echo $photo->getFilePath() ?>" title="click for the full-size version">
<?php echo image_tag('/uploads/'.$photo->getFilePath(), 'width=100%') ?>
</a><br/>
<p>
<?php echo include_partial('photo_description', array(
'photo' => $photo
)) ?>
</p>
<div id="footer">
powered by <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>
</div>
</div>
Symfony calls this type of code fragment a partial. Partial names start with an underscore so that they can be distinguished clearly in the templates/ folders. Partials need to be explicitly passed the variables that they can access (to preserve encapsulation). The first parameter of the include_partial() call is a partial name, which is the partial file name without the leading underscore and the trailing .php. You now understand why the _photo.php script of the photo module was called a partial column when we were dealing with the generated administration: the column was just including a partial.
D.R.Y. extremists would probably say that the two templates still have some code in common -- namely the main <div id="main"> surrounding the content and the footer. That's quite true, and in fact this code should not be part of the template -- it should be part of the layout. In the templates we wrote, there was no mention of <html> or <head> tags, yet they're present in the response. That's because the code resulting from each template execution is inserted into another template, called the global template or the layout. This is the container for the templates, or in design pattern language, the "decorator". The layout is a good way to store global navigation, site header and footer, sidebars, and so on. Have a look at the default layout in apps/frontend/templates/layout.php. We will modify it slightly to include the <div id="main"> around the content and to add the footer div:
// in apps/frontend/templates/layout.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<?php echo include_http_metas() ?>
<?php echo include_metas() ?>
<?php echo include_title() ?>
<link rel="shortcut icon" href="/favicon.ico" />
</head>
<body>
<div id="main">
<?php echo $sf_data->getRaw('sf_content') ?>
<div id="footer">
powered by <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>
</div>
</div>
</body>
</html>
The result of the template execution is inserted in the $sf_data->getRaw() line; alternatively, we could also say that the layout "wraps around" the template code. Now you can remove the <div id="main"></div> and the <div id="footer"></div> from the two templates. And while we're modifying the templates, we can think about the public/tag page, which is supposed to show a list of pictures that have a given tag. A list of pictures? But that's exactly what the public/index action shows! So instead of creating a new template, we'll reuse the existing indexSuccess.php for the tag action, as shown here:
// in apps/frontend/modules/public/actions/actions.class.php
public function executeTag()
{
$this->forward404Unless($tag = $this->getRequestParameter('tag'));
$c = new Criteria();
$c->addJoin(PhotoPeer::ID, TagPeer::PHOTO_ID);
$c->add(TagPeer::NAME, $tag);
$this->photos = PhotoPeer::doSelect($c);
$this->setTemplate('Index');
}
The first line of this code is a condensed version of the request parameter verification and variable initialization. It should remind you of the executePhoto() method. Then, we build a new Criteria, in order to retrieve all the Photo objects that are linked to a Tag object having the same name as the request parameter. This is equivalent to the following SQL query:
SELECT * FROM photo, tag WHERE tag.NAME='$tag' AND photo.ID=tag.PHOTO_ID;
The Criteria syntax may still look a bit odd, but after a couple more tries, you'll find it very natural. Anyway, the important part in this action is the last statement: the setTemplate() method tells symfony to use the indexSuccess.php template instead of the default template for this action (tagSuccess.php). We now have to modify the indexAction.php to handle the tag action. We'll also remove the code that was refactored to the layout:
// in apps/frontend/modules/public/templates/indexSuccess.php
<?php if($tag = $sf_params->get('tag')): ?>
<?php echo link_to('back to the photo list', 'public/index', 'style=display:block;float:right;') ?>
<?php endif; ?>
<h1>
My pictures
<?php if($tag): ?>
tagged "<?php echo $tag ?>"
<?php endif; ?>
</h1>
<?php foreach($photos as $photo): ?>
<div class="photo">
<?php echo link_to(
image_tag('/uploads/thumbnail/'.$photo->getFilePath()),
'public/photo?id='.$photo->getId(),
'class=image title='.$photo->getDescription()
) ?>
<?php echo include_partial('photo_description', array(
'photo' => $photo
)) ?>
</div>
<?php endforeach; ?>
In the action class, we have to use the getRequestParameter() method to retrieve a request parameter. In the template, the equivalent is $sf_params->get(). So the public/tag action now works fine -- it even displays a link to the index action.

Don't forget to remove the code that was refactored to the layout in the public/photo template as well. Agile development means a lot of code refactoring, and symfony offers you many tools (including layouts and partials) to do it right. In general, developing an application with symfony gives you the assurance that your code can change easily, even if you want to add a feature that was not designed in the first place.