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
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:
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:
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:
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:
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 theonDelete
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 theCASCADE
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 eithertrue
orfalse
. This will either mean the field has to contain data or it doesn't have to. If this attribute is set totrue
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'); } }
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.
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.
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:
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:
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.
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:
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.
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 theindexError.php
templatereturn myTemplate:
Looks for theindexmyTemplate.php
templatereturn 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.
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.
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.
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.
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.
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!
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:
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.
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
.
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.
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.
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.