Article
Learn symfony: a Beginner's Tutorial
Adding Pages
That's it for the administration interface. Now we can focus on the end-user pages. This time, the pages will not be created by a generator -- we'll code them by hand so that you understand how to add pages to your application.
First of all, we'll group the end user pages into a public module. The symfony command line offers a useful task to initiate the files and directories of an empty module:
$ php symfony init-module frontend public
This creates a new public/ directory in apps/frontend/modules/, with the following subdirectories:
actions/
config/
lib/
template/
validate/
The file structure follows the code separation proposed by the MVC paradigm. In actions/, you will find the code that deals with handling requests -- the controller code. templates/ contains the code dedicated to presentation -- the view code. The config/ directory is meant to contain configuration files, but it is empty when modules are initially created. You can put the classes that you want to use in this module into the lib/ directory (placing them there will provide the benefit of autoloading). Finally, the validate/ directory is for form validation files, which we won't cover in this tutorial.
How do we add a new page? A page in symfony is made up of two parts: an action and a template. The action code is executed before the template code. In fact, the action prepares data for the template. The template uses very little PHP code, and stays maintainable since it is used strictly for presentation, not application logic. An action is a method that's placed in the module actions.class.php with a name prefixed by 'execute.' A template is a file with the same name as the action, but with the suffix 'success.' Let's see how it all works together with an example. Let's add a page that will display thumbnails of all the uploaded pictures, ordered by date.
Open the apps/frontend/modules/public/actions/actions.class.php file. It contains an empty index action (the method called executeIndex()).The executeIndex() method is called when a user requests the URL public/index (the full URL, with the current configuration, would be http://localhost/sf_sandbox/web/frontend_dev.php/public/index).
At the moment, this action shows a default welcome page, so replace it with the following action:
// in apps/frontend/actions/actions.class.php
public function executeIndex()
{
$c = new Criteria();
$c->addDescendingOrderByColumn(PhotoPeer::CREATED_AT);
$this->photos = PhotoPeer::doSelect($c);
}
The Criteria object probably reminds you of the deleteTags() method we saw earlier in the model. Indeed, it is the same object that model uses to build a database query. These three lines are synonymous with the following SQL code:
SELECT * FROM photo ORDER BY created_at;
Here, the ORDER BY clause determines what we pass to the addDescendingByColumn method. The name of the class that's assigned the result of the function doSelect comes from the FROM clause. You may think that replacing the SQL with something else is a waste of time, because it forces you to learn a new syntax. But this assumption is incorrect for two reasons.
First, not writing actual SQL code protects your application from SQL injection attacks and keeps your code database-independent. If you ever choose to move the application from SQLite to an Oracle database, all you'll need is a parameter in a configuration file, despite the syntactic differences between the two databases. Second, the doSelect() call does much more than send a query to the database -- it actually creates Photo objects based on the resultset. That's right, the content of $this->photo is an array of objects of class Photo. You can use all the methods we defined previously, as well as the generated methods, on these objects. That's the greatest benefit of an ORM.
executeIndex does more than query the database and build objects based on the resultset. It actually passes the $photo array to the template. That's the purpose of the $this-> call in an action. Let's see how we can retrieve this data in the template.
When the action finishes, symfony looks for a template for this action. The template's name should comprise the action name suffixed with the action termination status. The default action termination is a 'Success', so symfony looks for an indexSuccess.php file located in the module templates/ directory. It exists, but it's empty, so edit it as follows:
// in apps/frontend/modules/public/templates/indexSuccess.php
<div id="main">
<h1>My pictures</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 $photo->getDescription() ?>"
on <?php echo $photo->getCreatedAt('d/m') ?>,
tagged <?php echo $photo->getTagsString() ?>
</div>
<?php endforeach; ?>
<div id="footer">
powered by <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>
</div>
</div>
Now you see how to use the data prepared in the action: the template iterates through the $photos array with a foreach, and calls methods of the Photo class on each of the $photo objects. You already know the image_tag() helper, so it's time to meet the link_to() helper, which outputs a hyperlink to another action. It expects at least two parameters: the bearer of the link (here, an <img> tag), and the target of the link, expressed as an internal URI. An internal URI is the combination of a module name and an action name (separated by a slash) and a set of parameters, written in the same way as normal URLs. The third (optional) parameter to the link_to() helper and most other helpers is a string of additional tag attributes, in old HTML 4.0 style. But don't worry -- all the HTML code output by symfony helpers is XHTML-compliant. And that's actually the best way to understand how helpers work: by looking at their output.
The output of the first link_to() of the template is as follows:
<a class="image" title="title" href="/frontend_dev.php/public/photo/id/3">
<img src="/uploads/thumbnail/68eda1eaf5a8eac4d4529fa85298c7de.jpg" />
</a>
Notice that the template uses the alternative PHP syntax (<?php foreach(): ?><?php endforeach; ?>) instead of the classical one (<?php foreach() {} ?>). This helps to prevent the mixing of PHP code with HTML code, and to keep the template readable by non-developers. If you end up writing curly braces or echoing HTML in a template, it's generally a good sign that you should refactor your code and move part of it to the action.
The result of the overall template code is that the public/index action displays a list of all thumbnails, each thumbnail bearing a link to a public/photo action (which is yet to be written -- patience!). But before we take a look at it, we should give it some style. We designed a simple Cascading Style Sheet for this tutorial. We won't reproduce it in this article, but you can download it here. You should place it in the project's web/css/ directory so that symfony to find it. And of course, you must also tell symfony to include the relevant <link> tag in the response. The Response object has a method just for that: it's called addStylesheet. The Actions object has direct access to the Response object, thanks to its getResponse method. However, since we need to use the stylesheet on every page, not just the one for this specific action, we'll call it in a preExecute method in the action. This method is called before every action of the module, and is very convenient for avoiding code repetition. So add this method to the actions.class.php:
// in apps/frontend/actions/actions.class.php
public function preExecute()
{
$this->getResponse()->addStylesheet('frontend');
}
That's it; the photo list is ready to be seen. Make a request to the URL http://localhost/sf_sandbox/web/frontend_dev.php/public/index.

So creating pages in symfony is as easy as creating one action (for the controller code) and one template (for the presentation code). To make sure you understand the idea, let's create a new page -- the photo detail page. It will be accessible via the internal URI public/photo, so its action should be called executePhoto(). Add it to the actions file with the following code:
// in apps/frontend/actions/actions.class.php
public function executePhoto()
{
$photo = PhotoPeer::retrieveByPk($this->getRequestParameter('id'));
$this->forward404unless($photo);
$this->photo = $photo;
}
There is something new in this action: the getRequestParameter method call. That's actually the way to retrieve a request parameter by name from the action. The retrieveByPk static method of the PhotoPeer model class is used to retrieve a Photo object based on its primary key. But we don't want this action to be called with erroneous parameters. If a user types the URL with a non-existent id, then instead of an error page, he or she will see a 404 error page. That's the purpose of the forward404unless call, which is equivalent to:
if(!$photo)
{
$this->forward404();
}
Now, how do we pass the $photo object to the template? You already saw this in the index action, so let's switch to the template. Create a photoSuccess.php template in the templates/ directory of the module and within it, write:
// 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 $photo->getDescription() ?>"
published on <?php echo $photo->getCreatedAt('d/m') ?>,
tagged <?php echo $photo->getTagsString() ?>
</p>
<div id="footer">
powered by <?php echo link_to('symfony', 'http://www.symfony-project.com') ?>
</div>
</div>
There's nothing new here, except that the link to the actual picture doesn't use the link_to() helper. This is because, in this particular case (linking to an uploaded file), writing it without the helper is faster.
