![]() |
|
Symfony tutorialMy first symfony project |
|
You are currently reading "symfony Tutorials" which is licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License license.
|
This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License. |
This tutorial is presently being converted from the 1.0 version. It is entirely possible to get a basic version running on 1.1, but you may need to get a few hints from more experienced users in the users' forum.
So, you want to give it a go? Let's build together a fully-functional web app in one hour. You name it. A bookseller application? Okay, another idea. A blog! That's a good one. Let's go.
We'll assume that you are working with Apache installed and running on your local machine. You will also need PHP 5.1.3 or newer.
To go fast, we will use the symfony sandbox. This is an empty symfony project where all the required libraries are already included, and where the basic configuration is already done. The great advantage of the sandbox over other types of installation is that you can start experimenting with symfony immediately.
Get it here: sf_sandbox_1_1.tgz
or here: sf_sandbox_1_1.zip,
and unpack it in your root web directory. On Linux systems, it is recommended to keep the
permissions as they are in the tar file (for example by using -p with tar command).
Refer to the included README file for more information. The resulting file structure
should look like this:
www/
sf_sandbox/
apps/
frontend/
cache/
config/
data/
doc/
lib/
log/
plugins/
test/
web/
css/
images/
js/
This shows a sf_sandbox project containing a frontend application.
Test the sandbox by requesting the following URL:
http://localhost/sf_sandbox/web/index.php/
You should see a congratulations page.

You can also install symfony in a custom folder and setup your web server with a Virtual Host or an Alias. The symfony book contains detailed chapters about symfony installation and the symfony directory structure.
So, the blog will handle posts, and you will enable comments on them. Create a
schema.yml file in sf_sandbox/config/ and paste the following data model:
propel:
blog_post:
id: ~
title: varchar(255)
excerpt: longvarchar
body: longvarchar
created_at: ~
blog_comment:
id: ~
blog_post_id: ~
author: varchar(255)
email: varchar(255)
body: longvarchar
created_at: ~
This configuration file uses the YAML syntax. It's a very simple language that allows XML-like tree structures described by indentation. Furthermore, it is faster to read and write than XML. The only thing is, the indentation has a meaning and tabulations are forbidden, so remember to use spaces for indentation. You will find more about YAML and the symfony configuration in the configuration chapter.
This schema describes the structure of two of the tables needed for the blog.
Post and Comment are the names of the related classes to be generated.
Save the file, open a command line, browse to the sf_sandbox/ directory and type:
$ php symfony propel:build-model
Make sure your command line folder is set to the root of your project (
sf_sandbox/) when you call thesymfonycommand.
A few classes are created in the sf_sandbox/lib/model/ directory. These are the
classes of the object-relational mapping system, which allows us to have access to a relational
database from within an object-oriented code without writing a single SQL query.
By default, symfony uses the Propel library for this purpose. Theses classes are
part of the model of our application
(find more in the model chapter).
Now, we need to convert the schema to SQL statements to initialize the database tables.
By default, the symfony sandbox is configured to work out of the box with a simple
SQLite file, so no database initialization is required.
You still need to check that the SQLite extension is installed and enabled correctly
(you can check this in php.ini - see how
in the PHP documentation).
By default, the sf_sandbox project will use a database file called sandbox.db
located in sf_sandbox/data/.
If you want to switch to MySQL for this project, use the configure:database task:
$ php symfony configure:database mysql://root:pa$$word@localhost/symfony_project
Change the DSN argument to match your settings (username, password, host, and database name) and then create the database with the command line or a web interface (as described in the model chapter).
If you use SQLite as your database engine, you will have to change some rights on *nix systems:
$ chmod 777 data data/sandbox.db
Now type in the command line:
$ php symfony propel:build-sql
A lib.model.schema.sql file is created in sf_sandbox/data/sql/.
The SQL statements found is this file can be used to initialize a database with
the same table structure.
At the time of writing, the file path configured for the sqlite database file in the 1.1 sandbox is not set up correctly. This however is easily fixed: just open up
/config/propel.iniand ensure that the following lines have 7 sets of '..' in them:propel.database.createUrl = sqlite://./../../../../../../../data/sandbox.db propel.database.url = sqlite://./../../../../../../../data/sandbox.dbIf they do not, copy the lines as they appear here, and save the file.
To build the table structure based on the the SQL file, type:
$ php symfony propel:insert-sql
Don't worry if there is a warning at that point, it is normal. The
propel:insert-sqlcommand removes existing tables before adding the ones from yourlib.model.schema.sql, and there are no tables to remove at the moment.
As we want to be able to create and edit the blog posts and comments, we also need to generate some forms based on our model schema:
$ php symfony propel:build-forms
This task generates classes in the sf_sandbox/lib/form/ directory.
These classes are used to manage our model objects as forms.
The basic features of a blog are to be able to Create, Retrieve, Update and Delete (CRUD) posts and comments. As you are new to symfony, you will not create symfony code from scratch, but rather let it generate the code that you may use and modify as needed. Symfony can interpret the data model to generate the CRUD interface automatically:
$ php symfony propel:generate-crud --non-verbose-templates --non-atomic-actions --with-show frontend post BlogPost
$ php symfony propel:generate-crud --non-verbose-templates --non-atomic-actions frontend comment BlogComment
$ php symfony cache:clear
When using the
propel:generate-crudtask, we have used the--non-verbose-templatesand--non-atomic-actionsoptions. If you want to learn the meaning of the available arguments and options for a given task, you can use the specialhelptask:$ php symfony help propel:generate-crud
You now have two modules (post and comment) that will let you manipulate
objects of the BlogPost and BlogComment classes. A module usually represents a
page or a group of pages with a similar purpose. Your new modules are located
in the sf_sandbox/apps/frontend/modules/ directory, and they are accessible
by the URLs:
http://localhost/sf_sandbox/web/frontend_dev.php/post
http://localhost/sf_sandbox/web/frontend_dev.php/comment
If you try to create a comment, you will have an error because symfony doesn't yet
know how to convert a post object to a string. Edit the BlogPost class
(lib/model/BlogPost.php) and add the __toString() method:
class BlogPost extends BaseBlogPost { public function __toString() { return $this->getTitle(); } }
Now, feel free to create some new posts to make the blog look less empty.

Find more about generators and the explanation of symfony projects structure (project, application, module).
In the URLs above, the name of the main script - called the front controller in symfony - was changed from
index.phptofrontend_dev.php. The two scripts access the same application (frontend), but in different environments. Withfrontend_dev.php, you access the application in the development environment, which provides handy development tools like the debug toolbar on the top right of the screen and the live configuration engine. That's why the processing of each page is slower than when usingindex.php, which is the front controller of the production environment, optimized for speed. If you want to keep on using the production environment, replacefrontend_dev.php/byindex.php/in the following URLs, but don't forget to clear the cache before watching the changes:$ php symfony cache:clear http://localhost/sf_sandbox/web/index.php/
Find more about environments.
In order to navigate between the two new modules, the blog needs some global navigation.
Edit the global template sf_sandbox/apps/frontend/templates/layout.php and
change the content of the <body> tag to:
<div id="container" style="width:600px;margin:0 auto;border:1px solid grey;padding:10px"> <div id="navigation" style="display:inline;float:right"> <ul> <li><?php echo link_to('List of posts', 'post/index') ?></li> <li><?php echo link_to('List of comments', 'comment/index') ?></li> </ul> </div> <div id="title"> <h1><?php echo link_to('My first symfony project', '@homepage') ?></h1> </div> <div id="content" style="clear:right"> <?php echo $sf_data->getRaw('sf_content') ?> </div> </div>
Please forgive the poor design and the use of inner-tag css, but one hour is rather a short amount of time!

While you are at it, you can change the title of your pages.
Edit the view configuration file of the application (sf_sandbox/apps/frontend/config/view.yml),
locate the line showing the title key and change it to:
default:
http_metas:
content-type: text/html
metas:
title: The best blog ever
description: symfony project
keywords: symfony, project
language: en
robots: index, follow
The home page itself needs to be changed. It uses the default template of the
default module, which is kept in the framework but not in your application
directory. To override it, you can create a custom main module:
$ php symfony generate:module frontend main
By default, the index action shows a default congratulations screen.
To remove it, edit the sf_sandbox/apps/frontend/modules/main/actions/actions.class.php
and remove the content of the executeIndex() method as follows:
/** * Executes index action * * @param sfRequest $request A request object */ public function executeIndex($request) { }
Edit the sf_sandbox/apps/frontend/modules/main/templates/indexSuccess.php file
to show a nice welcome message:
<h1>Welcome to my new blog</h1> <p>You are the <?php echo rand(1000,5000) ?>th visitor today.</p>
Now, you must tell symfony which action to execute when the homepage is requested.
To do so, edit the sf_sandbox/apps/frontend/config/routing.yml and change
the homepage rule as follows:
homepage:
url: /
param: { module: main, action: index }
Check the result by requesting the home page again:
http://localhost/sf_sandbox/web/frontend_dev.php/

Go ahead, start using your new web app. Make sure you've created a test post, and also create a test comment against your post.
Find more about views and templates.
That was fast, wasn't it? Now it is time to mix the comment module into the
post one to get comments displayed below posts.
First, you need to make the post comments available for the post display template.
In symfony, this kind of logic is kept in actions. Edit the actions file
sf_sandbox/apps/frontend/modules/post/actions/actions.class.php and change
the executeShow() method by adding the 4 last lines:
public function executeShow($request) { $this->blog_post = BlogPostPeer::retrieveByPk($request->getParameter('id')); $this->forward404Unless($this->blog_post); $c = new Criteria(); $c->add(BlogCommentPeer::BLOG_POST_ID, $request->getParameter('id')); $c->addAscendingOrderByColumn(BlogCommentPeer::CREATED_AT); $this->comments = BlogCommentPeer::doSelect($c); }
The Criteria and -Peer objects are part of Propel's object-relational mapping.
Basically, these four lines will handle a SQL query to the blog_comment table to get
the comments related to the current blog_post (the one designated by the URL parameter id).
The $this->comments line in the action will give access to a $comments variable
in the corresponding template. Now, modify the post display template
sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php by adding at the end:
// ... <?php use_helper('Text', 'Date') ?> <hr /> <?php if ($comments) : ?> <p><?php echo count($comments) ?> comments to this post.</p> <?php foreach ($comments as $comment): ?> <p><em>posted by <?php echo $comment->getAuthor() ?> on <?php echo format_date($comment->getCreatedAt()) ?></em></p> <div class="comment" style="margin-bottom:10px;"> <?php echo simple_format_text($comment->getBody()) ?> </div> <?php endforeach; ?> <?php endif; ?>
This page uses new PHP functions (format_date() and simple_format_text())
provided by symfony, and called 'helpers' because they do some tasks for you
that would normally require more time and code. Create a new comment for your
first post, then check again the first post, either by clicking on its number
in the list, or by typing directly:
http://localhost/sf_sandbox/web/frontend_dev.php/post/show?id=1

This is getting good.
Find more about the naming conventions linking an action to a template.
Currently we can't add comments to posts directly; if we're editing a post, we have to go to the comments editing section, create a new one, then select the post we want to comment on using the drop-down menu. It would be better if there was a link on each post editing page to go straight to the comment editing facility.
So let's arrange that first. In the modules/post/templates/showSuccess.php template,
add this line at the bottom:
<?php echo link_to('Add a comment', 'comment/edit?post_id='.$blog_post->getId()) ?>
The link_to() function (which we call a "helper") creates a hyperlink pointing to the
edit action of the comment module, so you can add a comment directly from the post
details page. At the moment, however, the comments edit page still offers a form element
to select which post to relate a comment to. What we want is for that field to be
replaced by a hidden field (containg the post primary key) if the comments edit page
URL is called specifying that key.
Before we can do that, we should say a few words about how forms work in symfony 1.1. Rather than specifying all the information about the various form elements (input boxes, tickable boxes, select menus, radio buttons and so forth) in an HTML template, some of this detail is delegated to a "form class". This encapsulates all the information required to render a basic version of an HTML form, making them (or at least a prototype version) very easy to get working quickly.
As you would expect, form classes are used to create form objects. In common with standard object-orientation principles, when an object is instantiated, a constructor is called. Therefore, when we create the comments form object, we can read the blog post it is to relate to (if any) and pass that to the constructor to determine whether to swap the post menu for a hidden field.
In fact, the CRUD generator we used previously to create our post and comment systems
makes use of a form class to define the form required in each case. Let's open up
sf_sandbox/lib/form/base/BaseBlogCommentForm.class.php together to see how the form
is defined. The most important method is setup(), which contains (amongst other items)
the following code:
public function setup() { $this->setWidgets(array( 'id' => new sfWidgetFormInputHidden(), 'blog_post_id' => new sfWidgetFormPropelSelect(array('model' => 'BlogPost', 'add_empty' => true)), 'author' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(), 'body' => new sfWidgetFormTextarea(), 'created_at' => new sfWidgetFormDateTime(), )); $this->setValidators(array( 'id' => new sfValidatorPropelChoice(array('model' => 'BlogComment', 'column' => 'id', 'required' => false)), 'blog_post_id' => new sfValidatorPropelChoice(array('model' => 'BlogPost', 'column' => 'id', 'required' => false)), 'author' => new sfValidatorString(array('max_length' => 255, 'required' => false)), 'email' => new sfValidatorString(array('max_length' => 255, 'required' => false)), 'body' => new sfValidatorString(array('required' => false)), 'created_at' => new sfValidatorDateTime(array('required' => false)), )); // ... }
There are two things being specified here: first are the "widgets", which are the HTML elements used in the form, and second, validators, which are used to check user input against a set of correctness checks. Symfony 1.0 users will notice a change of approach here: form elements were previously defined in the template, and validators were defined in YAML files (and sometimes also programmatically in actions).
Now that we have examined the base definition of the comments form, we can make some
changes. Changes however are not best made in the base form, so close that file and open
sf_sandbox/lib/form/BlogCommentForm.class.php. This inherits from
BaseBlogCommentForm, which means we can take advantage of that definition and add our
own modifications as required.
By default, the BlogCommentForm class has an empty configure method. We want to add a constructor that accepts both the blog comment object to edit (this is a Propel database object) and a reference to the post to attach it to. Then, when the configure() method is called, we need to change the blog post element from a menu (as defined in the base class) to a hidden field containing an automatically-filled value. Lets do that now; change the code in this file to the following:
class BlogCommentForm extends BaseBlogCommentForm { // Place to store related post PK class-wide private $idPost; /** * Constructor method for this class */ public function __construct($comment, $idPost) { // Save PK for configure action $this->idPost = $idPost; // Pass object to ancestor class sfPropelForm to allow editing parent::__construct($comment); } /** * Configure method, called automatically by ancestor class */ public function configure() { // If there is a specified related post, swap menu for hidden field if ($this->idPost) { $this->widgetSchema[$name = 'blog_post_id'] = new sfWidgetFormInputHidden(); $this->setDefault($name, $this->idPost); } } }
To use this new class, we need to find the place where this form is created, and
add the post primary key as a new parameter. This is stored in
sf_sandbox/apps/frontend/modules/comment/actions/actions.class.php. Find the code in
the executeEdit method which reads thus:
$this->form = new BlogCommentForm(BlogCommentPeer::retrieveByPk($request->getParameter('id')));
Change that to the following:
$this->form = new BlogCommentForm(BlogCommentPeer::retrieveByPk($request->getParameter('id')), $request->getParameter('post_id'));
After you have made these changes, you will now be able to add a comment directly to a post without having to explicitly specify the post to attach it to.
Next, after adding a comment, we want the user to come back to the post it relates to, rather that remaining on the comment editing page. To accomplish this, we need to edit executeEdit again. Find the following code:
if ($request->isMethod('post')) { $this->form->bind($request->getParameter('blog_comment')); if ($this->form->isValid()) { $blog_comment = $this->form->save(); $this->redirect('comment/edit?id='.$blog_comment->getId()); } }
And change the redirect line so it reads thus:
$this->redirect('post/show?id='.$blog_comment->getBlogPostId());
This will ensure that when a comment is saved, the user is returned to the post that the comment is related to. There are two things here that are worthy of note: firstly, the save is achieved simply by calling the save method on the form object (this is possible as a result of the form being a child of sfFormPropel, which is a special symfony form class that understands database tables). Secondly, we redirect immediately after the save, so that if the page is subsequently refreshed, the user is not asked if they wish to repeat the POST action again.
Okay, so that wraps up this part of the tutorial. You wanted a blog? You have a blog. Incidentally, since we've covered symfony actions a lot here, you may wish to find out more about them from the manual.
Visitors can enter comments, but what if they submit the form without any data in it, or data that is obviously wrong? You would end up with a database containing invalid rows. To avoid that, we need to set up some validation rules to specify what data is allowed. You will recall that some default rules were set up for us already during the CRUD build process, in the BaseBlogCommentForm class. We will override some of these rules now.
Again, our changes go in BlogCommentForm.class.php, and not in the base class. So, open that file, and add in the following PHP code at the end of the configure() method:
$this->validatorSchema['blog_post_id'] = new sfValidatorPropelChoice( array('model' => 'BlogPost', 'column' => 'id', 'required' => true), array('required' => 'This comment must have an associated post')); $this->validatorSchema['author'] = new sfValidatorString( array('required' => true), array('required' => 'The author field cannot be left blank')); $this->validatorSchema['email'] = new sfValidatorEmail( array('required' => false), array('invalid' => 'The email address is not valid')); $this->validatorSchema['body'] = new sfValidatorString( array('required' => true), array('required' => 'The body field cannot be left blank'));
Each of these rows overwrites the base validator for the specified form element name — the default definitions for 'blog_post_id', 'author', 'email' and 'body' are effectively deleted. It is worth bearing in mind that we could equally have used the syntax that was used in the base class — the setValidators() method — but that method requires all validators to be specified. By doing it this alternative way, we have avoided duplicating a little code.
The syntax of the validators themselves is fairly simple. As you can see, the first array parameter contains a number of options, such as whether the parameter is required, and the second array parameter contains message(s) to use if the validation fails. If an error message is not specified, then symfony will even output a simple default message instead.
Once these rules are in place, try saving a comment without an author name, a bad email address, or no body text - you now have a robust form! You will notice a number of things: first of all, where the form contains data, that data will automatically be preserved during the form submission. This saves the user having to type it back in (and normally is something in that the programmer has to arrange manually). Also, errors (in this case) are placed next to the fields that failed their associated validation tests.
Now would be a good time to explain a little about how the form save process works. It
uses the following action code, which you edited earlier in
/sf_sandbox/apps/frontend/modules/comment/actions/actions.class.php:
$this->form = new BlogCommentForm(BlogCommentPeer::retrieveByPk($request->getParameter('id')), $request->getParameter('post_id')); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('blog_comment')); if ($this->form->isValid()) { $blog_comment = $this->form->save(); $this->redirect('post/show?id='.$blog_comment->getBlogPostId()); } }
After the form object is instantiated, the following happens:

Find more about form validation.
Did you notice the way the URLs are rendered? You can make them more user and search engine-friendly. Let's use the post title as an URL for posts.
The problem is that post titles can contain special characters like spaces.
If you just escape them, the URL will contain some ugly %20 strings,
so we will extend the model to add a new method to the BlogPost object
to get a clean, stripped title. To do that, edit the file BlogPost.php located
in the sf_sandbox/lib/model/ directory, and add the following method:
public function getStrippedTitle() { $result = strtolower($this->getTitle()); // strip all non word chars $result = preg_replace('/\W/', ' ', $result); // replace all white space sections with a dash $result = preg_replace('/\ +/', '-', $result); // trim dashes $result = preg_replace('/\-$/', '', $result); $result = preg_replace('/^\-/', '', $result); return $result; }
Now you can create a permalink action for the post module. Add the following
method to the modules/post/actions/actions.class.php:
public function executePermalink($request) { $posts = BlogPostPeer::doSelect(new Criteria()); $title = $request->getParameter('title'); foreach ($posts as $post) { if ($post->getStrippedTitle() == $title) { $request->setParameter('id', $post->getId()); return $this->forward('post', 'show'); } } $this->forward404(); }
The post list can call this permalink action instead of the show one for
each post. In modules/post/templates/indexSuccess.php, delete the id table
header and cell, and change the Title cell from this:
<td><?php echo $post->getTitle() ?></td>
to this, which uses a named rule we will create in a second:
<td><?php echo link_to($blog_post->getTitle(), '@post?title='.$blog_post->getStrippedTitle()) ?></td>
Just one more step: Edit the routing.yml located in the
sf_sandbox/apps/frontend/config/ directory and add these rules at the top:
list_of_posts:
url: /latest_posts
param: { module: post, action: index }
post:
url: /blog/:title
param: { module: post, action: permalink }
Now navigate again in your application to see your new URLs in action. If you get an error, it may be because the routing cache needs to be cleared. To do that, type the following at the command line while in your sf_sandbox folder:
$ php symfony cc

Find more about smart URLs.
Well, if this is meant to be a blog, then it is perhaps a little strange that everybody is allowed to post! This isn't generally how blogs are meant to work, so let's clean up our templates a bit.
In the template modules/post/templates/showSuccess.php, get rid of the
'edit' link by removing the line:
<a href="<?php echo url_for('post/edit?id='.$blog_post->getId()) ?>">Edit</a>
Do the same for the modules/post/templates/listSuccess.php template and remove:
<a href="<?php echo url_for('post/edit') ?>">Create</a>
You also have to remove the following methods from modules/post/actions/actions.class.php:
* executeEdit
* executeDelete
This means that readers cannot post anymore, which is what we want.
For you to write posts, let's create a backend application by typing in the
command line (still from the sf_sandbox project directory):
$ php symfony generate:app backend
$ php symfony propel:init-admin backend post BlogPost
$ php symfony propel:init-admin backend comment BlogComment
This time, we use the admin generator. It offers much more features and customization than the basic CRUD generator.
Just like you did for the frontend application, edit the layout (apps/backend/templates/layout.php)
to add global navigation:
<div id="navigation">
<ul style="list-style:none;">
<li><?php echo link_to('Manage posts', 'post/index') ?></li>
<li><?php echo link_to('Manage comments', 'comment/index') ?></li>
</ul>
</div>
<div id="content">
<?php echo $sf_data->getRaw('sf_content') ?>
</div>
You can access your new back-office application in the development environment by calling:
http://localhost/sf_sandbox/web/backend_dev.php/post

The great advantage of the generated admin is that you can easily customize it by editing a configuration file.
Change the backend/modules/post/config/generator.yml to:
generator:
class: sfPropelAdminGenerator
param:
model_class: BlogPost
theme: default
fields:
title: { name: Title }
excerpt: { name: Excerpt }
body: { name: Body }
nb_comments: { name: Comments }
created_at: { name: Creation date }
list:
title: Post list
layout: tabular
display: [=title, excerpt, nb_comments, created_at]
object_actions:
_edit: ~
_delete: ~
max_per_page: 5
filters: [title, created_at]
edit:
title: Post detail
fields:
title: { type: input_tag, params: size=53 }
excerpt: { type: textarea_tag, params: size=50x2 }
body: { type: textarea_tag, params: size=50x10 }
created_at: { type: input_date_tag, params: rich=on }
Note that among the existing columns of the blog_post table, the admin will look
for a column (or getter method) for nb_comments. Since this is a generated value and
not a column, we can add a getter to our model (sf_sandbox/lib/model/BlogPost.php):
public function getNbComments() { return count($this->getBlogComments()); }
Now refresh the Post administration screen to see the changes:

Currently the backend application can be accessed by everybody. We therefore need to
add some access restrictions. In apps/backend/modules/post/config/, add a
file called security.yml with the following content:
all:
is_secure: on
Repeat this operation for the comment module. Now you can no longer access these
modules, unless you are logged in. However, currently the login action doesn't
exist! Of course, we can easily add it. To do so, create the security module
skeleton:
$ php symfony generate:module backend security
The following part of the tutorial refers to code that would have appeared in the 1.0 version of the sandbox, and has not been updated to take account of symfony 1.1. It will take a while longer to convert this section. Please bear with us!
This new module will be used to handle the login form and the request.
Edit the apps/backend/modules/security/templates/indexSuccess.php to
create the login form:
<h2>Authentication</h2> <?php if ($sf_request->hasErrors()): ?> Identification failed - please try again <?php endif; ?> <?php echo form_tag('security/login') ?> <label for="login">login:</label> <?php echo input_tag('login', $sf_params->get('login')) ?> <label for="password">password:</label> <?php echo input_password_tag('password') ?> <?php echo submit_tag('submit', 'class=default') ?> </form>
Add the login action that is called by the form to the security module
(in the apps/backend/modules/security/actions/actions.class.php file):
public function executeLogin($request) { if ($request->getParameter('login') == 'admin' && $request->getParameter('password') == 'password') { $this->getUser()->setAuthenticated(true); return $this->redirect('post/list'); } else { $request->setError('login', 'incorrect entry'); return $this->forward('security', 'index'); } }
Like for the main module, remove the default code in the index action:
public function executeIndex() { }
The last thing to do is to set the security module as the default module to
handle login actions. To do that, open the apps/backend/config/settings.yml
configuration file and add:
all:
.actions:
login_module: security
login_action: index
At that point, if you try to access the Posts management, you will have to enter a login and a password:

Find more about security.
Ok, the hour is out. You made it. Now you can use both applications in the production environment and play with them:
frontend: http://localhost/sf_sandbox/web/index.php/
backend: http://localhost/sf_sandbox/web/backend.php/
At this point, if you meet an error, it might be because you changed the model after some actions were put in cache (cache isn't activated in the development environment). To clear the cache, simply type:
$ php symfony cc
See, the application is fast and runs smoothly. Pretty darn cool, isn't it? Feel free to explore the code, add new modules, and change the design of pages.
And don't forget to mention your working symfony applications in the symfony Wiki!
If you find a typo or an error, please register and open a ticket.
If you need support or have a technical question, please post to the user mailing-list or to the forum.