Development

/doc/branches/1.1/tutorial/my-first-project.txt

You must first sign up to be able to contribute.

root/doc/branches/1.1/tutorial/my-first-project.txt

Revision 19393, 33.2 kB (checked in by Russ, 2 weeks ago)

[doc] [1.0, 1.1, 1.2] (closes #5656) note on php_xsl extension requirement

Line 
1 My first symfony project
2 ========================
3
4 So, you want to give it a go? Let's build together a fully-functional web app in
5 one hour. You name it. A bookseller application? Okay, another idea. A blog!
6 That's a good one. Let's go.
7
8 This tutorial assumes that you are working with Apache installed and running on your
9 local machine. You will also need PHP 5.1.3 or newer.
10
11 Install symfony and initialize the project
12 ------------------------------------------
13
14 To go fast, the symfony sandbox will be used. This is an empty symfony project where all
15 the required libraries are already included,
16 and where the basic configuration is already done. The great advantage of the sandbox
17 over other types of installation is that you can start experimenting with symfony
18 immediately.
19
20 Get it here: [sf_sandbox_1_1.tgz](http://www.symfony-project.org/get/sf_sandbox_1_1.tgz)
21 or here: [sf_sandbox_1_1.zip](http://www.symfony-project.org/get/sf_sandbox_1_1.zip),
22 and unpack it in your root web directory. On Linux systems, it is recommended to keep the
23 permissions as they are in the tar file (for example by using `-p` with `tar` command).
24 Refer to the included `README` file for more information. The resulting file structure
25 should look like this:
26
27     www/
28       sf_sandbox/
29         apps/
30           frontend/
31         cache/
32         config/
33         data/
34         doc/
35         lib/
36         log/
37         plugins/
38         test/
39         web/
40           css/
41           images/
42           js/
43
44 This shows a `sf_sandbox` **project** containing a `frontend` **application**.
45 Test the sandbox by requesting the following URL:
46
47     http://localhost/sf_sandbox/web/index.php/
48
49 You should see a congratulations page.
50
51 ![Congratulations](/images/tutorials/mfp_1_1/first-congrats.png)
52
53 You can also install symfony in a custom folder and setup your web server with
54 a Virtual Host or an Alias. The symfony book contains detailed chapters about
55 [symfony installation](http://www.symfony-project.org/book/1_1/03-Running-Symfony)
56 and the [symfony directory structure](http://www.symfony-project.org/book/1_1/02-Exploring-Symfony-s-Code).
57
58 Initialize the data model
59 -------------------------
60
61 So, the blog will handle posts, and you will enable comments on them. Create a
62 `schema.yml` file in `sf_sandbox/config/` and paste the following data model:
63
64     [yml]
65     propel:
66       blog_post:
67         id:           ~
68         title:        { type: varchar(255), required: true }
69         excerpt:      { type: longvarchar }
70         body:         { type: longvarchar }
71         created_at:   ~
72       blog_comment:
73         id:           ~
74         blog_post_id: ~
75         author:       { type: varchar(255) }
76         email:        { type: varchar(255) }
77         body:         { type: longvarchar }
78         created_at:   ~
79
80 This configuration file uses the YAML syntax. It's a very simple language that
81 allows XML-like tree structures described by indentation. Furthermore, it is
82 faster to read and write than XML. The only thing is, the indentation has a
83 meaning and tabulations are forbidden, so remember to use spaces for indentation.
84 You will find more about YAML and the symfony configuration in the
85 [configuration chapter](http://www.symfony-project.org/book/1_1/05-Configuring-Symfony).
86
87 This schema describes the structure of two of the tables needed for the blog.
88 `blog_post` and `blog_comment` are the names of the related classes to be generated.
89 Save the file, open a command line, browse to the `sf_sandbox/` directory and type:
90
91     $ php symfony propel:build-model
92
93 >**Note**: Make sure your command line folder is set to the root of your project
94 >(`sf_sandbox/`) when you call the `symfony` command.
95 >
96 >If you receive the error 'Could not perform XLST transformation',
97 >check that you have the php_xsl extension enabled in your php.ini file.
98
99 A few classes are created in the `sf_sandbox/lib/model/` directory. These are the
100 classes of the object-relational mapping system, which allows us to have access to a relational
101 database from within an object-oriented code without writing a single SQL query.
102 By default, symfony uses the Propel library for this purpose. These classes are
103 part of the **model** of our application
104 (find more in the [model chapter](http://www.symfony-project.org/book/1_1/08-Inside-the-Model-Layer)).
105
106 Now, we need to convert the schema to SQL statements to initialize the database tables.
107 By default, the symfony sandbox is configured to work out of the box with a simple
108 SQLite file, so no database initialization is required.
109 You still need to check that the SQLite extension is installed and enabled correctly
110 (you can check this in `php.ini` - see how
111 [in the PHP documentation](http://fr3.php.net/manual/en/ref.sqlite.php)).
112
113 By default, the `sf_sandbox` project will use a database file called `sandbox.db`
114 located in `sf_sandbox/data/`.
115
116 If you want to switch to MySQL for this project, use the `configure:database` task:
117
118     $ php symfony configure:database mysql://root:pa$$word@localhost/symfony_project
119
120 Change the DSN argument to match your settings (username, password, host, and database name)
121 and then create the database with the command line or a web interface (as described in
122 the [model chapter](http://www.symfony-project.org/book/1_1/08-Inside-the-Model-Layer)).
123 Then, open `sf_sandbox/config/databases.yml` and set 'phptype' to 'mysql', and 'database'
124 to the name of your MySQL database.
125
126 >**Caution**
127 >If you use SQLite as your database engine, you will have to change some rights on *nix systems:
128 >
129 >     $ chmod 777 data data/sandbox.db
130
131 Now type in the command line:
132
133     $ php symfony propel:build-sql
134
135 A `lib.model.schema.sql` file is created in `sf_sandbox/data/sql/`.
136 The SQL statements found is this file can be used to initialize a database with
137 the same table structure.
138
139 To build the table structure based on the the SQL file, type:
140
141     $ php symfony propel:insert-sql
142
143 >**Note**: Don't worry if there is a warning at that point, it is normal.
144 >The `propel:insert-sql` command removes existing tables before adding the ones
145 >from your `lib.model.schema.sql`, and there are no tables to remove at the moment.
146
147 As you want to be able to create and edit blog posts and comments, you also need to
148 generate some forms based on the model schema:
149
150     $ php symfony propel:build-forms
151
152 This task generates classes in the `sf_sandbox/lib/form/` directory.
153 These classes are used to manage your model objects as forms.
154
155 >**Tip**
156 >All the above commands can be done in a single one by using `propel:build-all`.
157
158 Create the application
159 ----------------------
160
161 The basic features of a blog are to be able to Create, Retrieve, Update and
162 Delete (CRUD) posts and comments. As you are new to symfony, you will not create
163 symfony code from scratch, but rather let it generate the code that you may use
164 and modify as needed. Symfony can interpret the data model to generate the CRUD
165 interface automatically:
166
167     $ php symfony propel:generate-crud --non-verbose-templates --with-show frontend post BlogPost
168     $ php symfony propel:generate-crud --non-verbose-templates frontend comment BlogComment
169     $ php symfony cache:clear
170
171 >**Tip**
172 >When using the `propel:generate-crud` task, you have used the `--non-verbose-templates`
173 >option. If you want to learn the meaning of the available
174 >arguments and options for a given task, you can use the special `help` task:
175 >
176 >     $ php symfony help propel:generate-crud
177
178 You now have two modules (`post` and `comment`) that will let you manipulate
179 objects of the `BlogPost` and `BlogComment` classes. A **module** usually represents a
180 page or a group of pages with a similar purpose. Your new modules are located
181 in the `sf_sandbox/apps/frontend/modules/` directory, and they are accessible
182 by the URLs:
183
184     http://localhost/sf_sandbox/web/frontend_dev.php/post
185     http://localhost/sf_sandbox/web/frontend_dev.php/comment
186
187 If you try to create a comment, you will have an error because symfony doesn't yet
188 know how to convert a post object to a string. Edit the `BlogPost` class
189 (`lib/model/BlogPost.php`) and add the `__toString()` method:
190
191     [php]
192     class BlogPost extends BaseBlogPost
193     {
194       public function __toString()
195       {
196         return $this->getTitle();
197       }
198     }
199
200 Lastly, add the following CSS to `sf_sandbox/web/css/main.css`:
201
202     body, td
203     {
204       font-family: Arial, Verdana, sans-serif;
205       font-size: 12px;
206     }
207
208     td { margin: 4px; padding: 4px; }
209
210 Now, feel free to create some new posts to make the blog look less empty.
211
212 ![post CRUD](/images/tutorials/mfp_1_1/first-crud.png)
213
214 Find more about [generators](http://www.symfony-project.org/book/1_1/14-Generators)
215 and the explanation of symfony projects
216 [structure](http://www.symfony-project.org/book/1_1/04-The-Basics-of-Page-Creation)
217 (project, application, module).
218
219 >**Note**: In the URLs above, the name of the main script - called the
220 >*front controller* in symfony - was changed from `index.php` to `frontend_dev.php`.
221 >The two scripts access the same application (`frontend`), but in different environments.
222 >With `frontend_dev.php`, you access the application in the **development environment**,
223 >which provides handy development tools like the debug toolbar on the top right
224 >of the screen and the live configuration engine. That's why the processing of
225 >each page is slower than when using `index.php`, which is the front controller
226 >of the **production environment**, optimized for speed. If you want to keep on
227 >using the production environment, replace `frontend_dev.php/` by `index.php/`
228 >in the following URLs, but don't forget to clear the cache before watching the
229 >changes:
230 >
231 >     $ php symfony cache:clear
232 >
233 >     http://localhost/sf_sandbox/web/index.php/
234
235 Find more about [environments](http://www.symfony-project.org/book/1_1/05-Configuring-Symfony#Environments).
236
237 Modify the layout
238 -----------------
239
240 In order to navigate between the two new modules, the blog needs some global navigation.
241
242 Edit the global template `sf_sandbox/apps/frontend/templates/layout.php` and
243 change the content of the `<body>` tag to:
244
245     [php]
246     <div id="container" style="width:700px;margin:0 auto;border:1px solid grey;padding:10px">
247       <div id="navigation" style="display:inline;float:right">
248         <ul>
249           <li><?php echo link_to('List of posts', 'post/index') ?></li>
250           <li><?php echo link_to('List of comments', 'comment/index') ?></li>
251         </ul>
252       </div>
253       <div id="title">
254         <h1><?php echo link_to('My first symfony project', '@homepage') ?></h1>
255       </div>
256
257       <div id="content" style="clear:right">
258         <?php echo $sf_data->getRaw('sf_content') ?>
259       </div>
260     </div>
261
262 Please forgive the poor design and the use of inner-tag css, but
263 one hour is rather a short amount of time!
264
265 ![post CRUD in layout](/images/tutorials/mfp_1_1/first-crud-layout.png)
266
267 While you are at it, you can change the title of your pages.
268 Edit the view configuration file of the application (`sf_sandbox/apps/frontend/config/view.yml`),
269 locate the line showing the `title` key and change it something appropriate. Note that
270 several lines here are commented out with a hash symbol - you can uncomment these if
271 you wish.
272
273     default:
274       http_metas:
275         content-type: text/html
276
277       metas:
278         title:        The best blog ever
279         #description:  symfony project
280         #keywords:     symfony, project
281         #language:     en
282         robots:       index, follow
283
284 The home page itself needs to be changed. It uses the default template of the
285 `default` module, which is kept in the framework but not in your application
286 directory. To override it, you can create a custom `main` module:
287
288     $ php symfony generate:module frontend main
289
290 By default, the `index` action shows a default congratulations screen.
291 To remove it, edit the `sf_sandbox/apps/frontend/modules/main/actions/actions.class.php`
292 and remove the content of the `executeIndex()` method as follows:
293
294     [php]
295     /**
296      * Executes index action
297      *
298      * @param sfRequest $request A request object
299      */
300     public function executeIndex($request)
301     {
302     }
303
304 Edit the `sf_sandbox/apps/frontend/modules/main/templates/indexSuccess.php` file
305 to show a nice welcome message:
306
307     [php]
308     <h1>Welcome to my new blog</h1>
309     <p>You are the <?php echo rand(1000,5000) ?>th visitor today.</p>
310
311 Now, you must tell symfony which action to execute when the homepage is requested.
312 To do so, edit the `sf_sandbox/apps/frontend/config/routing.yml` and change
313 the `homepage` rule as follows:
314
315     [yml]
316     homepage:
317       url:   /
318       param: { module: main, action: index }
319
320 Check the result by requesting the home page again:
321
322     http://localhost/sf_sandbox/web/frontend_dev.php/
323
324 ![New home page](/images/tutorials/mfp_1_1/first-welcome.png)
325
326 Go ahead, start using your new web app. Make sure you've created a test post, and
327 also create a test comment against your post.
328
329 Find more about [views and templates](http://www.symfony-project.org/book/1_1/07-Inside-the-View-Layer).
330
331 Pass data from the action to the template
332 -----------------------------------------
333
334 That was fast, wasn't it? Now it is time to mix the `comment` module into the
335 `post` one to get comments displayed below posts.
336
337 First, you need to make the post comments available for the post display template.
338 In symfony, this kind of logic is kept in **actions**. Edit the actions file
339 `sf_sandbox/apps/frontend/modules/post/actions/actions.class.php` and change
340 the `executeShow()` method by adding the four last lines:
341
342     [php]
343     public function executeShow($request)
344     {
345       $this->blog_post = BlogPostPeer::retrieveByPk($request->getParameter('id'));
346       $this->forward404Unless($this->blog_post);
347
348       $c = new Criteria();
349       $c->add(BlogCommentPeer::BLOG_POST_ID, $request->getParameter('id'));
350       $c->addAscendingOrderByColumn(BlogCommentPeer::CREATED_AT);
351       $this->comments = BlogCommentPeer::doSelect($c);
352     }
353
354 The `Criteria` and `-Peer` objects are part of Propel's object-relational mapping.
355 Basically, these four lines will handle a SQL query to the `blog_comment` table to get
356 the comments related to the current `blog_post`. Now, modify the post display template
357 `sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php` by adding at the end:
358
359     [php]
360     // ...
361     <?php use_helper('Text', 'Date') ?>
362
363     <hr />
364     <?php if ($comments) : ?>
365       <p><?php echo count($comments) ?> comments to this post.</p>
366       <?php foreach ($comments as $comment): ?>
367         <p><em>posted by <?php echo $comment->getAuthor() ?> on <?php echo format_date($comment->getCreatedAt()) ?></em></p>
368         <div class="comment" style="margin-bottom:10px;">
369           <?php echo simple_format_text($comment->getBody()) ?>
370         </div>
371       <?php endforeach; ?>
372     <?php endif; ?>
373
374 This page uses new PHP functions that are called 'helpers', because they do some tasks
375 for you that would normally require more time and code. Create a new comment for your
376 first post, then check again the first post, either by clicking on its number
377 in the list, or by typing directly:
378
379     http://localhost/sf_sandbox/web/frontend_dev.php/post/show?id=1
380
381 ![Comment under post](/images/tutorials/mfp_1_1/first-comments-under-post.png)
382
383 This is getting good.
384
385 Find more about the [naming conventions](http://www.symfony-project.org/book/1_1/07-Inside-the-View-Layer)
386 linking an action to a template.
387
388 Add a record relative to another table
389 --------------------------------------
390
391 Currently you can't add comments to posts directly; if you edit a post, you
392 have to go to the comments editing section, create a new one, then select the post you
393 want to comment on using the drop-down menu. The screen looks like this:
394
395 ![Related comment](/images/tutorials/mfp_1_1/related-comment-1.png)
396
397 It would be better if there was a link on each post editing page to go straight to
398 the comment editing facility, so let's arrange that first. In the
399 `sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php` template, add this line at the bottom:
400
401     [php]
402     <?php echo link_to('Add a comment', 'comment/edit?post_id='.$blog_post->getId()) ?>
403
404 The `link_to()` helper creates a hyperlink pointing to the
405 `edit` action of the `comment` module, so you can add a comment directly from the post
406 details page. At the moment, however, the comments edit page still offers a form element
407 to select which post to relate a comment to. This would be best
408 replaced by a hidden field (containing the post primary key) if the comments edit page
409 URL is called specifying that key.
410
411 In symfony, forms are managed by classes. So, let's edit the `BlogCommentForm` class to
412 make our changes. The file is located under the `sf_sandbox/lib/form/` directory:
413
414     [php]
415     class BlogCommentForm extends BaseBlogCommentForm
416     {
417       /**
418        * Configure method, called when the form is instantiated
419        */
420       public function configure()
421       {
422         $this->widgetSchema['blog_post_id'] = new sfWidgetFormInputHidden();
423       }
424     }
425
426 >**Note**: For more details on the form framework, you are advised to read the
427 [Forms Book](http://www.symfony-project.org/book/forms/1_1/en/).
428
429 After you have made this change, you will now be able to add a comment directly to a
430 post without having to explicitly specify the post to attach it to:
431
432 ![Related comment](/images/tutorials/mfp_1_1/related-comment-2.png)
433
434 Next, after adding a comment, we want the user to come back to the post it relates to,
435 rather that remaining on the comment editing page. To accomplish this, we need to edit
436 `executeUpdate` method. Find the following code in `sf_sandbox/apps/frontend/modules/comment/actions/actions.class.php`:
437
438     [php]
439     $this->form = new BlogCommentForm(BlogCommentPeer::retrieveByPk($request->getParameter('id')));
440     $this->form->setDefault('blog_post_id', $request->getParameter('post_id'));
441
442     if ($request->isMethod('post'))
443     {
444       $this->form->bind($request->getParameter('blog_comment'));
445       if ($this->form->isValid())
446       {
447         $blog_comment = $this->form->save();
448
449         $this->redirect('comment/edit?id='.$blog_comment->getId());
450       }
451     }
452
453 And change the redirect line so it reads thus:
454
455     [php]
456     $this->redirect('post/show?id='.$blog_comment->getBlogPostId());
457
458 This will ensure that when a comment is saved, the user is returned to the post that
459 the comment is related to. There are two things here that are worthy of note: firstly,
460 the save is achieved simply by calling the save method on the form object (this is
461 because the form is associated with the Propel model and therefore knows how to serialize
462 the object back to the database). Secondly, we redirect immediately
463 after the save, so that if the page is subsequently refreshed, the user is not asked if
464 they wish to repeat the POST action again.
465
466 Okay, so that wraps up this part of the tutorial. You wanted a blog? You have a blog.
467 Incidentally, since we've covered symfony actions a lot here, you may wish to find out
468 more about them from
469 [the manual](http://www.symfony-project.org/book/1_1/06-Inside-the-Controller-Layer).
470
471 Form Validation
472 ---------------
473
474 Visitors can enter comments, but what if they submit the form without any data
475 in it, or data that is obviously wrong? You would end up with a database containing
476 invalid rows. To avoid that, we need to set up some validation rules to specify what
477 data is allowed.
478
479 When symfony has created the form classes for us, if has generated the form elements
480 to render on the screen, but it has also added some default validation rules by introspecting
481 the schema. As the `title` is required in the `blog_post` table, you won't be able to
482 submit a form without a title. You also won't be able to submit a post with a title
483 longer than 255 character.
484
485 Let's override some of these rules now in the `BlogCommentForm` class.
486 So, open the file, and add in the following PHP code at the end of the configure() method:
487
488     [php]
489     $this->validatorSchema['email'] = new sfValidatorEmail(
490       array('required' => false),
491       array('invalid' => 'The email address is not valid'));
492
493 By redefining the validator for the `email` column, we have overridden the default behavior.
494
495 Once this new rule is in place, try saving a comment with a bad email
496 address - you now have a robust form! You will notice a number of
497 things: first of all, where the form contains data, that data will automatically be
498 preserved during the form submission. This saves the user having to type it back in
499 (and normally is something in that the programmer has to arrange manually). Also, errors
500 (in this case) are placed next to the fields that failed their associated validation
501 tests.
502
503 Now would be a good time to explain a little about how the form save process works. It
504 uses the following action code, which you edited earlier in
505 `/sf_sandbox/apps/frontend/modules/comment/actions/actions.class.php`:
506
507     [php]
508     $this->form = new BlogCommentForm(BlogCommentPeer::retrieveByPk($request->getParameter('id')));
509     $this->form->setDefault('blog_post_id', $request->getParameter('post_id'));
510
511     if ($request->isMethod('post'))
512     {
513       $this->form->bind($request->getParameter('blog_comment'));
514       if ($this->form->isValid())
515       {
516         $blog_comment = $this->form->save();
517
518         $this->redirect('post/show?id='.$blog_comment->getBlogPostId());
519       }
520     }
521
522 After the form object is instantiated, the following happens:
523
524  * The code checks that the HTTP method is a POST
525  * The parameter array `blog_comment` is retrieved. The `getParameter()` method detects
526 that this name is an array of values in the form, not a single value, and returns them
527 as an associative array (e.g. form element `blog_comment[author]` is returned in an
528 array having a key of `author`)
529  * This associative array is then fed into the form using a process called **binding**,
530 in which the values are used to fill form elements in the form object. After this, the
531 values are determined to have either passed or failed the validation checks
532  * Only if the form is valid does the save go ahead, after which the page redirects
533 immediately to the show action.
534
535 ![Form validation](/images/tutorials/mfp_1_1/first-form-validation.png)
536
537 Find more about
538 [form validation](http://www.symfony-project.org/book/1_1/en/02-Form-Validation).
539
540 Change the URL format
541 ---------------------
542
543 Did you notice the way the URLs are rendered? You can make them more user and
544 search engine-friendly. Let's use the post title as a URL for posts.
545
546 The problem is that post titles can contain special characters like spaces.
547 If you just escape them, the URL will contain some ugly `%20` strings,
548 so the model needs to be extended with a new method in the `BlogPost` object,
549 to get a clean, stripped title. To do that, edit the file `BlogPost.php` located
550 in the `sf_sandbox/lib/model/` directory, and add the following method:
551
552     [php]
553     public function getStrippedTitle()
554     {
555       $result = strtolower($this->getTitle());
556
557       // strip all non word chars
558       $result = preg_replace('/\W/', ' ', $result);
559
560       // replace all white space sections with a dash
561       $result = preg_replace('/\ +/', '-', $result);
562
563       // trim dashes
564       $result = preg_replace('/\-$/', '', $result);
565       $result = preg_replace('/^\-/', '', $result);
566
567       return $result;
568     }
569
570 Now you can create a `permalink` action for the `post` module. Add the following
571 method to the `sf_sandbox/apps/frontend/modules/post/actions/actions.class.php`:
572
573     [php]
574     public function executePermalink($request)
575     {
576       $posts = BlogPostPeer::doSelect(new Criteria());
577       $title = $request->getParameter('title');
578       foreach ($posts as $post)
579       {
580         if ($post->getStrippedTitle() == $title)
581         {
582           $request->setParameter('id', $post->getId());
583
584           return $this->forward('post', 'show');
585         }
586       }
587
588       $this->forward404();
589     }
590
591 The post list can call this `permalink` action instead of the `show` one for
592 each post. In `sf_sandbox/apps/frontend/modules/post/templates/indexSuccess.php`, delete the `id` table
593 header and cell, and change the `Title` cell from this:
594
595     [php]
596     <td><?php echo $blog_post->getTitle() ?></td>
597
598 to this, which uses a named rule we will create in a second:
599
600     [php]
601     <td><?php echo link_to($blog_post->getTitle(), '@post?title='.$blog_post->getStrippedTitle()) ?></td>
602
603 Just one more step: edit the `routing.yml` located in the
604 `sf_sandbox/apps/frontend/config/` directory and add these rules at the top:
605
606     list_of_posts:
607       url:   /latest_posts
608       param: { module: post, action: index }
609
610     post:
611       url:   /blog/:title
612       param: { module: post, action: permalink }
613
614 Now navigate again in your application to see your new URLs in action. If you get an
615 error, it may be because the routing cache needs to be cleared. To do that, type the
616 following at the command line while in your `sf_sandbox` folder:
617
618     $ php symfony cc
619
620 ![Routed URLs](/images/tutorials/mfp_1_1/first-routing.png)
621
622 Find more about [smart URLs](http://www.symfony-project.org/book/1_1/09-Links-and-the-Routing-System).
623
624 Cleaning up the frontend
625 ------------------------
626
627 Well, if this is meant to be a blog, then it is perhaps a little strange that everybody
628 is allowed to post! This isn't generally how blogs are meant to work, so let's clean up our
629 templates a bit.
630
631 In the template `sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php`, get rid of the
632 'edit' link by removing the line:
633
634     <a href="<?php echo url_for('post/edit?id='.$blog_post->getId()) ?>">Edit</a>
635     &nbsp;
636
637 Do the same for the `sf_sandbox/apps/frontend/modules/post/templates/indexSuccess.php` template and remove:
638
639     <a href="<?php echo url_for('post/edit') ?>">Create</a>
640
641 You should also remove the following methods from `sf_sandbox/apps/frontend/modules/post/actions/actions.class.php`:
642
643  * `executeEdit`
644  * `executeDelete`
645
646 This means that readers cannot post anymore, which is what is required.
647
648 Generation of the backend
649 -------------------------
650
651 To allow you to write posts, let's create a backend application by typing in the
652 command line (still from the `sf_sandbox` project directory):
653
654     $ php symfony generate:app backend
655     $ php symfony propel:init-admin backend post BlogPost
656     $ php symfony propel:init-admin backend comment BlogComment
657
658 This time, we use the [admin generator](http://www.symfony-project.org/book/1_1/14-Generators).
659 It offers much more features and customization than the basic CRUD generator.
660
661 Just like you did for the `frontend` application, edit the layout (`apps/backend/templates/layout.php`)
662 to add global navigation:
663
664     <div id="navigation">
665       <ul style="list-style:none;">
666         <li><?php echo link_to('Manage posts', 'post/index') ?></li>
667         <li><?php echo link_to('Manage comments', 'comment/index') ?></li>
668       </ul>
669     </div>
670     <div id="content">
671       <?php echo $sf_data->getRaw('sf_content') ?>
672     </div>
673
674 You can access your new back-office application in the development environment by calling:
675
676     http://localhost/sf_sandbox/web/backend_dev.php/post
677
678 ![basic generated admin](/images/tutorials/mfp_1_1/first-basic-admin.png)
679
680 The great advantage of the generated admin is that you can easily customize it by
681 editing a configuration file.
682
683 Change the `sf_sandbox/apps/backend/modules/post/config/generator.yml` to:
684
685     generator:
686       class:              sfPropelAdminGenerator
687       param:
688         model_class:      BlogPost
689         theme:            default
690         fields:
691           title:          { name: Title }
692           excerpt:        { name: Excerpt }
693           body:           { name: Body }
694           nb_comments:    { name: Comments }
695           created_at:     { name: Creation date }
696         list:
697           title:          Post list
698           layout:         tabular
699           display:        [=title, excerpt, nb_comments, created_at]
700           object_actions:
701             _edit:        ~
702             _delete:      ~
703           max_per_page:   5
704           filters:        [title, created_at]
705         edit:
706           title:          Post detail
707           fields:
708             title:        { type: input_tag, params: size=53 }
709             excerpt:      { type: textarea_tag, params: size=50x2 }
710             body:         { type: textarea_tag, params: size=50x10 }
711             created_at:   { type: input_date_tag, params: rich=on }
712
713 Note that among the existing columns of the `blog_post` table, the admin will look
714 for a column (or getter method) for `nb_comments`. Since this is a generated value and
715 not a column, we can add a getter to our model (`sf_sandbox/lib/model/BlogPost.php`):
716
717     [php]
718     public function getNbComments()
719     {
720       return count($this->getBlogComments());
721     }
722
723 Now refresh the Post administration screen to see the changes:
724
725 ![customized generated admin](/images/tutorials/mfp_1_1/first-custom-admin.png)
726
727 Restrict access to the backend
728 ------------------------------
729
730 Currently the backend application can be accessed by everybody. We therefore need to
731 add some access restrictions. In `apps/backend/config/`, edit the file called
732 `security.yml` and reset the content to:
733
734     all:
735       is_secure: on
736
737 Now you can no longer access these modules, unless you are logged in. However,
738 currently the login action doesn't exist! Of course, we can easily add it.
739 To do so, let's install a suitable plugin from the symfony website - sfGuardPlugin.
740 Type the following at the command line:
741
742     $ php symfony plugin:install sfGuardPlugin
743
744 This will download the plugin from the symfony plugin repository. At this point the
745 command line should give you an indication that the installation was successful:
746
747     $ php symfony plugin:install sfGuardPlugin
748     >> plugin    installing plugin "sfGuardPlugin"
749     >> sfPearFrontendPlugin Attempting to discover channel "pear.symfony-project.com"...
750     >> sfPearFrontendPlugin downloading channel.xml ...
751     >> sfPearFrontendPlugin Starting to download channel.xml (663 bytes)
752     >> sfPearFrontendPlugin .
753     >> sfPearFrontendPlugin ...done: 663 bytes
754     >> sfPearFrontendPlugin Auto-discovered channel "pear.symfony-project.com", alias
755     >> sfPearFrontendPlugin "symfony", adding to registry
756     >> sfPearFrontendPlugin Attempting to discover channel
757     >> sfPearFrontendPlugin "plugins.symfony-project.org"...
758     >> sfPearFrontendPlugin downloading channel.xml ...
759     >> sfPearFrontendPlugin Starting to download channel.xml (639 bytes)
760     >> sfPearFrontendPlugin ...done: 639 bytes
761     >> sfPearFrontendPlugin Auto-discovered channel "plugins.symfony-project.org", alias
762     >> sfPearFrontendPlugin "symfony-plugins", adding to registry
763     >> sfPearFrontendPlugin downloading sfGuardPlugin-2.2.0.tgz ...
764     >> sfPearFrontendPlugin Starting to download sfGuardPlugin-2.2.0.tgz (18,589 bytes)
765     >> sfPearFrontendPlugin ...done: 18,589 bytes
766     >> sfSymfonyPluginManager Installation successful for plugin "sfGuardPlugin"
767
768 Next, the plugin needs to be enabled. Edit `sf_sandbox/apps/backend/config/settings.yml`
769 to enable the login system, as follows. Uncomment the `all:` key and add the following:
770
771     # (Some stuff here)
772     all:
773       .actions:
774         login_module:    sfGuardAuth   # To be called when a non-authenticated user
775         login_action:    signin     # Tries to access a secure page
776
777         secure_module:   sfGuardAuth   # To be called when a user doesn't have
778         secure_action:   secure    # The credentials required for an action
779
780       .settings:
781         enabled_modules: [default, sfGuardAuth, sfGuardGroup, sfGuardPermission, sfGuardUser]
782
783 Now, enable the new user system by editing `sf_sandbox/apps/backend/lib/myUser.class.php`,
784 so that it reads:
785
786     [php]
787     class myUser extends sfGuardSecurityUser
788     {
789     }
790
791 Now, the model needs to be built, the new SQL needs to be generated, and the database tables
792 need to be updated:
793
794     $ php symfony propel:build-model
795     $ php symfony propel:build-sql
796     $ php symfony propel:insert-sql
797
798 >**Note**
799 >When launching the `propel:insert-sql` task, symfony will drop all tables to re-create them.
800 >As during the development this happens a lot, symfony can store initial data in fixtures
801 >(see [populating a database](http://www.symfony-project.org/book/1_1/16-Application-Management-Tools#Populating%20a%20Database) for more information).
802
803 Now, clear your cache again. Finally, create a user by running:
804
805     $ symfony guard:create-user frontend jon SoMePasSwOrD
806
807 Let's now make the post administration module the default one in our backend system. To
808 do this, open up the `apps/backend/config/routing.yml` file and locate the
809 `homepage` key. Change the module from `default` to `post`.
810
811 At that point, if you try to access the posts management
812 (http://localhost/sf_sandbox/web/backend_dev.php/post), you will have to enter a valid username
813 and password:
814
815 ![login form](/images/tutorials/mfp_1_1/logon.png)
816
817 Find more about [security](http://www.symfony-project.org/book/1_1/06-Inside-the-Controller-Layer#Action%20Security).
818
819 Conclusion
820 ----------
821
822 Ok, the hour is out. You made it. Now you can use both applications in the production
823 environment and play with them:
824
825     frontend:   http://localhost/sf_sandbox/web/index.php/
826     backend:    http://localhost/sf_sandbox/web/backend.php/
827
828 At this point, if you meet an error, it might be because you changed the model after
829 some actions were put in cache (cache isn't activated in the development environment).
830 To clear the cache, simply type:
831
832     $ php symfony cc
833
834 See, the application is fast and runs smoothly. Pretty cool, isn't it?
835 Feel free to explore the code, add new modules, and change the design of pages.
836
837 And don't forget to mention your working symfony applications in the
838 [symfony Wiki](http://trac.symfony-project.org/wiki/ApplicationsDevelopedWithSymfony)!
Note: See TracBrowser for help on using the browser.

The Sensio Labs Network

Since 1998, Sensio Labs has been promoting the Open-Source software movement by providing quality web application development, training, consulting, and supporting several large Open-Source projects.