Symfony 1.3 Web Application Development
上QQ阅读APP看书,第一时间看更新

The milkshake shop

Our application will be designed for a fictitious milkshake shop. The functional requirements for our shop are:

  • Display of the menu, job vacancies, and locations; these details will be updated from the back office
  • Sign up to a newsletter, where users can submit their details
  • Search facility for the menu
  • Secure backend for staff to update the site
  • The site must be responsive
  • Option for the users to view our vacancies in three languages

Creating the skeleton folder structure

Symfony has many ways in which it can help the developer create applications with less efforts—one way is by using the Symfony tasks available on the Command Line Interface (CLI). We will be using this method extensively in this book. These Symfony tasks do the following:

  • Generate the folder structure for your project, modules, applications, and tasks
  • Clear the generated cache and rotate log files
  • Create controllers to enable and disable an application and set permissions
  • Interact with the ORM layer to build models and forms
  • Interact with SQL to insert data into the database
  • Generate initial tests and execute them
  • Search for text in your templates that need i18N dictionaries and create them

Note

If you'd like to see all the tasks that are available with Symfony, just type the following on your command line:

symfony

Let's begin with using one of the Symfony tasks to generate the file structure for our project. Our project will reside in the milkshake folder. In your terminal window, change your folder path to the path that will hold your project and create this milkshake folder. My folder will reside in workspace folder. Therefore, I would type this:

$mkdir ~/workspace/milkshake && cd ~/workspace/milkshake

Now that I have the project folder and have changed the path to within the milkshake folder, let's use a Symfony task to generate the project file structure. In your terminal window, type the following:

$/home/timmy/workspace/milkshake>symfony generate:project –orm=Propelmilkshake

We can generate our project we can also specify which ORM we would like to use. Our application is going to use the Propel ORM, but you can also opt for Doctrine ORM. By default, Doctrine ORM is enabled.

After pressing the Enter key, the task goes into action. Now you will see output like the one in the following screenshot on your terminal window. This screenshot shows the folder structure being created:

Creating the skeleton folder structure

Let's have a look at the top-level folders that have been created in our project folder:

Note

There are three folders that must be writable by the web server. These are the cache, log, and web/uploads/. If these are not writable to your web server, then the server itself will either produce warnings at startup or fail to start. The files permissions are usually set during the generation process, but sometimes this can fail. You can use the following task to set the permissions:

$/home/timmy/workspace/milkshake>symfony project: permissions

With the initial project skeleton built, next we must create an application. Symfony defines an application as operations that are grouped logically and that can run independent of one another. For example, many sites have a frontend and back office/admin area. The admin area is where a user logs in and updates areas on the frontend. Of course, our application will also provide this functionality. We will call these areas frontend and backend. Our first application is going to be the frontend. Again, let's use a Symfony task to generate the frontend application folder structure. Enter the following task in your terminal window:

$/home/timmy/workspace/milkshake>symfony generate:app frontend

Again, after executing this task, you will see the following in your terminal window:

Creating the skeleton folder structure

Let's have a look at the top-level folders that have just been created in the apps folder:

These steps will generate our project and frontend application folder structure, and we can now view our project in a web browser. Open your web browser and go to http://milkshake/frontend_dev.php and you will see the following default Symfony page:

Creating the skeleton folder structure

Now we can see the default project page along with the instructions on what to do next. The frontend_dev.php file is our index file while developing the frontend application, and adheres to the naming convention of<application>_<environment>.php. This controller is the development controller and is rather helpful while developing. Before we continue though, I urge you to also have a look at the web debug toolbar. We will be covering this later in this book.

Creating our database schema

When you think of creating the database schema, what generally springs to mind is either SQL CREATE statements or using a client such as phpMyAdmin to create the schema. But in Symfony, we will create the schema in either YAML or XML. For our application, we will be using the XML for a few reasons:

  • XML schemas are arguably easier to read than YAML schemas
  • Susceptibility of YAML to whitespace errors is exaggerated in these larger configuration files, and sometimes there is fall over with very large databases

Our schema may appear as though it's only for the database, but it is also a part of the ORM and forms generation process. As you can imagine, the schema is important for the integrity of our application.

Our database will be fairly simple. We will need to hold data for the milkshakes, flavors, store locations, vacancies, and user sign-up details. For visual reference, the entity relationship diagram is shown as follows:

Creating our database schemacontrollerabout

By default, Symfony only generates a schema.yml file that is located in the config folder. As we want to create our schema using XML, simply rename the existing schema.yml file to schema.xml. Once you have done that, copy in the following schema:

<?xml version="1.0" encoding="UTF-8"?>
<database name="propel" defaultIdMethod="native" noXsd="true" package="lib.model">
<table name="milkshakes" idMethod="native" phpName="Milkshake">
<column name="id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" index="true" />
<column name="name" type="VARCHAR" size="100" required="true" index="true" />
<column name="URL_slug" type="VARCHAR" size="100" required="true" />
<column name="image_URL" type="VARCHAR" size="40" required="true" />
<column name="thumb_URL" type="VARCHAR" size="40" required="true" />
<column name="calories" type="FLOAT" required="true" />
<column name="views" type="INTEGER" default="0"/>
<column name="created_at" type="TIMESTAMP" required="true" />
<column name="updated_at" type="TIMESTAMP" required="true" />
<index name="milkshake_name_index">
<index-column name="name"/>
</index>
</table>
<table name="flavors" idMethod="native" phpName="Flavor">
<column name="id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" />
<column name="name" type="VARCHAR" size="20" required="true" />
<column name="created_at" type="TIMESTAMP" required="true" />
</table>
<table name="milkshake_flavors" idMethod="native" phpName="MilkshakeFlavor">
<column name="id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" />
<column name="milkshake_id" type="INTEGER" required="true" />
<column name="flavor_id" type="INTEGER" required="true" />
<foreign-key foreignTable="flavors" onDelete="CASCADE">
<reference local="flavor_id" foreign="id" />
</foreign-key>
<foreign-key foreignTable="milkshakes" onDelete="CASCADE">
<reference local="milkshake_id" foreign="id" />
</foreign-key>
</table>
<table name="store_locations" idMethod="native" phpName="StoreLocation">
<column name="id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" />
<column name="address1" type="VARCHAR" size="100" required="true" />
<column name="address2" type="VARCHAR" size="100" required="true" />
<column name="address3" type="VARCHAR" size="50" required="true" />
<column name="postcode" type="VARCHAR" size="8" required="true" />
<column name="city" type="VARCHAR" size="50" required="true" />
<column name="country" type="VARCHAR" size="50" required="true" />
<column name="phone" type="VARCHAR" size="20" required="true" />
<column name="fax" type="VARCHAR" size="20" required="true" />
</table>
<table name="vacancies" idMethod="native" phpName="Vacancy">
<column name="id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" />
<column name="position" type="VARCHAR" size="30" required="true" />
<column name="position_description" type="VARCHAR" size="100" required="true" />
<column name="location_id" type="INTEGER" required="true" />
<foreign-key foreignTable="store_locations" onDelete="CASCADE">
<reference local="location_id" foreign="id" />
</foreign-key>
</table>
</database>

Note

When creating our schema, it is important to stick to the Propel and Symfony naming conventions—all table names should be plural and all models should be singular. The reason for this is that all the methods names are generated and have s appended to the function name. To retrieve all milkshake flavors, we would call the getMilkshakeFlavors() method from the milkshake object. If the object was in a plural form, the method created would be getMilkshakeFlavorss().

The schema itself is straightforward, but there are a few small options we should quickly go over:

  • package="lib.model"

    When Symfony will generate our models, they will be placed inside the lib/model folder. Later when we take a look at plugins, you will see that this can be changed.

  • onDelete="CASCADE"

    By default, Symfony enforces referential integrity by using the RESTRICT value for the onDelete attribute. This means that if we wanted to delete a milkshake, the system would prevent that as the milkshake is associated with flavors. Therefore, using the CASCADE value will allow us to delete the item.

    Tip

    Further information about this is available on the Propel web site http://propel.phpdb.org/trac/wiki/Users/Documentation/1.3/Relationships.

  • phpName="MilkShake"

    Although this attribute can be omitted, it gives us the ability to name the generated models. Although Symfony will do this automatically, this adds flexibility.

  • required="true"

    The required attribute can be set to either true or false. This will either mean the field has to contain data or it doesn't have to. If this attribute is set to true when using the admin generator, the generated form will be in bold.

Symfony offers special handling of date/time fields. The fields must be named created_at or created_on, updated_at or updated_on. When first saving a record, if you have a field named created_at, Symfony will transparently add in the data/time. This also goes for the updated_at field.

We have created the database schema. Next, before using Symfony's CLI task, we must set up the database connections to build our database tables and the corresponding models, forms, and filters.

Configuring the ORM layer

The earlier versions of Symfony included the Propel ORM library as a part of its core components. But with releases of the new versions, it was decided to move the ORM out from its core framework and package the ORM as a plugin. The reason behind this is Doctrine, which is the other popular ORM used with Symfony. The new releases now allow the developer to easily choose which ORM they prefer. Both ORMs are packaged as plugins and are distributed with Symfony. Although Doctrine is now the default ORM, our application will be using Propel. However, later we'll be looking at a plugin that allows you to interchange ORMs.

As mentioned previously, the first file that Symfony reads is the ProjectConfiguration.class.php file, which is where we set configuration options for our project. It's located in our project's config folder. For example, we can enable plugins and set default folders. Now let's modify the ProjectConfiguration.class.php file from the config folder after making the change:

<?php
require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/ sfCoreAutoload.class.php';
sfCoreAutoload::register();
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
$this->enablePlugins('sfDoctrinePlugin');
}
}

Note

Our configuration is what is included in the sandbox, if you have installed Symfony via PEAR then the path to the sfCoreAutolad.class.php will be different.

For us to enable Propel we have to change the plugin sfDoctrinePlugin to sfPropelPlugin like:

<?php
require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
$this->enablePlugins('sfPropelPlugin');
}
}

In Chapter 1, Getting Started with Symfony, I mentioned that Symfony uses autoloading to load all classes so that the developer does not need to use the require() function. The project configuration file is what starts this off, as you can see in the highlighted lines above. Also, you can see that our ORM plugin has been enabled. Every plugin that needs to be enabled needs to be defined here.

Configuring the database connection

In order to connect to our database, there are a few parameters that we need to set. These are the username, password, host, and the name of the database. All of these settings are defined in two configuration files—databases.yml and propel.ini—which again are located in the config folder with all other project-wide configuration files.

Setting these parameters can be done either by using a Symfony task on the CLI or by directly editing the files. Let's add them via the command line as follows:

$/home/timmy/workspace/milkshake>symfony configure:database "mysql:host=localhost;dbname=milkshake" root myPassword

After executing the above task, open both databases.yml and propel.ini files. In them you will see that the database connection properties have now been updated.

Note

Opening both of the database configuration files and entering the details manually is preferred because then you don't have to remember the tasks' parameters. Also, from a security point of view you should never enter a password directly on the command line where the password is in plain text.

Using another database engine

By default, Symfony is configured to use the MySQL database. Of course, Symfony supports many other databases such as PostgreSQL, Oracle, SQLite, and MSSQL. Just to show you how easy it is to change to another database, I will set up my project for use with PostgreSQL. This is included here only as an example to demonstrate how to change database engines. For our application, we will be using MySQL.

In the database.yml file, you will see the following:

all:
propel:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'pgsql:host=localhost;dbname=milkshake'
username: root
password: myPassword
encoding: utf8
persistent: true
pooling: true

In the propel.ini file, you will see the following:

propel.database             = pgsql
propel.database.driver  = pgsql
propel.database.url     = pgsql:host=localhost;dbname=milkshake

Generating the models, forms, and filters

The Symfony tasks handle building all of the models, forms, and filters. When we were creating the database schema earlier, I mentioned that the schema is the root of all models, forms, and filters. To show this in action, we are going to generate them all. When you execute the tasks, the schema.xml file is parsed and then the ORM layer is generated based on it. At your terminal, execute the following three tasks:

$/home/timmy/workspace/milkshake>symfony propel:build-model $/home/timmy/workspace/milkshake>symfony propel:build-forms $/home/timmy/workspace/milkshake>symfony propel:build-filters

Now we have generated the entire ORM layer and all the generated classes are located in your lib folder. If you take a look inside the lib folder, you will see that there are three new subfolders—form, filter, and model—which all contain the classes. In Chapter 3, Adding the Business Logic and Complex Application Logic, we will take a look at these generated classes.

In the following screenshot, you can see the overall folder structure:

Generating the models, forms, and filters

Building the database

The last step is to create the database and then create all of the tables. I have created my database called milkshake on the CLI using the following command:

$/home/timmy/workspace/milkshake>mysqladmin create milkshake -u root -p

Now that we have created the database, we need to generate the SQL that will create our tables. Again, we are going to use a Symfony task for this. Just like creating the ORM layer, the task will build the SQL based on the schema.xml file. From the CLI, execute the following task:

$/home/timmy/workspace/milkshake>symfony propel:build-sql

This has now generated a SQL file that contains all of the SQL statements needed to build the tables in our database. This file is located in the data/sql folder within the project folder. Looking at the generated lib.model.schema.sql file in this folder, you can view the SQL. Next, we need to insert the SQL into the database. Again using a Symfony task, execute the following on the CLI:

$/home/timmy/workspace/milkshake>symfony propel:insert-sql

During the execution of the task, you will be prompted to enter a y or N as to whether you want to delete the existing data. As this command will delete your existing tables and then create new tables, enter y. During development, the confirmation can become tiring. To get around this you can append the no-confirmation switch to the end as shown here:

>symfony propel:insert-sql --no-confirmation

Afterwards, check in your database and you should see all of the tables created as shown in the following screenshot:

Building the database

I have showed you how to execute each of the tasks in order to build everything. But there is a simpler way to do this, and that is with yet another Symfony task which executes all of the above tasks:

$/home/timmy/workspace/milkshake>symfony propel:build-all

or

$/home/timmy/workspace/milkshake>symfony propel:build-all --noconfirmation

Our application is now all set up with a database and the ORM layer configured. Next, we can start on the application logic and produce a wireframe.

Creating the application modules

In Symfony, all requests are initially handled by a front controller before being passed to an action. The actions then implement the application logic before returning the presentation template that will be rendered.

Our application will initially contain four areas—home, location, menu, and vacancies. These areas will essentially form modules within our frontend application. A module is similar to an application, which is the place to group all application logic and is self contained. Let's now create the modules on the CLI by executing the following tasks:

$/home/timmy/workspace/milkshake>symfony generate:module frontend home $/home/timmy/workspace/milkshake>symfony generate:module frontend location $/home/timmy/workspace/milkshake>symfony generate:module frontend menu $/home/timmy/workspace/milkshake>symfony generate:module frontend vacancies

Executing these tasks will create all of the modules' folder structures along with default actions, templates, and tests in our frontend application. You will see the following screenshot when running the first task:

Creating the application modules

Let's examine the folder structure for a module:

Now browse tohttp://milkshake/frontend_dev.php/menu and you will see Symfony's default page for our menu module. Notice that this page also provides useful information on what to do next. This information, of course, is to render our template rather than have Symfony forward the request.

Creating the application modules

Handling the routing

We have just tested our menu module and Symfony was able to handle this request without us having to set anything. This is because the URL was interpreted as http://milkshake/module/action/:params. If the action is missing, Symfony will automatically append index and execute the index action if one exists in the module. Looking at the URL for our menu module, we can use either http://milkshake/frontend_dev.php/menu or http://milkshake/frontend_dev.php/menu/index for the moment. Also, if you want to pass variables from the URL, then we can just add them to the end of the URL. For example, if we wanted to also pass page=1 to the menu module, the URL would be http://milkshake/frontend_dev.php/menu/index/page/1. The problem here is that we must also specify the name of the action, which doesn't leave much room for customizing a URL.

Mapping the URL to the application logic is called routing. In the earlier example, we browsed to http://milkshake/frontend_dev.php/menu and Symfony was able to route that to our menu module without us having to configure anything. First, let's take a look at the routing file located at apps/frontend/config/routing.yml.

# default rules
homepage:
URL: /
param: { module: default, action: index }
default_index:
URL: /:module
param: { action: index }
default:
URL: /:module/:action/*

This is the default routing file that was generated for us. Using the home page routing rules as an example, the route is broken down into three parts:

  • A unique label: homepage
  • A URL pattern: URL: /
  • An array of request parameters: param: { module: menu, action: index }

We refer to each one of the rules within the routing file using a unique label. A URL pattern is what Symfony uses to map the URL to a rule, and the array of parameters is what maps the request to the module and the action. By using a routing file, Symfony caters for complicated URLs, which can restrict parameter types, request methods, and associate parameters to our Propel ORM layer. In fact, Symfony includes an entire framework that handles the routing for us. In the later chapters, we will expand on routing.

The application logic

As we have seen, Symfony routes all requests to an action within a module. So let's open the actions class for our menu module, which is located at apps/frontend/modules/menu/actions/actions.class.php.

class menuActions extends sfActions
{
/**
* Executes index action
*
* @param sfRequest $request A request object
*/
public function executeIndex(sfWebRequest $request)
{
$this->forward('default', 'module');
}
}

This menuActions class contains all of the menu actions and as you can see, it extends the sfActions base class. This class was generated for us along with a default 'index' action (method). The default index action simply forwards the request to Symfony's default module, which in turn generates the default page that we were presented with.

All of the actions follow the same naming convention, that is, the action name must begin with the word execute followed by the action name starting with a capital letter. Also, the request object is passed to the action, which contains all of the parameters that are in the request.

Let's begin by modifying the default behavior of the menu module to display our own template. Here we need the application logic to return the template name that needs to be rendered. To do this, we simply replace the call to the forward function with a return statement that has the template name:

public function executeIndex(sfWebRequest $request)
{
return sfView::SUCCESS;
}

A default index template was also generated for us in the templates folder, that is, apps/frontend/modules/menu/templates/indexSuccess.php. Returning the sfView::SUCCESS constant will render this template for us. The template rendered will depend on the returned string from the action. All templates must also follow the naming convention of actionNameReturnString.php. Therefore, our action called index returns the sfView constant SUCCESS, meaning that the indexSuccess.php template needs to be present within the templates folder for our menu module. We can return other strings, such as these:

  • return sfView::ERROR: Looks for the indexError.php template
  • return myTemplate: Looks for the indexmyTemplate.php template
  • return sfView::NONE: Will not return a template and, therefore, bypass the view layer; this could be used as an example for an AJAX request

However, just removing the $this->forward('default', 'module') function will also return indexSuccess.php by default. It is worth adding the return value for ease in reading. Now that we have rendered the menu template, go ahead and do the same for the home, locations, and vacancies modules.

Rendering the template

The final step is to create the needed XHTML to render the template. Views (or templates as they are known) form the presentation layer. Although I am discussing a template as one whole entity, it is not entirely true. Just like all good template frameworks (Smarty, for example), a template can consist of reusable components. As with Symfony, there is no exception. We will be exploring these in greater detail in later chapters.

In a nutshell, as the template is rendered, it pulls in all other modules as they are called (with the exception of slots) and then decorates the template with a wrapper layout template.

Note

Although developers are free to use PHP code in the templates, PHP should be kept to an absolute minimum, and serve only for presentation logic.

There are two folders where we can keep templates—the moduleName/templates folder or the apps/templates folder. The former holds all templates for your module and the latter is where all your global templates reside. If you look inside the global template folder, you will see the global layout template—layout.php. This global template contains all of the code for the outer shell of the module template. This wrapper template is where we have things such as the HTML starting tags, meta tags, title tags, and others, along with the main XHTML closing tags.

Let's look at the main layout template 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 include_http_metas() ?>
<?php include_metas() ?>
<?php include_title() ?>
<link rel="shortcut icon" href="/favicon.ico" />
<?php include_stylesheets() ?>
<?php include_javascripts() ?>
</head>
<body>
<?php echo $sf_content ?>
</body>
</html>

It is worth noting how the meta tags and title tags are included in the first part of the template. These are all passed in either from a view configuration file or by the action as follows:

<?php include_http_metas() ?>
<?php include_metas() ?>
<?php include_title() ?>

The second part is how our module template is included in the global template. In this case, the layout template is rending the raw format of the generated module template. Symfony has a few escaping settings, meaning that it will turn certain characters into their HTML equivalents.

As this layout is a part of the overall template and, therefore, generally present as part of all templates, we are going to add the navigation to this template. But before we do so, we need to add our routing rules so that we can add the navigation links.

Adding our routing rules

At the moment, Symfony is able to find our pages by module/action and because we use the default index action, Symfony will render our template. Now we need to add links to our application. We could just create the links that point to module/action, but this will soon get complicated as the site scales upwards. Besides, there is also a production and development controller involved. So instead of just creating links, we will create the routing rules. Our links will reference the routing rules and, therefore, will generate the URLs for us. Open up the routing.yml file in the apps/frontend/config folder and add the following:

# default rules
homepage:
URL: /
param: { module: default, action: index }
menu:
URL: /menu
param: { module: menu, action: index }
locations:
URL: /locations
param: { module: location, action: index }
vacancies:
URL: /vacancies
param: { module: vacancies, action: index }
default_index:
URL: /:module
param: { action: index }
default:
URL: /:module/:action/*

As you can see, we have followed the same pattern as the default rules. We first added a label and then defined the URL pattern, followed by the module and action. Now let's go back to our layout template.

Note

Make sure your rules are above the default and default_index rules.

To access these routes from within, the template Symfony provides these helpers—link_to() and url_for(). Essentially, a helper is a function that returns HTML. In the case of these two helpers, they will return an HTML link tag. We are going to add the following four links to our layout:

<?php echo link_to('Home', '@home') ?>
<?php echo link_to('Menu', '@menu') ?>
<?php echo link_to('Store Locations', '@locations
<?php echo link_to('Vacancies', '@vacancies

The first parameter passed to the helper is the text that will be displayed as the link. The second parameter references a rule in the routing; we simply start with the @ symbol followed by the routing label. When you look at the final page a little later, you will see that Symfony has correctly inserted the URL for us. Depending on what controller we are using, the correct controller will also be inserted as a part of the URL.

Configuring template parameters

We need to set the page titles in the HTML. As mentioned earlier, templates can be configured in a few ways. Here we are going to set the page title in the template configuration file. To do this, we must create a folder named config within each of the modules that we have already created, and create a view.yml file within the config folder. The view.yml configuration file will contain all of the meta and title descriptions, along with any other template-specific variables. There are many more options that we can set, but for now we will only need to set the basic settings. After you have created both the config folder and the view.yml file within the folder for the menu module, open your view.yml file and add the following:

indexSuccess:
  metas:
   title:       Our Menu

The first parameter references the template by the file name. Our template is indexSuccess.php and so we use the filename minus the extension. Now we would like to set the page title within the metas. This file can now be copied to all the other config folders that you have created; of course, you just have to amend the title. Being able to configure all templates within a module forces a tightly coded application. This file is great if we don't need the title to be dynamic, but we will address that later.

After copying this file to all the other modules and changing the titles, you are all done!

Styling the pages

The last step is to add a little CSS to give the pages an initial wireframe feeling. On my layout template, I have added the inline styling which sets the page up.

This is the layout.php template. The highlighted lines indicate where I have added the final CSS:

<!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 include_http_metas() ?>
<?php include_metas() ?>
<?php include_title() ?>
<link rel="shortcut icon" href="/favicon.ico" />
<style type="text/css"> body{margin:0; padding:0; font-family: Arial, Verdana, sans-serif; font-size: 11px;} a img,:link img,:visited img { border: none; color: #000000; } </style>
</head>
<body>
<div style="width: 786px; border: 1px solid #000000; margin: auto;"> <div style="height: 100px;"> <div style="text-align: center"><h1>Top Banner</h1></div> </div> <div style="width: 192px; float: left; "> <div style="width: 100%; text-align: left;"> <ul style="list-style-type:none;margin:0;padding:0;">
<li style="margin:0;padding:0.25em 0.5em 0.25em 0.5em; 
width: 150px; border-bottom: 1px solid #000000; 
border-right: 1px solid #000000; ; border-top: 1px solid #000000; ">
<?php echo link_to('Home', '@homepage') ?></li> <li style="margin:0;padding:0.25em 0.5em 0.25em 0.5em; width: 150px; border-bottom: 1px solid #000000; border-right: 1px solid #000000; "> <?php echo link_to('Menu', '@menu') ?></li>
<li style="margin:0;padding:0.25em 0.5em 0.25em 0.5em; 
width: 150px; border-bottom: 1px solid #000000; 
border-right: 1px solid #000000; "> <?php echo link_to('Store Locations', '@locations') ?></li>
<li style="margin:0;padding:0.25em 0.5em 0.25em 0.5em; 
width: 150px; border-bottom: 1px solid #000000; 
border-right: 1px solid #000000; ">  <?php echo link_to('Vacancies', '@vacancies') ?></li> </ul> </div> </div> <div style="margin-left: 208px"> <div style="min-height: 100px"> <?php echo $sf_data-> getRaw('sf_content') ?> </div> </div> <div style="text-align: center"><h1>Footer</h1></div> <div style="clear:both"></div> </div>
</body>
</html>

With the result HTML added to our layout, we now get a nice wire frame that resembles the site:

Styling the pages

Note

Debugging:

When developing and trying to troubleshoot, it is important to use the frontend_dev.php controller. Otherwise, the errors will not be revealed as Symfony hides them in a production environment.

Using the development controller means we can use the debug bar at the top of the window to see what is happening. Also, there are log files available to us in the log folder that can be used for troubleshooting.

Common installation problems

When first starting out, there are a few potential problems that you can encounter. Below are a few of the main errors that you might come across.

Web server 404 error page

The requested URL /index.php file was not found on this server.

This is because you haven't created an application. So, on the command line enter:

>symfony app frontend

This will create the application, along with the controllers, index.php and frontend_dev.php.

A symfony 'Oop! An error occurred'

One thing to remember is this page is in fact the default Symfony 500 error page, and not a web server 500 error page. While developing, you should always use the development controller (app_dev.php). The development controller lets you see the exact problem. The production controller (index.php) hides the problem away from the users.

Pages and debug bar do not render correctly

Another common problem is that when you configure your virtual hosts, you will often leave an important line out and therefore will be left seeing a page with no CSS.

Pages and debug bar do not render correctly

As you can see the above page has lost all CSS styling. This is caused by not including an alias directive in you virtual host configuration file. Include the following in virtual host configuration file:

#Set symfony alias
Alias /sf /usr/share/php/data/symfony/web/sf

On some Linux installations, you need to also include:

<Directory "/usr/share/php/data/symfony/web/sf ">
Allow from all
</Directory >

Throughout the book we are going to learn the ins and outs of Symfony and in no time at all will know why Symfony truly is an excellent framework.